Initial working commit.
This commit is contained in:
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
.rebar3
|
||||
_*
|
||||
.eunit
|
||||
*.o
|
||||
*.beam
|
||||
*.plt
|
||||
*.swp
|
||||
*.swo
|
||||
.erlang.cookie
|
||||
ebin
|
||||
log
|
||||
erl_crash.dump
|
||||
.rebar
|
||||
logs
|
||||
_build
|
||||
.idea
|
||||
rebar3.crashdump
|
||||
*~
|
||||
35
LICENSE
Normal file
35
LICENSE
Normal file
@@ -0,0 +1,35 @@
|
||||
Copyright (c) 2017, Mathieu Kerjouan <contact [at] steepath.eu>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
3. All advertising materials mentioning features or use of this
|
||||
software must display the following acknowledgement: This
|
||||
product includes software developed by the <organization>.
|
||||
|
||||
4. Neither the name of the <organization> nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
46
README.md
Normal file
46
README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
#bencode
|
||||
|
||||
Bencode Erlang Library Implementation
|
||||
|
||||
## Build
|
||||
|
||||
$ rebar3 compile
|
||||
|
||||
## Usage
|
||||
|
||||
You can encode Erlang terms to Bencoded string:
|
||||
|
||||
> {ok, E} = bencode:encode([1, 2, 3, mystring]).
|
||||
{ok,<<"li1ei2ei3e8:mystringe">>}
|
||||
> E.
|
||||
<<"li1ei2ei3e8:mystringe">>
|
||||
|
||||
You can also decode Bencoded string to Erlang terms:
|
||||
|
||||
> {ok, D} = bencode_decode(B).
|
||||
{ok,[1,2,3,<<"mystring">>]}
|
||||
> D.
|
||||
[1,2,3,<<"mystring">>]
|
||||
|
||||
If you want to parse torrent file:
|
||||
|
||||
> {ok, Torrent} = file:read_file("/path/to/my.torrent").
|
||||
> {ok, Decoded} = bencode:decode(Torrent).
|
||||
> Decoded.
|
||||
|
||||
## Todo
|
||||
|
||||
- Decoding options
|
||||
- Encoding options
|
||||
- Benchmarking
|
||||
- More documentation
|
||||
- More Test Unit
|
||||
- More Common Test
|
||||
- Find better way to order keys in dictionary
|
||||
|
||||
## References
|
||||
|
||||
- https://wiki.theory.org/BitTorrentSpecification#Bencoding
|
||||
- http://fileformats.wikia.com/wiki/Torrent_file
|
||||
- https://github.com/jlouis/benc
|
||||
- https://en.wikipedia.org/wiki/Bencode
|
||||
2
rebar.config
Normal file
2
rebar.config
Normal file
@@ -0,0 +1,2 @@
|
||||
{erl_opts, [debug_info]}.
|
||||
{deps, []}.
|
||||
15
src/bencode.app.src
Normal file
15
src/bencode.app.src
Normal file
@@ -0,0 +1,15 @@
|
||||
{application, bencode,
|
||||
[{description, "Bencode Erlang Library"},
|
||||
{vsn, "0.1.0"},
|
||||
{registered, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
stdlib
|
||||
]},
|
||||
{env,[]},
|
||||
{modules, []},
|
||||
|
||||
{maintainers, ["Mathieu Kerjouan <contact [at] steepath.eu>"]},
|
||||
{licenses, ["BSD-4"]},
|
||||
{links, ["https://github.com/niamtokik/bencode"]}
|
||||
]}.
|
||||
155
src/bencode.erl
Normal file
155
src/bencode.erl
Normal file
@@ -0,0 +1,155 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% Copyright (c) 2017, Mathieu Kerjouan <contact [at] steepath.eu>
|
||||
%%% All rights reserved.
|
||||
%%%
|
||||
%%% Redistribution and use in source and binary forms, with or without
|
||||
%%% modification, are permitted provided that the following conditions
|
||||
%%% are met:
|
||||
%%%
|
||||
%%% 1. Redistributions of source code must retain the above copyright
|
||||
%%% notice, this list of conditions and the following disclaimer.
|
||||
%%%
|
||||
%%% 2. Redistributions in binary form must reproduce the above
|
||||
%%% copyright notice, this list of conditions and the following
|
||||
%%% disclaimer in the documentation and/or other materials provided
|
||||
%%% with the distribution.
|
||||
%%%
|
||||
%%% 3. All advertising materials mentioning features or use of this
|
||||
%%% software must display the following acknowledgement: This
|
||||
%%% product includes software developed by the <organization>.
|
||||
%%%
|
||||
%%% 4. Neither the name of the <organization> nor the names of its
|
||||
%%% contributors may be used to endorse or promote products derived
|
||||
%%% from this software without specific prior written permission.
|
||||
%%%
|
||||
%%% THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY
|
||||
%%% EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
%%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
%%% PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE
|
||||
%%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
%%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
%%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
%%% PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
%%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
%%% TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
%%% THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
%%% SUCH DAMAGE.
|
||||
%%%
|
||||
%%% ------------------------------------------------------------------
|
||||
%%%
|
||||
%%% Bencoding FSM schema:
|
||||
%%%
|
||||
%%% _____________________ ________________________
|
||||
%%% | | +------>| |
|
||||
%%% | integer: | | | string: |
|
||||
%%% | <<$i,Char,$e>> | | | <<Char,$:,Bytes>> |
|
||||
%%% | where integer(Char) | | | where integer(Char) |
|
||||
%%% |_____________________|<------+ | bitstring(Bytes) |
|
||||
%%% /_\ | | |________________________|
|
||||
%%% | | | /_\
|
||||
%%% | | | |
|
||||
%%% | | | |
|
||||
%%% ___|_________________ | | _______________|________
|
||||
%%% | |--+ +--| |
|
||||
%%% | list: | | dictionary: |
|
||||
%%% | <<$l,Content,$e>> |--------->| <<$d,Content,$e>> |
|
||||
%%% |_____________________|<---------|________________________|
|
||||
%%%
|
||||
%%% ------------------------------------------------------------------
|
||||
%%%
|
||||
%%% @author Mathieu Kerjouan
|
||||
%%% @copyright (c) 2017, Mathieu Kerjouan <contact [at] steepath.eu>
|
||||
%%% @doc
|
||||
%%% @end
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(bencode).
|
||||
-export([encode/1, encode/2, encode_options/0]).
|
||||
-export([decode/1, decode/2, decode_options/0]).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
-spec decode_options() -> list().
|
||||
decode_options() ->
|
||||
[integer_as_string
|
||||
,integer_as_bitstring
|
||||
,string_as_list
|
||||
,dictionary_as_proplists
|
||||
,dictionary_as_map].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%% @param bencoded string as bitstring or binary term
|
||||
%% @returns erlang term based on bencode schema.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec decode(bitstring() | list())
|
||||
-> list() | map() | integer() | bitstring().
|
||||
decode(Bitstring) ->
|
||||
decode(Bitstring, []).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%% @param bencoded string as bitstring or binary term
|
||||
%% @param options list
|
||||
%% @returns erlang term based on bencode schema.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec decode(bitstring() | list(), list())
|
||||
-> list() | map() | integer() | bitstring().
|
||||
decode(List, Opts)
|
||||
when is_list(List) ->
|
||||
decode(erlang:list_to_bitstring(List), Opts);
|
||||
decode(Bitstring, _) ->
|
||||
bencode_decode:switch(Bitstring).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%%
|
||||
%%--------------------------------------------------------------------
|
||||
-spec encode_options() -> list().
|
||||
encode_options() ->
|
||||
[integer_as_string
|
||||
,integer_as_bitstring
|
||||
,string_as_list
|
||||
,dictionary_as_proplists
|
||||
,dictionary_as_map].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
encode_test() ->
|
||||
?assertEqual(encode([1,2,3,[1,2,3]]),
|
||||
{ok, <<"li1ei2ei3eli1ei2ei3eee">>}),
|
||||
?assertEqual(encode([#{a => 1}, 1, 23]),
|
||||
{ok, <<"ld1:ai1eei1ei23ee">>}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec encode(integer() | bitstring() | list() | map())
|
||||
-> {ok, bitstring()}.
|
||||
encode(Data)
|
||||
when is_integer(Data) ->
|
||||
bencode_encode:integer(Data);
|
||||
encode(Data)
|
||||
when is_bitstring(Data) ->
|
||||
bencode_encode:string(Data);
|
||||
encode(Data)
|
||||
when is_list(Data) ->
|
||||
bencode_encode:list(Data);
|
||||
encode(Data)
|
||||
when is_map(Data) ->
|
||||
bencode_encode:dictionary(Data).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
encode(_,_) ->
|
||||
ok.
|
||||
370
src/bencode_decode.erl
Normal file
370
src/bencode_decode.erl
Normal file
@@ -0,0 +1,370 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% Copyright (c) 2017, Mathieu Kerjouan <contact [at] steepath.eu>
|
||||
%%% All rights reserved.
|
||||
%%%
|
||||
%%% Redistribution and use in source and binary forms, with or without
|
||||
%%% modification, are permitted provided that the following conditions
|
||||
%%% are met:
|
||||
%%%
|
||||
%%% 1. Redistributions of source code must retain the above copyright
|
||||
%%% notice, this list of conditions and the following disclaimer.
|
||||
%%%
|
||||
%%% 2. Redistributions in binary form must reproduce the above
|
||||
%%% copyright notice, this list of conditions and the following
|
||||
%%% disclaimer in the documentation and/or other materials provided
|
||||
%%% with the distribution.
|
||||
%%%
|
||||
%%% 3. All advertising materials mentioning features or use of this
|
||||
%%% software must display the following acknowledgement: This
|
||||
%%% product includes software developed by the <organization>.
|
||||
%%%
|
||||
%%% 4. Neither the name of the <organization> nor the names of its
|
||||
%%% contributors may be used to endorse or promote products derived
|
||||
%%% from this software without specific prior written permission.
|
||||
%%%
|
||||
%%% THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY
|
||||
%%% EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
%%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
%%% PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE
|
||||
%%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
%%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
%%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
%%% PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
%%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
%%% TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
%%% THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
%%% SUCH DAMAGE.
|
||||
%%%
|
||||
%%% ------------------------------------------------------------------
|
||||
%%%
|
||||
%%% @author Mathieu Kerjouan
|
||||
%%% @copyright (c) 2017, Mathieu Kerjouan <contact [at] steepath.eu>
|
||||
%%% @doc
|
||||
%%% @end
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(bencode_decode).
|
||||
-export([integer/1]).
|
||||
-export([string/1]).
|
||||
-export([list/1]).
|
||||
-export([dictionary/1]).
|
||||
-export([switch/1]).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc switch/1 helper function will route bitstring parameter
|
||||
%% based on pattern matching.
|
||||
%% @end
|
||||
%% @param bencoded string as bitstring.
|
||||
%% @return erlang term
|
||||
%%--------------------------------------------------------------------
|
||||
switch(Bitstring) ->
|
||||
case Bitstring of
|
||||
<<"i", _/bitstring>> ->
|
||||
switch_integer(Bitstring);
|
||||
<<"l", _/bitstring>> ->
|
||||
switch_list(Bitstring);
|
||||
<<"d", _/bitstring>> ->
|
||||
switch_dictionary(Bitstring);
|
||||
<<Char, _/bitstring>>
|
||||
when Char >= $0 andalso Char =< $9 ->
|
||||
switch_string(Bitstring);
|
||||
_Else ->
|
||||
{error, not_bencoded}
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%% @param
|
||||
%%--------------------------------------------------------------------
|
||||
switch_integer(Bitstring) ->
|
||||
integer(Bitstring).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%% @param
|
||||
%%--------------------------------------------------------------------
|
||||
switch_list(Bitstring) ->
|
||||
list(Bitstring).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
switch_dictionary(Bitstring) ->
|
||||
dictionary(Bitstring).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
switch_string(Bitstring) ->
|
||||
string(Bitstring).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
integer_test() ->
|
||||
?assertEqual(integer(<<"test">>), {not_integer, <<"test">>}),
|
||||
?assertEqual(integer(<<"ie">>), {error, invalid}),
|
||||
?assertEqual(integer(<<"i0e">>), {ok, 0}),
|
||||
?assertEqual(integer(<<"i0etest">>), {ok, 0, <<"test">>}),
|
||||
?assertEqual(integer(<<"i-0e">>), {error, invalid}),
|
||||
?assertEqual(integer(<<"i-00001e">>), {error, invalid}),
|
||||
?assertEqual(integer(<<"i000001e">>), {error, invalid}),
|
||||
?assertEqual(integer(<<"i1e">>), {ok, 1}),
|
||||
?assertEqual(integer(<<"i3e">>), {ok, 3}),
|
||||
?assertEqual(integer(<<"i-3e">>), {ok, -3}),
|
||||
?assertEqual(integer(<<"i03e">>), {error, invalid}),
|
||||
?assertEqual(integer(<<"i1etest">>), {ok, 1, <<"test">>}),
|
||||
% 64bit value
|
||||
?assertEqual(integer(<<"i18446744073709551616e">>),
|
||||
{ok, 18446744073709551616}),
|
||||
% 64bit negative value
|
||||
?assertEqual(integer(<<"i-18446744073709551616e">>),
|
||||
{ok, (-18446744073709551616)}),
|
||||
% 128bit value
|
||||
?assertEqual(integer(<<"i340282366920938463463374607431768211456e">>),
|
||||
{ok, 340282366920938463463374607431768211456}),
|
||||
% 128bit negative value
|
||||
?assertEqual(integer(<<"i-340282366920938463463374607431768211456e">>),
|
||||
{ok, (-340282366920938463463374607431768211456)}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%% @param bencoded string as bitstring or binary.
|
||||
%% @returns tuple with atom() tag and erlang integer term.
|
||||
%%--------------------------------------------------------------------
|
||||
-spec integer(bitstring() | list())
|
||||
-> {ok, integer()} |
|
||||
{ok, integer(), bitstring()} |
|
||||
{error, term()}.
|
||||
integer(List) when is_list(List) ->
|
||||
integer(erlang:list_to_bitstring(List));
|
||||
integer(<<"ie">>) ->
|
||||
{error, invalid};
|
||||
integer(<<"i0e">>) ->
|
||||
{ok, 0};
|
||||
integer(<<"i0e", Rest/bitstring>>) ->
|
||||
{ok, 0, Rest};
|
||||
integer(<<$i, Rest/bitstring>>) ->
|
||||
integer(Rest, <<>>);
|
||||
integer(_Else)
|
||||
when is_bitstring(_Else) ->
|
||||
{not_integer, _Else}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%% @param
|
||||
%% @returns
|
||||
%%--------------------------------------------------------------------
|
||||
-spec integer(bitstring(), bitstring())
|
||||
-> {ok, integer()} |
|
||||
{ok, integer(), bitstring()} |
|
||||
{error, term()}.
|
||||
integer(<<$0, _/bitstring>>, <<>>) ->
|
||||
{error, invalid};
|
||||
integer(<<$-, $0, _/bitstring>>, <<>>) ->
|
||||
{error, invalid};
|
||||
integer(<<Number, Rest/bitstring>>, <<>>)
|
||||
when (Number >= $1 andalso Number =< $9) orelse Number =:= $- ->
|
||||
integer(Rest, <<Number>>);
|
||||
integer(<<Number, Rest/bitstring>>, Buf)
|
||||
when Number >= $0 andalso Number =< $9 ->
|
||||
integer(Rest, <<Buf/bitstring, Number>>);
|
||||
integer(<<$e>>, Buf) ->
|
||||
{ok, erlang:binary_to_integer(Buf)};
|
||||
integer(<<$e, Rest/bitstring>>, Buf) ->
|
||||
{ok, erlang:binary_to_integer(Buf), Rest};
|
||||
integer(_, _) ->
|
||||
{error, invalid}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
string_test() ->
|
||||
?assertEqual(string(<<"0:">>), {ok, <<"">>}),
|
||||
?assertEqual(string(<<"1:a">>), {ok, <<"a">>}),
|
||||
?assertEqual(string(<<"2:be">>), {ok, <<"be">>}),
|
||||
?assertEqual(string(<<"4:test">>), {ok, <<"test">>}),
|
||||
?assertEqual(string(<<"5:">>), {error, bad_length}),
|
||||
?assertEqual(string(<<"6:jijoja4:test">>),
|
||||
{ok, <<"jijoja">>, <<"4:test">>}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec string(bitstring() | list())
|
||||
-> {ok, bitstring()} |
|
||||
{ok, bitstring(), bitstring()} |
|
||||
{error, bad_length} |
|
||||
{not_string, bitstring()}.
|
||||
|
||||
string(List)
|
||||
when is_list(List) ->
|
||||
string(erlang:list_to_bitstring(List));
|
||||
string(<<"0:">>) ->
|
||||
{ok, <<"">>};
|
||||
string(<<Number, Rest/bitstring>>)
|
||||
when Number >= $1 andalso Number =< $9 ->
|
||||
string(Rest, <<Number>>);
|
||||
string(_Else) ->
|
||||
{not_string, _Else}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec string(bitstring(), bitstring())
|
||||
-> {ok, bitstring()} |
|
||||
{ok, bitstring(), bitstring()} |
|
||||
{error, bad_length}.
|
||||
|
||||
string(<<$:, Rest/bitstring>>, Length) ->
|
||||
string(Rest, erlang:binary_to_integer(Length), <<>>);
|
||||
string(<<Number, Rest/bitstring>>, Length)
|
||||
when Number >= $0 andalso Number =< $9 ->
|
||||
string(Rest, <<Length/bitstring, Number>>).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec string(bitstring(), non_neg_integer(), bitstring())
|
||||
->{ok, bitstring()} |
|
||||
{ok, bitstring(), bitstring()} |
|
||||
{error, bad_length}.
|
||||
|
||||
string(<<>>, 0, String) ->
|
||||
{ok, String};
|
||||
string(<<>>, _, _) ->
|
||||
{error, bad_length};
|
||||
string(Rest, 0, String) ->
|
||||
{ok, String, Rest};
|
||||
string(<<Char, Rest/bitstring>>, Length, String) ->
|
||||
string(Rest, Length-1, <<String/bitstring, Char>>).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
list_test() ->
|
||||
?assertEqual(list(<<"l4:spam4:eggse">>),
|
||||
{ok, [<<"spam">>, <<"eggs">>]}),
|
||||
?assertEqual(list(<<"le">>),
|
||||
{ok, []}),
|
||||
?assertEqual(list(<<"ldee">>), {ok, [#{}]}),
|
||||
?assertEqual(list(<<"llleee">>), {ok, [[[]]]}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec list(bitstring())
|
||||
-> {ok, list()} |
|
||||
{ok, list(), bitstring()} |
|
||||
{error, no_ending_delimiter} |
|
||||
{not_list, bitstring()}.
|
||||
|
||||
list(<<$l, Rest/bitstring>>) ->
|
||||
list(Rest, []);
|
||||
list(_Else) ->
|
||||
{not_list, _Else}.
|
||||
|
||||
-spec list(bitstring(), list())
|
||||
-> {ok, list()} |
|
||||
{ok, list(), bitstring()} |
|
||||
{error, term()}.
|
||||
list(<<$e>>, List) ->
|
||||
{ok, lists:reverse(List)};
|
||||
list(<<$e, Rest/bitstring>>, List) ->
|
||||
{ok, lists:reverse(List), Rest};
|
||||
list(Bitstring, Buf) ->
|
||||
case switch(Bitstring) of
|
||||
{ok, _} ->
|
||||
{error, no_ending_delimiter};
|
||||
{ok, Data, Rest} ->
|
||||
list(Rest, [Data] ++ Buf)
|
||||
end.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
dictionary_test() ->
|
||||
?assertEqual(dictionary(<<"de">>), {ok, #{}}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec dictionary(bitstring())
|
||||
-> {ok, map()} |
|
||||
{ok, map(), bitstring()} |
|
||||
{error, no_value} |
|
||||
{error, no_ending_delimiter} |
|
||||
{not_dictionary, bitstring()}.
|
||||
|
||||
dictionary(<<"de">>) ->
|
||||
{ok, #{}};
|
||||
dictionary(<<"de", Rest/bitstring>>) ->
|
||||
{ok, #{}, Rest};
|
||||
dictionary(<<$d, Rest/bitstring>>) ->
|
||||
dictionary(Rest, #{}, {});
|
||||
dictionary(_Else) ->
|
||||
{not_dictionary, _Else}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec dictionary(bitstring(), map())
|
||||
-> {ok, map()} |
|
||||
{ok, map(), bitstring()} |
|
||||
{error, term()}.
|
||||
dictionary(<<$e>>, Dict) ->
|
||||
{ok, Dict};
|
||||
dictionary(<<$e, Rest/bitstring>>, Dict) ->
|
||||
{ok, Dict, Rest};
|
||||
dictionary(Bitstring, Dict) ->
|
||||
dictionary(Bitstring, Dict, {}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec dictionary(bitstring(), map(), tuple())
|
||||
-> {ok, map()} |
|
||||
{ok, map(), bitstring()} |
|
||||
{error, term()}.
|
||||
dictionary(<<>>, _, {_}) ->
|
||||
{error, no_value};
|
||||
dictionary(<<>>, _, {_, _}) ->
|
||||
{error, no_ending_delimiter};
|
||||
dictionary(<<$e, _/bitstring>>, _, {_}) ->
|
||||
{error, no_value};
|
||||
dictionary(Bitstring, Dict, {}) ->
|
||||
case switch(Bitstring) of
|
||||
{ok, _} ->
|
||||
{error, no_ending_delimiter};
|
||||
{ok, Key, Rest} ->
|
||||
dictionary(Rest, Dict, {Key})
|
||||
end;
|
||||
dictionary(Bitstring, Dict, {Key}) ->
|
||||
case switch(Bitstring) of
|
||||
{ok, _} ->
|
||||
{error, no_ending_delimiter};
|
||||
{ok, Value, Rest} ->
|
||||
dictionary(Rest, Dict, {Key, Value})
|
||||
end;
|
||||
dictionary(Bitstring, Dict, {Key, Value}) ->
|
||||
dictionary(Bitstring, maps:put(Key, Value, Dict)).
|
||||
289
src/bencode_encode.erl
Normal file
289
src/bencode_encode.erl
Normal file
@@ -0,0 +1,289 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% Copyright (c) 2017, Mathieu Kerjouan <contact [at] steepath.eu>
|
||||
%%% All rights reserved.
|
||||
%%%
|
||||
%%% Redistribution and use in source and binary forms, with or without
|
||||
%%% modification, are permitted provided that the following conditions
|
||||
%%% are met:
|
||||
%%%
|
||||
%%% 1. Redistributions of source code must retain the above copyright
|
||||
%%% notice, this list of conditions and the following disclaimer.
|
||||
%%%
|
||||
%%% 2. Redistributions in binary form must reproduce the above
|
||||
%%% copyright notice, this list of conditions and the following
|
||||
%%% disclaimer in the documentation and/or other materials provided
|
||||
%%% with the distribution.
|
||||
%%%
|
||||
%%% 3. All advertising materials mentioning features or use of this
|
||||
%%% software must display the following acknowledgement: This
|
||||
%%% product includes software developed by the <organization>.
|
||||
%%%
|
||||
%%% 4. Neither the name of the <organization> nor the names of its
|
||||
%%% contributors may be used to endorse or promote products derived
|
||||
%%% from this software without specific prior written permission.
|
||||
%%%
|
||||
%%% THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY
|
||||
%%% EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
%%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
%%% PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE
|
||||
%%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
%%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
%%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
%%% PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
%%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
%%% TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
%%% THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
%%% SUCH DAMAGE.
|
||||
%%%
|
||||
%%% ------------------------------------------------------------------
|
||||
%%%
|
||||
%%% @author Mathieu Kerjouan
|
||||
%%% @copyright (c) 2017, Mathieu Kerjouan <contact [at] steepath.eu>
|
||||
%%% @doc
|
||||
%%% @end
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(bencode_encode).
|
||||
-export([integer/1]).
|
||||
-export([string/1]).
|
||||
-export([list/1]).
|
||||
-export([dictionary/1]).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
integer_test() ->
|
||||
?assertEqual(integer(1), {ok, <<"i1e">>}),
|
||||
?assertEqual(integer(-1), {ok, <<"i-1e">>}),
|
||||
?assertEqual(integer(0), {ok, <<"i0e">>}),
|
||||
?assertEqual(integer(1000), {ok, <<"i1000e">>}),
|
||||
?assertEqual(integer(-1000), {ok, <<"i-1000e">>}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec integer(integer()) -> {ok, bitstring()}.
|
||||
integer(Integer)
|
||||
when is_integer(Integer) ->
|
||||
Bitstring = erlang:integer_to_binary(Integer),
|
||||
{ok, <<$i, Bitstring/bitstring, $e>>}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
string_test() ->
|
||||
?assertEqual(string(<<"test">>), {ok, <<"4:test">>}),
|
||||
?assertEqual(string(<<"">>), {ok, <<"0:">>}),
|
||||
?assertEqual(string(a), {ok, <<"1:a">>}),
|
||||
?assertEqual(string(test), {ok, <<"4:test">>}),
|
||||
?assertEqual(string(<<"dumped">>), {ok, <<"6:dumped">>}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec string(bitstring()) -> {ok, bitstring()}.
|
||||
string(List)
|
||||
when is_list(List) ->
|
||||
string(erlang:list_to_bitstring(List));
|
||||
string(Atom) when is_atom(Atom) ->
|
||||
string(erlang:atom_to_binary(Atom, utf8));
|
||||
string(<<>>) ->
|
||||
{ok, <<"0:">>};
|
||||
string(Bitstring) ->
|
||||
Length = erlang:byte_size(Bitstring),
|
||||
LengthBitstring = erlang:integer_to_binary(Length),
|
||||
{ok, <<LengthBitstring/bitstring,$:,Bitstring/bitstring>>}.
|
||||
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
list_test() ->
|
||||
?assertEqual(list([1,2,3]),
|
||||
{ok, <<"li1ei2ei3ee">>}),
|
||||
?assertEqual(list([<<"a">>,<<"b">>,<<"c">>]),
|
||||
{ok, <<"l1:a1:b1:ce">>}),
|
||||
?assertEqual(list([a,b,c]),
|
||||
{ok, <<"l1:a1:b1:ce">>}),
|
||||
?assertEqual(list([#{}]),
|
||||
{ok, <<"ldee">>}),
|
||||
?assertEqual(list([[]]),
|
||||
{ok, <<"llee">>}),
|
||||
?assertEqual(list([1,2,3,[a,b,c]]),
|
||||
{ok,<<"li1ei2ei3el1:a1:b1:cee">>}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec list(list()) -> {ok, bitstring()}.
|
||||
list([]) ->
|
||||
{ok, <<"le">>};
|
||||
list(List)
|
||||
when is_list(List) ->
|
||||
list(List, <<>>).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec list(list(), bitstring()) -> {ok, bitstring()}.
|
||||
list([], Buf) ->
|
||||
{ok, <<$l, Buf/bitstring, $e>>};
|
||||
list([H|T], Buf)
|
||||
when is_integer(H)->
|
||||
{ok, Integer} = integer(H),
|
||||
list(T, <<Buf/bitstring, Integer/bitstring>>);
|
||||
list([H|T], Buf)
|
||||
when is_atom(H) ->
|
||||
{ok, String} = string(H),
|
||||
list(T, <<Buf/bitstring, String/bitstring>>);
|
||||
list([H|T], Buf)
|
||||
when is_list(H) ->
|
||||
{ok, String} = list(H),
|
||||
list(T, <<Buf/bitstring, String/bitstring>>);
|
||||
list([H|T], Buf)
|
||||
when is_bitstring(H) ->
|
||||
{ok, String} = string(H),
|
||||
list(T, <<Buf/bitstring, String/bitstring>>);
|
||||
list([{Key, Value}|T], Buf)
|
||||
when is_bitstring(Key) ->
|
||||
{wip, proplist};
|
||||
list([H|T], Buf)
|
||||
when is_map(H) ->
|
||||
{ok, Dictionary} = dictionary(H),
|
||||
list(T, <<Buf/bitstring, Dictionary/bitstring>>).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
dictionary_test() ->
|
||||
?assertEqual(dictionary(#{<<"test">> => []}),
|
||||
{ok, <<"d4:testlee">>}),
|
||||
?assertEqual(dictionary(#{a => #{ b => c }}),
|
||||
{ok,<<"d1:ad1:b1:cee">>}),
|
||||
?assertEqual(dictionary(#{aaa => 3, bb => 2, c => 1 }),
|
||||
{ok,<<"d1:ci1e2:bbi2e3:aaai3ee">>}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec dictionary(map()) -> {ok, bitstring()}.
|
||||
dictionary(Map)
|
||||
when is_map(Map), map_size(Map) =:= 0 ->
|
||||
{ok, <<"de">>};
|
||||
dictionary(Map)
|
||||
when is_map(Map), map_size(Map) >= 1 ->
|
||||
Keys = sort_map_keys(Map),
|
||||
Enc = fun(K, M) ->
|
||||
V = maps:get(K, M),
|
||||
key_value(K, V)
|
||||
end,
|
||||
Dict = << (Enc(Key, Map)) || Key <- Keys >>,
|
||||
{ok, <<$d, Dict/bitstring, $e>>}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
sort_map_keys_test() ->
|
||||
?assertEqual(sort_map_keys(#{aaa => 3, bb => 2, c => 1}),
|
||||
[c, bb, aaa]),
|
||||
?assertEqual(sort_map_keys(#{<<"aaa">> => 3, <<"bb">> => 2, <<"c">> => 1 }),
|
||||
[<<"c">>,<<"bb">>,<<"aaa">>]).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec sort_map_keys(map()) -> list().
|
||||
sort_map_keys(Map)
|
||||
when is_map(Map) ->
|
||||
Keys = maps:keys(Map),
|
||||
Fun = fun F(X) when is_atom(X) ->
|
||||
erlang:atom_to_binary(X, utf8);
|
||||
F(X) when is_list(X) ->
|
||||
erlang:list_to_bitstring(X);
|
||||
F(X) when is_integer(X) ->
|
||||
erlang:integer_to_binary(X);
|
||||
F(X) -> X
|
||||
end,
|
||||
Zip = [ {erlang:byte_size(Fun(X)), X} || X <- Keys ],
|
||||
SortedZip = lists:sort(Zip),
|
||||
{_, Sort} = lists:unzip(SortedZip),
|
||||
Sort.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
key_test() ->
|
||||
?assertEqual(key(a), <<"1:a">>),
|
||||
?assertEqual(key(1), <<"1:1">>),
|
||||
?assertEqual(key(<<"a">>), <<"1:a">>).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec key(integer() | atom() | list() | bitstring())
|
||||
-> bitstring().
|
||||
key(Key) when is_integer(Key) ->
|
||||
key(erlang:integer_to_binary(Key));
|
||||
key(Key) when is_list(Key) ->
|
||||
key(erlang:list_to_bitstring(Key));
|
||||
key(Key) when is_atom(Key) ->
|
||||
key(erlang:atom_to_binary(Key, utf8));
|
||||
key(Key) when is_bitstring(Key) ->
|
||||
{ok, KeyBitstring} = string(Key),
|
||||
KeyBitstring.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
value_test() ->
|
||||
ok.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec value(integer() | atom() | list() | bitstring())
|
||||
-> bitstring().
|
||||
value(Value) when is_integer(Value) ->
|
||||
{ok, ValueBitstring} = integer(Value),
|
||||
ValueBitstring;
|
||||
value(Value) when is_bitstring(Value) ->
|
||||
{ok, ValueBitstring} = string(Value),
|
||||
ValueBitstring;
|
||||
value(Value) when is_atom(Value) ->
|
||||
{ok, ValueBitstring} = string(Value),
|
||||
ValueBitstring;
|
||||
value(Value) when is_list(Value) ->
|
||||
{ok, ValueBitstring} = list(Value),
|
||||
ValueBitstring;
|
||||
value(Value) when is_map(Value) ->
|
||||
{ok, ValueBitstring} = dictionary(Value),
|
||||
ValueBitstring.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
%% @doc
|
||||
%% @end
|
||||
%%--------------------------------------------------------------------
|
||||
-spec key_value(bitstring(), integer() | list() | bitstring() | map() )
|
||||
-> bitstring().
|
||||
key_value(Key, Value) ->
|
||||
K = key(Key),
|
||||
V = value(Value),
|
||||
<<K/bitstring, V/bitstring>>.
|
||||
Reference in New Issue
Block a user