restructure to be a littel more flexible
[lwes-erlang/github-mirror.git] / src / lwes_net_udp.erl
blob25b783a20143309e741624afbc175971500371c9
1 -module (lwes_net_udp).
3 -include_lib ("lwes_internal.hrl").
5 % This modules attempts to separate out the low level network details from the
6 % rest of the system. It is by no means necessary to use this as a transport
7 % but the base system assumes UDP or UDP/Multicast, so those forms are
8 % attempted to be encapsulated here. A certain set of options is set by
9 % default.
10 -export([new/2,
11 open/2,
12 send/3,
13 close/1,
14 address/1
15 ]).
17 -define (DEFAULT_TTL, 25).
18 -define (DEFAULT_RECBUF, 16777216).
20 -record(lwes_net_udp, { ip :: inet:ip_address(),
21 port :: inet:port_number(),
22 is_multicast = false :: boolean(),
23 ttl = ?DEFAULT_TTL :: non_neg_integer(),
24 recbuf = ?DEFAULT_RECBUF :: non_neg_integer(),
25 options = []
26 }).
27 new (listener, Config) ->
28 InitialStruct = check_config (Config),
29 case InitialStruct of
30 #lwes_net_udp { ip = Ip, is_multicast = true, ttl = TTL,
31 recbuf = Recbuf, options = ExtraOptions} ->
32 InitialStruct#lwes_net_udp {
33 options = merge_options ([ { ip, Ip },
34 { multicast_ttl, TTL },
35 { multicast_loop, false },
36 { add_membership, {Ip, {0,0,0,0}}},
37 { reuseaddr, true },
38 { recbuf, Recbuf },
39 { mode, binary }
41 ExtraOptions)
43 #lwes_net_udp { is_multicast = false,
44 recbuf = Recbuf, options = ExtraOptions} ->
45 InitialStruct#lwes_net_udp {
46 options = merge_options ([ { reuseaddr, true },
47 { recbuf, Recbuf },
48 { mode, binary }
50 ExtraOptions)
52 end;
53 new (emitter, Config) ->
54 InitialStruct = check_config (Config),
55 InitialOptions = InitialStruct#lwes_net_udp.options,
56 TTL = InitialStruct#lwes_net_udp.ttl,
58 % emitters don't care about most options, but including ttl
59 % means we can use the same emitter for multicast as well as
60 % unicast traffic
61 InitialStruct#lwes_net_udp {
62 options = merge_options ([ { multicast_ttl, TTL },
63 { mode, binary }
65 InitialOptions)
68 open (listener, #lwes_net_udp { port = Port, options = Options}) ->
69 gen_udp:open (Port, Options);
70 open (emitter, #lwes_net_udp { options = Options}) ->
71 gen_udp:open (0, Options).
73 % allow some of the other forms of config, so the config structure can
74 % just be reused when sending in most cases
75 send (Socket, {Ip, Port, _}, Packet) ->
76 gen_udp:send (Socket, Ip, Port, Packet);
77 send (Socket, {Ip, Port}, Packet) ->
78 gen_udp:send (Socket, Ip, Port, Packet);
79 send (Socket, #lwes_net_udp { ip = Ip, port = Port}, Packet) ->
80 gen_udp:send (Socket, Ip, Port, Packet).
82 close (Socket) ->
83 gen_udp:close (Socket).
85 address (#lwes_net_udp { ip = Ip, port = Port }) ->
86 {Ip, Port}.
88 %%====================================================================
89 %% Internal functions
90 %%====================================================================
92 % this will merge in any passed in Overrides options on top of any defaults
93 % listed. it's currently assumed there are no raw defaults
94 merge_options (Defaults, Overrides) ->
95 % first split into options and raw options
96 {OverrideOpts, OverrideRaw} = partition (Overrides),
97 % then merge, preferring overrides and add back the raw at the end
98 lists:ukeymerge(1, lists:sort(OverrideOpts), lists:sort(Defaults))
99 ++ OverrideRaw.
101 % gen_udp has a long list of mostly consistent options, the few outliers are
102 % binary | list - these can also be specified as {mode, binary | list}
103 % {raw,_,_,_} - these do not have a 2-tuple form
104 % this function will normalize on {mode, binary | list} and separate out
105 % {raw,_,_,_} options, resulting in two lists
106 partition (Options) ->
107 lists:foldl( fun (list, {A,O}) -> {[{mode,list} | A],O};
108 (binary, {A,O}) -> {[{mode,binary} | A],O};
109 (R = {raw,_,_,_},{A,O}) -> {A,[R|O]};
110 (Other, {A,O}) -> {[Other|A],O}
111 end,
112 {[],[]},
113 Options).
115 parse_options ([], AccumlatedOptions) ->
116 AccumlatedOptions;
117 % some options are simply passed through
118 parse_options ([{recbuf, Recbuf} | Rest], N) ->
119 parse_options (Rest, N#lwes_net_udp{ recbuf = Recbuf });
120 % others have slightly different naming, so are changed
121 parse_options ([{ttl, TTL} | Rest], N ) ->
122 parse_options (Rest, N#lwes_net_udp { ttl = TTL });
123 % check_config options which get converted to raw options
124 parse_options ([reuseport | Rest],
125 N = #lwes_net_udp { options = OptionsIn}) ->
126 parse_options (Rest, N#lwes_net_udp { options = reuseport() ++ OptionsIn});
127 % finally options which we assume are correct for now
128 parse_options ([O | Rest],
129 N = #lwes_net_udp { options = OptionsIn}) ->
130 parse_options (Rest, N#lwes_net_udp { options = [ O | OptionsIn]}).
132 is_multicast ({N1, _, _, _}) when N1 >= 224, N1 =< 239 ->
133 true;
134 is_multicast (_) ->
135 false.
137 reuseport() ->
138 case os:type() of
139 {unix, linux} ->
140 [ {raw, 1, 15, <<1:32/native>>} ];
141 {unix, OS} when OS =:= darwin;
142 OS =:= freebsd;
143 OS =:= openbsd;
144 OS =:= netbsd ->
145 [ {raw, 16#ffff, 16#0200, <<1:32/native>>} ];
146 _ -> []
147 end.
149 check_ip (Ip) when ?is_ip_addr (Ip) ->
151 check_ip (IpList) when is_list (IpList) ->
152 case inet_parse:address (IpList) of
153 {ok, Ip} -> Ip;
154 _ ->
155 erlang:error(badarg)
156 end;
157 check_ip (_) ->
158 % essentially turns function_clause error into badarg
159 erlang:error (badarg).
161 check_port(Port) when ?is_uint16 (Port) ->
162 Port;
163 check_port(_) ->
164 % essentially turns function_clause error into badarg
165 erlang:error (badarg).
167 check_config ({Ip, Port}) ->
168 CheckedIp = check_ip (Ip),
169 #lwes_net_udp { ip = CheckedIp,
170 port = check_port(Port),
171 is_multicast = is_multicast(CheckedIp) };
172 check_config ({Ip, Port, Options}) when is_list (Options) ->
173 parse_options (Options, check_config ({Ip,Port}));
174 check_config (_) ->
175 % essentially turns function_clause error into badarg
176 erlang:error (badarg).
179 %%--------------------------------------------------------------------
180 %%% Test functions
181 %%--------------------------------------------------------------------
182 -ifdef (TEST).
183 -include_lib ("eunit/include/eunit.hrl").
185 test_one (Config) ->
186 EmitterConfig = new (emitter, Config),
187 ListenerConfig = new (listener, Config),
188 {ok, Emitter} = lwes_net_udp:open(emitter, EmitterConfig),
189 {ok, Listener}= lwes_net_udp:open(listener, ListenerConfig),
190 Input = <<"hello">>,
191 lwes_net_udp:send(Emitter, EmitterConfig, Input),
192 Output = receive {udp,_,_,_,P} -> P end,
193 lwes_net_udp:close(Listener),
194 lwes_net_udp:close(Emitter),
195 ?assertEqual (Input, Output).
197 % test emission and receipt with various options
198 lwes_net_udp_test_ () ->
199 { inorder,
201 fun () -> test_one (C) end
202 || C
203 <- [
204 {"127.0.0.1",12321},
205 {"127.0.0.1",12321,[{ttl,3}]},
206 {"127.0.0.1",12321,[{ttl,12},{recbuf, 65535}]},
207 {"224.1.1.111",12321,[{multicast_loop, true}]}
212 % test address functionality
213 lwes_net_udp_address_test_ () ->
215 ?_assertEqual (Expected, address(Given))
216 || {Expected, Given}
217 <- [
218 {{{127,0,0,1},9191}, new(emitter, {"127.0.0.1",9191})}
222 % test various error cases
223 lwes_net_udp_error_test_ () ->
225 ?_assertError (badarg, new(emitter,{foo,bar})),
226 ?_assertError (badarg, new(emitter,{"256.0.0.0",99})),
227 ?_assertError (badarg, new(emitter,{"127.0.0.1",99999}))
230 % test options merging
231 lwes_net_udp_options_test_ () ->
233 ?_assertEqual(Expected, merge_options (Defaults, Overrides))
234 || {Expected, Defaults, Overrides}
235 <- [
236 { [{mode,list}], [{mode,binary}], [list] },
237 { [{mode,binary}], [{mode,binary}], [binary] },
238 { [{mode,list},{raw,a,b,c}], [{mode,binary}], [list,{raw,a,b,c}] },
239 { [{add_membership,{{127,0,0,1},{0,0,0,0}}},
240 {ip,{127,0,0,1}},
241 {mode,binary},
242 {multicast_loop,true},
243 {multicast_ttl,12},
244 {recbuf,65535},
245 {reuseaddr,true}],
246 [{ip, {127,0,0,1}},
247 {multicast_ttl, 12},
248 {multicast_loop, false},
249 {add_membership, {{127,0,0,1}, {0,0,0,0}}},
250 {reuseaddr, true},
251 {recbuf, 65535},
252 {mode, binary}
254 [{multicast_loop, true}]
260 check_config_test_ () ->
262 ?_assertEqual (#lwes_net_udp {ip = {127,0,0,1}, port = 9191,
263 is_multicast = false,
264 ttl = 3, recbuf = 65535},
265 check_config ({{127,0,0,1},9191,[{ttl,3},{recbuf,65535}]})),
266 ?_assertEqual (#lwes_net_udp {ip = {127,0,0,1}, port = 9191,
267 is_multicast = false,
268 ttl = 3, recbuf = 65535},
269 check_config ({"127.0.0.1",9191,[{ttl,3},{recbuf,65535}]})),
270 ?_assertEqual (#lwes_net_udp {ip = {127,0,0,1}, port = 9191,
271 is_multicast = false,
272 ttl = 3, recbuf = 65535,
273 options = reuseport()},
274 check_config ({"127.0.0.1",9191,
275 [{ttl,3}, {recbuf,65535}, reuseport]})),
276 ?_assertEqual (#lwes_net_udp {ip = {127,0,0,1}, port = 9191,
277 is_multicast = false,
278 ttl = 3, recbuf = ?DEFAULT_RECBUF},
279 check_config ({"127.0.0.1",9191,[{ttl,3}]})),
280 ?_assertEqual (#lwes_net_udp {ip = {127,0,0,1}, port = 9191,
281 is_multicast = false,
282 ttl = 3, recbuf = ?DEFAULT_RECBUF},
283 check_config ({{127,0,0,1},9191,[{ttl,3}]})),
284 ?_assertEqual (#lwes_net_udp {ip = {127,0,0,1}, port = 9191,
285 is_multicast = false,
286 ttl = ?DEFAULT_TTL, recbuf = ?DEFAULT_RECBUF},
287 check_config ({"127.0.0.1",9191})),
288 ?_assertEqual (#lwes_net_udp {ip = {127,0,0,1}, port = 9191,
289 is_multicast = false,
290 ttl = ?DEFAULT_TTL, recbuf = ?DEFAULT_RECBUF},
291 check_config ({{127,0,0,1},9191})),
292 ?_assertError (badarg, check_config ({"655.0.0.1",9191,3})),
293 ?_assertError (badarg, check_config ({"655.0.0.1",9191,65})),
294 ?_assertError (badarg, check_config ({"655.0.0.1",9191})),
295 ?_assertError (badarg, check_config ({{655,0,0,1},9191})),
296 ?_assertError (badarg, check_config ({{127,0,0,1},91919}))
299 -endif.