%%%-------------------------------------------------------------------
  File     : stun_codec.erl
  Author   :   <  >
%%% Description : STUN codec
  Created :   7 Aug 2009 by   <  >
%%%
%%%
  ejabberd , Copyright ( C ) 2002 - 2010    ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License as
  published by the Free Software Foundation ; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
%%% General Public License for more details.
%%%
  You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
  Foundation , Inc. , 59 Temple Place , Suite 330 , Boston , MA
  02111 - 1307 USA
%%%
%%%-------------------------------------------------------------------
-module(stun_codec).

%% API
-export([decode/1,
	 encode/1,
	 version/1,
	 reason/1,
	 pp/1]).

%% Tests
-export([test_udp/2,
	 test_tcp/2,
	 test_tls/2,
	 test_public/0]).

-include("stun.hrl").

%%====================================================================
%% API
%%====================================================================
decode(<<0:2, Type:14, Len:16, Magic:32, TrID:96,
	Body:Len/binary, Tail/binary>>) ->
    case catch decode(Type, Magic, TrID, Body) of
	{'EXIT', _} ->
	    {error, unparsed};
	Res ->
	    {ok, Res, Tail}
    end;
decode(<<0:2, _/binary>>) ->
    more;
decode(<<>>) ->
    empty;
decode(_) ->
    {error, unparsed}.

encode(#stun{class = Class,
	     method = Method,
	     magic = Magic,
	     trid = TrID} = Msg) ->
    ClassCode = case Class of
		    request -> 0;
		    indication -> 1;
		    response -> 2;
		    error -> 3
		end,
    Type = ?STUN_TYPE(ClassCode, Method),
    Attrs = enc_attrs(Msg),
    Len = size(Attrs),
    <<0:2, Type:14, Len:16, Magic:32, TrID:96, Attrs/binary>>.

pp(Term) ->
    io_lib_pretty:print(Term, fun pp/2).

version(#stun{magic = ?STUN_MAGIC}) ->
    new;
version(#stun{}) ->
    old.

reason(300) -> <<"Try Alternate">>;
reason(400) -> <<"Bad Request">>;
reason(401) -> <<"Unauthorized">>;
reason(420) -> <<"Unknown Attribute">>;
reason(438) -> <<"Stale Nonce">>;
reason(500) -> <<"Server Error">>;
reason(_) -> <<"Undefined Error">>.

%%====================================================================
  Internal functions
%%====================================================================
decode(Type, Magic, TrID, Body) ->
    Method = ?STUN_METHOD(Type),
    Class = case ?STUN_CLASS(Type) of
		0 -> request;
		1 -> indication;
		2 -> response;
		3 -> error
	    end,
    dec_attrs(Body, #stun{class = Class,
			  method = Method,
			  magic = Magic,
			  trid = TrID}).

dec_attrs(<<Type:16, Len:16, Rest/binary>>, Msg) ->
    PaddLen = padd_len(Len),
    <<Val:Len/binary, _:PaddLen, Tail/binary>> = Rest,
    NewMsg = dec_attr(Type, Val, Msg),
    if Type == ?STUN_ATTR_MESSAGE_INTEGRITY ->
	    NewMsg;
       true ->
	    dec_attrs(Tail, NewMsg)
    end;
dec_attrs(<<>>, Msg) ->
    Msg.

enc_attrs(Msg) ->
    list_to_binary(
      [enc_attr(?STUN_ATTR_SOFTWARE, Msg#stun.'SOFTWARE'),
       enc_addr(?STUN_ATTR_MAPPED_ADDRESS, Msg#stun.'MAPPED-ADDRESS'),
       enc_xor_addr(?STUN_ATTR_XOR_MAPPED_ADDRESS,
		    Msg#stun.magic, Msg#stun.trid,
		    Msg#stun.'XOR-MAPPED-ADDRESS'),
       enc_addr(?STUN_ATTR_ALTERNATE_SERVER, Msg#stun.'ALTERNATE-SERVER'),
       enc_attr(?STUN_ATTR_USERNAME, Msg#stun.'USERNAME'),
       enc_attr(?STUN_ATTR_REALM, Msg#stun.'REALM'),
       enc_attr(?STUN_ATTR_NONCE, Msg#stun.'NONCE'),
       enc_error_code(Msg#stun.'ERROR-CODE'),
       enc_unknown_attrs(Msg#stun.'UNKNOWN-ATTRIBUTES')]).

dec_attr(?STUN_ATTR_MAPPED_ADDRESS, Val, Msg) ->
    <<_, Family, Port:16, AddrBin/binary>> = Val,
    Addr = dec_addr(Family, AddrBin),
    Msg#stun{'MAPPED-ADDRESS' = {Addr, Port}};
dec_attr(?STUN_ATTR_XOR_MAPPED_ADDRESS, Val, Msg) ->
    <<_, Family, XPort:16, XAddr/binary>> = Val,
    Magic = Msg#stun.magic,
    Port = XPort bxor (Magic bsr 16),
    Addr = dec_xor_addr(Family, Magic, Msg#stun.trid, XAddr),
    Msg#stun{'XOR-MAPPED-ADDRESS' = {Addr, Port}};
dec_attr(?STUN_ATTR_SOFTWARE, Val, Msg) ->
    Msg#stun{'SOFTWARE' = Val};
dec_attr(?STUN_ATTR_USERNAME, Val, Msg) ->
    Msg#stun{'USERNAME' = Val};
dec_attr(?STUN_ATTR_REALM, Val, Msg) ->
    Msg#stun{'REALM' = Val};
dec_attr(?STUN_ATTR_NONCE, Val, Msg) ->
    Msg#stun{'NONCE' = Val};
dec_attr(?STUN_ATTR_MESSAGE_INTEGRITY, Val, Msg) ->
    Msg#stun{'MESSAGE-INTEGRITY' = Val};
dec_attr(?STUN_ATTR_ALTERNATE_SERVER, Val, Msg) ->
    <<_, Family, Port:16, Address/binary>> = Val,
    IP = dec_addr(Family, Address),
    Msg#stun{'ALTERNATE-SERVER' = {IP, Port}};
dec_attr(?STUN_ATTR_ERROR_CODE, Val, Msg) ->
    <<_:21, Class:3, Number:8, Reason/binary>> = Val,
    if Class >=3, Class =< 6, Number >=0, Number =< 99 ->
	    Code = Class * 100 + Number,
	    Msg#stun{'ERROR-CODE' = {Code, Reason}}
    end;
dec_attr(?STUN_ATTR_UNKNOWN_ATTRIBUTES, Val, Msg) ->
    Attrs = dec_unknown_attrs(Val, []),
    Msg#stun{'UNKNOWN-ATTRIBUTES' = Attrs};
dec_attr(Attr, _Val, #stun{unsupported = Attrs} = Msg)
  when Attr =< 16#7fff ->
    Msg#stun{unsupported = [Attr|Attrs]};
dec_attr(_Attr, _Val, Msg) ->
    Msg.

dec_addr(1, <<A1, A2, A3, A4>>) ->
    {A1, A2, A3, A4};
dec_addr(2, <<A1:16, A2:16, A3:16, A4:16,
	     A5:16, A6:16, A7:16, A8:16>>) ->
    {A1, A2, A3, A4, A5, A6, A7, A8}.

dec_xor_addr(1, Magic, _TrID, <<XAddr:32>>) ->
    Addr = XAddr bxor Magic,
    dec_addr(1, <<Addr:32>>);
dec_xor_addr(2, Magic, TrID, <<XAddr:128>>) ->
    Addr = XAddr bxor ((Magic bsl 96) bor TrID),
    dec_addr(2, <<Addr:128>>).

dec_unknown_attrs(<<Attr:16, Tail/binary>>, Acc) ->
    dec_unknown_attrs(Tail, [Attr|Acc]);
dec_unknown_attrs(<<>>, Acc) ->
    lists:reverse(Acc).

enc_attr(_Attr, undefined) ->
    <<>>;
enc_attr(Attr, Val) ->
    Len = size(Val),
    PaddLen = padd_len(Len),
    <<Attr:16, Len:16, Val/binary, 0:PaddLen>>.

enc_addr(_Type, undefined) ->
    <<>>;
enc_addr(Type, {{A1, A2, A3, A4}, Port}) ->
    enc_attr(Type, <<0, 1, Port:16, A1, A2, A3, A4>>);
enc_addr(Type, {{A1, A2, A3, A4, A5, A6, A7, A8}, Port}) ->
    enc_attr(Type, <<0, 2, Port:16, A1:16, A2:16, A3:16,
		    A4:16, A5:16, A6:16, A7:16, A8:16>>).

enc_xor_addr(_Type, _Magic, _TrID, undefined) ->
    <<>>;
enc_xor_addr(Type, Magic, _TrID, {{A1, A2, A3, A4}, Port}) ->
    XPort = Port bxor (Magic bsr 16),
    <<Addr:32>> = <<A1, A2, A3, A4>>,
    XAddr = Addr bxor Magic,
    enc_attr(Type, <<0, 1, XPort:16, XAddr:32>>);
enc_xor_addr(Type, Magic, TrID,
	     {{A1, A2, A3, A4, A5, A6, A7, A8}, Port}) ->
    XPort = Port bxor (Magic bsr 16),
    <<Addr:128>> = <<A1:16, A2:16, A3:16, A4:16,
		    A5:16, A6:16, A7:16, A8:16>>,
    XAddr = Addr bxor ((Magic bsl 96) bor TrID),
    enc_attr(Type, <<0, 2, XPort:16, XAddr:128>>).

enc_error_code(undefined) ->
    <<>>;
enc_error_code({Code, Reason}) ->
    Class = Code div 100,
    Number = Code rem 100,
    enc_attr(?STUN_ATTR_ERROR_CODE,
	     <<0:21, Class:3, Number:8, Reason/binary>>).

enc_unknown_attrs([]) ->
    <<>>;
enc_unknown_attrs(Attrs) ->
    enc_attr(?STUN_ATTR_UNKNOWN_ATTRIBUTES,
	     list_to_binary([<<Attr:16>> || Attr <- Attrs])).

%%====================================================================
%% Auxiliary functions
%%====================================================================
pp(Tag, N) ->
    try
	pp1(Tag, N)
    catch _:_ ->
	    no
    end.

pp1(stun, N) ->
    N = record_info(size, stun) - 1,
    record_info(fields, stun);
pp1(_, _) ->
    no.

  Workaround for stupid clients .
-ifdef(NO_PADDING).
padd_len(_Len) ->
    0.
-else.
padd_len(Len) ->
    case Len rem 4 of
	0 -> 0;
	N -> 8*(4-N)
    end.
-endif.

%%====================================================================
%% Test functions
%%====================================================================
bind_msg() ->
    Msg = #stun{method = ?STUN_METHOD_BINDING,
		class = request,
		trid = random:uniform(1 bsl 96),
		'SOFTWARE' = <<"test">>},
    encode(Msg).

test_udp(Addr, Port) ->
    test(Addr, Port, gen_udp).

test_tcp(Addr, Port) ->
    test(Addr, Port, gen_tcp).

test_tls(Addr, Port) ->
    test(Addr, Port, ssl).

test(Addr, Port, Mod) ->
    Res = case Mod of
	      gen_udp ->
		  Mod:open(0, [binary, {active, false}]);
	      _ ->
		  Mod:connect(Addr, Port,
			      [binary, {active, false}], 1000)
	  end,
    case Res of
	{ok, Sock} ->
	    if Mod == gen_udp ->
		    Mod:send(Sock, Addr, Port, bind_msg());
	       true ->
		    Mod:send(Sock, bind_msg())
	    end,
	    case Mod:recv(Sock, 0, 1000) of
		{ok, {_, _, Data}} ->
		    try_dec(Data);
		{ok, Data} ->
		    try_dec(Data);
		Err ->
		    io:format("err: ~p~n", [Err])
	    end,
	    Mod:close(Sock);
	Err ->
	    io:format("err: ~p~n", [Err])
    end.

try_dec(Data) ->
    case decode(Data) of
	{ok, Msg, _} ->
	    io:format("got:~n~s~n", [pp(Msg)]);
	Err ->
	    io:format("err: ~p~n", [Err])
    end.

public_servers() ->
    [{"stun.ekiga.net", 3478, 3478, 5349},
     {"stun.fwdnet.net", 3478, 3478, 5349},
     {"stun.ideasip.com", 3478, 3478, 5349},
     {"stun01.sipphone.com", 3478, 3478, 5349},
     {"stun.softjoys.com", 3478, 3478, 5349},
     {"stun.voipbuster.com", 3478, 3478, 5349},
     {"stun.voxgratia.org", 3478, 3478, 5349},
     {"stun.xten.com", 3478, 3478, 5349},
     {"stunserver.org", 3478, 3478, 5349},
     {"stun.sipgate.net", 10000, 10000, 5349},
     {"numb.viagenie.ca", 3478, 3478, 5349},
     {"stun.ipshka.com", 3478, 3478, 5349},
     {"localhost", 3478, 5349, 5349}].

test_public() ->
    ssl:start(),
    lists:foreach(
      fun({Addr, UDPPort, TCPPort, TLSPort}) ->
	      io:format("trying ~s:~p on UDP... ", [Addr, UDPPort]),
	      test_udp(Addr, UDPPort),
	      io:format("trying ~s:~p on TCP... ", [Addr, TCPPort]),
	      test_tcp(Addr, TCPPort),
	      io:format("trying ~s:~p on TLS... ", [Addr, TLSPort]),
	      test_tls(Addr, TLSPort)
      end, public_servers()).

-------------------------------------------------------------------
 Description : STUN codec



 This program is free software; you can redistribute it and/or
 License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 General Public License for more details.

 along with this program; if not, write to the Free Software

-------------------------------------------------------------------
 API
 Tests
====================================================================
 API
====================================================================
====================================================================
====================================================================
====================================================================
 Auxiliary functions
====================================================================
====================================================================
 Test functions
====================================================================
  File     : stun_codec.erl
  Author   :   <  >
  Created :   7 Aug 2009 by   <  >
  ejabberd , Copyright ( C ) 2002 - 2010    ProcessOne
  modify it under the terms of the GNU General Public License as
  published by the Free Software Foundation ; either version 2 of the
  You should have received a copy of the GNU General Public License
  Foundation , Inc. , 59 Temple Place , Suite 330 , Boston , MA
  02111 - 1307 USA
-module(stun_codec).

-export([decode/1,
	 encode/1,
	 version/1,
	 reason/1,
	 pp/1]).

-export([test_udp/2,
	 test_tcp/2,
	 test_tls/2,
	 test_public/0]).

-include("stun.hrl").

decode(<<0:2, Type:14, Len:16, Magic:32, TrID:96,
	Body:Len/binary, Tail/binary>>) ->
    case catch decode(Type, Magic, TrID, Body) of
	{'EXIT', _} ->
	    {error, unparsed};
	Res ->
	    {ok, Res, Tail}
    end;
decode(<<0:2, _/binary>>) ->
    more;
decode(<<>>) ->
    empty;
decode(_) ->
    {error, unparsed}.

encode(#stun{class = Class,
	     method = Method,
	     magic = Magic,
	     trid = TrID} = Msg) ->
    ClassCode = case Class of
		    request -> 0;
		    indication -> 1;
		    response -> 2;
		    error -> 3
		end,
    Type = ?STUN_TYPE(ClassCode, Method),
    Attrs = enc_attrs(Msg),
    Len = size(Attrs),
    <<0:2, Type:14, Len:16, Magic:32, TrID:96, Attrs/binary>>.

pp(Term) ->
    io_lib_pretty:print(Term, fun pp/2).

version(#stun{magic = ?STUN_MAGIC}) ->
    new;
version(#stun{}) ->
    old.

reason(300) -> <<"Try Alternate">>;
reason(400) -> <<"Bad Request">>;
reason(401) -> <<"Unauthorized">>;
reason(420) -> <<"Unknown Attribute">>;
reason(438) -> <<"Stale Nonce">>;
reason(500) -> <<"Server Error">>;
reason(_) -> <<"Undefined Error">>.

  Internal functions
decode(Type, Magic, TrID, Body) ->
    Method = ?STUN_METHOD(Type),
    Class = case ?STUN_CLASS(Type) of
		0 -> request;
		1 -> indication;
		2 -> response;
		3 -> error
	    end,
    dec_attrs(Body, #stun{class = Class,
			  method = Method,
			  magic = Magic,
			  trid = TrID}).

dec_attrs(<<Type:16, Len:16, Rest/binary>>, Msg) ->
    PaddLen = padd_len(Len),
    <<Val:Len/binary, _:PaddLen, Tail/binary>> = Rest,
    NewMsg = dec_attr(Type, Val, Msg),
    if Type == ?STUN_ATTR_MESSAGE_INTEGRITY ->
	    NewMsg;
       true ->
	    dec_attrs(Tail, NewMsg)
    end;
dec_attrs(<<>>, Msg) ->
    Msg.

enc_attrs(Msg) ->
    list_to_binary(
      [enc_attr(?STUN_ATTR_SOFTWARE, Msg#stun.'SOFTWARE'),
       enc_addr(?STUN_ATTR_MAPPED_ADDRESS, Msg#stun.'MAPPED-ADDRESS'),
       enc_xor_addr(?STUN_ATTR_XOR_MAPPED_ADDRESS,
		    Msg#stun.magic, Msg#stun.trid,
		    Msg#stun.'XOR-MAPPED-ADDRESS'),
       enc_addr(?STUN_ATTR_ALTERNATE_SERVER, Msg#stun.'ALTERNATE-SERVER'),
       enc_attr(?STUN_ATTR_USERNAME, Msg#stun.'USERNAME'),
       enc_attr(?STUN_ATTR_REALM, Msg#stun.'REALM'),
       enc_attr(?STUN_ATTR_NONCE, Msg#stun.'NONCE'),
       enc_error_code(Msg#stun.'ERROR-CODE'),
       enc_unknown_attrs(Msg#stun.'UNKNOWN-ATTRIBUTES')]).

dec_attr(?STUN_ATTR_MAPPED_ADDRESS, Val, Msg) ->
    <<_, Family, Port:16, AddrBin/binary>> = Val,
    Addr = dec_addr(Family, AddrBin),
    Msg#stun{'MAPPED-ADDRESS' = {Addr, Port}};
dec_attr(?STUN_ATTR_XOR_MAPPED_ADDRESS, Val, Msg) ->
    <<_, Family, XPort:16, XAddr/binary>> = Val,
    Magic = Msg#stun.magic,
    Port = XPort bxor (Magic bsr 16),
    Addr = dec_xor_addr(Family, Magic, Msg#stun.trid, XAddr),
    Msg#stun{'XOR-MAPPED-ADDRESS' = {Addr, Port}};
dec_attr(?STUN_ATTR_SOFTWARE, Val, Msg) ->
    Msg#stun{'SOFTWARE' = Val};
dec_attr(?STUN_ATTR_USERNAME, Val, Msg) ->
    Msg#stun{'USERNAME' = Val};
dec_attr(?STUN_ATTR_REALM, Val, Msg) ->
    Msg#stun{'REALM' = Val};
dec_attr(?STUN_ATTR_NONCE, Val, Msg) ->
    Msg#stun{'NONCE' = Val};
dec_attr(?STUN_ATTR_MESSAGE_INTEGRITY, Val, Msg) ->
    Msg#stun{'MESSAGE-INTEGRITY' = Val};
dec_attr(?STUN_ATTR_ALTERNATE_SERVER, Val, Msg) ->
    <<_, Family, Port:16, Address/binary>> = Val,
    IP = dec_addr(Family, Address),
    Msg#stun{'ALTERNATE-SERVER' = {IP, Port}};
dec_attr(?STUN_ATTR_ERROR_CODE, Val, Msg) ->
    <<_:21, Class:3, Number:8, Reason/binary>> = Val,
    if Class >=3, Class =< 6, Number >=0, Number =< 99 ->
	    Code = Class * 100 + Number,
	    Msg#stun{'ERROR-CODE' = {Code, Reason}}
    end;
dec_attr(?STUN_ATTR_UNKNOWN_ATTRIBUTES, Val, Msg) ->
    Attrs = dec_unknown_attrs(Val, []),
    Msg#stun{'UNKNOWN-ATTRIBUTES' = Attrs};
dec_attr(Attr, _Val, #stun{unsupported = Attrs} = Msg)
  when Attr =< 16#7fff ->
    Msg#stun{unsupported = [Attr|Attrs]};
dec_attr(_Attr, _Val, Msg) ->
    Msg.

dec_addr(1, <<A1, A2, A3, A4>>) ->
    {A1, A2, A3, A4};
dec_addr(2, <<A1:16, A2:16, A3:16, A4:16,
	     A5:16, A6:16, A7:16, A8:16>>) ->
    {A1, A2, A3, A4, A5, A6, A7, A8}.

dec_xor_addr(1, Magic, _TrID, <<XAddr:32>>) ->
    Addr = XAddr bxor Magic,
    dec_addr(1, <<Addr:32>>);
dec_xor_addr(2, Magic, TrID, <<XAddr:128>>) ->
    Addr = XAddr bxor ((Magic bsl 96) bor TrID),
    dec_addr(2, <<Addr:128>>).

dec_unknown_attrs(<<Attr:16, Tail/binary>>, Acc) ->
    dec_unknown_attrs(Tail, [Attr|Acc]);
dec_unknown_attrs(<<>>, Acc) ->
    lists:reverse(Acc).

enc_attr(_Attr, undefined) ->
    <<>>;
enc_attr(Attr, Val) ->
    Len = size(Val),
    PaddLen = padd_len(Len),
    <<Attr:16, Len:16, Val/binary, 0:PaddLen>>.

enc_addr(_Type, undefined) ->
    <<>>;
enc_addr(Type, {{A1, A2, A3, A4}, Port}) ->
    enc_attr(Type, <<0, 1, Port:16, A1, A2, A3, A4>>);
enc_addr(Type, {{A1, A2, A3, A4, A5, A6, A7, A8}, Port}) ->
    enc_attr(Type, <<0, 2, Port:16, A1:16, A2:16, A3:16,
		    A4:16, A5:16, A6:16, A7:16, A8:16>>).

enc_xor_addr(_Type, _Magic, _TrID, undefined) ->
    <<>>;
enc_xor_addr(Type, Magic, _TrID, {{A1, A2, A3, A4}, Port}) ->
    XPort = Port bxor (Magic bsr 16),
    <<Addr:32>> = <<A1, A2, A3, A4>>,
    XAddr = Addr bxor Magic,
    enc_attr(Type, <<0, 1, XPort:16, XAddr:32>>);
enc_xor_addr(Type, Magic, TrID,
	     {{A1, A2, A3, A4, A5, A6, A7, A8}, Port}) ->
    XPort = Port bxor (Magic bsr 16),
    <<Addr:128>> = <<A1:16, A2:16, A3:16, A4:16,
		    A5:16, A6:16, A7:16, A8:16>>,
    XAddr = Addr bxor ((Magic bsl 96) bor TrID),
    enc_attr(Type, <<0, 2, XPort:16, XAddr:128>>).

enc_error_code(undefined) ->
    <<>>;
enc_error_code({Code, Reason}) ->
    Class = Code div 100,
    Number = Code rem 100,
    enc_attr(?STUN_ATTR_ERROR_CODE,
	     <<0:21, Class:3, Number:8, Reason/binary>>).

enc_unknown_attrs([]) ->
    <<>>;
enc_unknown_attrs(Attrs) ->
    enc_attr(?STUN_ATTR_UNKNOWN_ATTRIBUTES,
	     list_to_binary([<<Attr:16>> || Attr <- Attrs])).

pp(Tag, N) ->
    try
	pp1(Tag, N)
    catch _:_ ->
	    no
    end.

pp1(stun, N) ->
    N = record_info(size, stun) - 1,
    record_info(fields, stun);
pp1(_, _) ->
    no.

  Workaround for stupid clients .
-ifdef(NO_PADDING).
padd_len(_Len) ->
    0.
-else.
padd_len(Len) ->
    case Len rem 4 of
	0 -> 0;
	N -> 8*(4-N)
    end.
-endif.

bind_msg() ->
    Msg = #stun{method = ?STUN_METHOD_BINDING,
		class = request,
		trid = random:uniform(1 bsl 96),
		'SOFTWARE' = <<"test">>},
    encode(Msg).

test_udp(Addr, Port) ->
    test(Addr, Port, gen_udp).

test_tcp(Addr, Port) ->
    test(Addr, Port, gen_tcp).

test_tls(Addr, Port) ->
    test(Addr, Port, ssl).

test(Addr, Port, Mod) ->
    Res = case Mod of
	      gen_udp ->
		  Mod:open(0, [binary, {active, false}]);
	      _ ->
		  Mod:connect(Addr, Port,
			      [binary, {active, false}], 1000)
	  end,
    case Res of
	{ok, Sock} ->
	    if Mod == gen_udp ->
		    Mod:send(Sock, Addr, Port, bind_msg());
	       true ->
		    Mod:send(Sock, bind_msg())
	    end,
	    case Mod:recv(Sock, 0, 1000) of
		{ok, {_, _, Data}} ->
		    try_dec(Data);
		{ok, Data} ->
		    try_dec(Data);
		Err ->
		    io:format("err: ~p~n", [Err])
	    end,
	    Mod:close(Sock);
	Err ->
	    io:format("err: ~p~n", [Err])
    end.

try_dec(Data) ->
    case decode(Data) of
	{ok, Msg, _} ->
	    io:format("got:~n~s~n", [pp(Msg)]);
	Err ->
	    io:format("err: ~p~n", [Err])
    end.

public_servers() ->
    [{"stun.ekiga.net", 3478, 3478, 5349},
     {"stun.fwdnet.net", 3478, 3478, 5349},
     {"stun.ideasip.com", 3478, 3478, 5349},
     {"stun01.sipphone.com", 3478, 3478, 5349},
     {"stun.softjoys.com", 3478, 3478, 5349},
     {"stun.voipbuster.com", 3478, 3478, 5349},
     {"stun.voxgratia.org", 3478, 3478, 5349},
     {"stun.xten.com", 3478, 3478, 5349},
     {"stunserver.org", 3478, 3478, 5349},
     {"stun.sipgate.net", 10000, 10000, 5349},
     {"numb.viagenie.ca", 3478, 3478, 5349},
     {"stun.ipshka.com", 3478, 3478, 5349},
     {"localhost", 3478, 5349, 5349}].

test_public() ->
    ssl:start(),
    lists:foreach(
      fun({Addr, UDPPort, TCPPort, TLSPort}) ->
	      io:format("trying ~s:~p on UDP... ", [Addr, UDPPort]),
	      test_udp(Addr, UDPPort),
	      io:format("trying ~s:~p on TCP... ", [Addr, TCPPort]),
	      test_tcp(Addr, TCPPort),
	      io:format("trying ~s:~p on TLS... ", [Addr, TLSPort]),
	      test_tls(Addr, TLSPort)
      end, public_servers()).
