diff --git a/tools/Jamfile b/tools/Jamfile
index 961d69cb4..99232d03e 100644
--- a/tools/Jamfile
+++ b/tools/Jamfile
@@ -33,6 +33,7 @@ project tools
static
;
+exe fuzz_torrent : fuzz_torrent.cpp ;
exe parse_hash_fails : parse_hash_fails.cpp ;
exe parse_access_log : parse_access_log.cpp ;
exe parse_request_log : parse_request_log.cpp ;
diff --git a/tools/Makefile.am b/tools/Makefile.am
index a6c7d0785..eea60386a 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -1,5 +1,6 @@
tool_programs = \
- parse_request_log
+ parse_request_log \
+ fuzz_torrent
if ENABLE_EXAMPLES
bin_PROGRAMS = $(tool_programs)
@@ -26,6 +27,7 @@ EXTRA_DIST = Jamfile \
run_tests.py
parse_request_log_SOURCES = parse_request_log.cpp
+fuzz_torrent_SOURCES = fuzz_torrent.cpp
LDADD = $(top_builddir)/src/libtorrent-rasterbar.la
diff --git a/tools/fuzz_torrent.cpp b/tools/fuzz_torrent.cpp
new file mode 100644
index 000000000..c1f39c6d4
--- /dev/null
+++ b/tools/fuzz_torrent.cpp
@@ -0,0 +1,415 @@
+/*
+
+Copyright (c) 2015, Arvid Norberg
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * 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.
+ * Neither the name of the author 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
+
+*/
+
+#include
+#include
+#include
+#include
+#include "libtorrent/lazy_entry.hpp"
+#include "libtorrent/torrent_info.hpp"
+#include "libtorrent/error_code.hpp"
+
+using libtorrent::lazy_entry;
+using boost::random::mt19937;
+using boost::random::uniform_int_distribution;
+
+char const* invalid_utf8_sequences[] =
+{
+"\x80",
+"\xbf",
+"\xff",
+"\xfe",
+"\xff\xff\xfe\xfe",
+"\xc0\xaf",
+"\xe0\x80\xaf",
+"\xf0\x80\x80\xaf",
+"\xf8\x80\x80\x80\xaf ",
+"\xfc\x80\x80\x80\x80\xaf",
+"\xc1\xbf",
+"\xe0\x9f\xbf",
+"\xf0\x8f\xbf\xbf",
+"\xf8\x87\xbf\xbf\xbf",
+"\xfc\x83\xbf\xbf\xbf\xbf",
+"\xc0\x80",
+"\xe0\x80\x80",
+"\xf0\x80\x80\x80",
+"\xf8\x80\x80\x80\x80",
+"\xfc\x80\x80\x80\x80\x80",
+"\xed\xa0\x80",
+"\xed\xad\xbf",
+"\xed\xae\x80",
+"\xed\xaf\xbf",
+"\xed\xb0\x80",
+"\xed\xbe\x80",
+"\xed\xbf\xbf",
+"\xed\xa0\x80\xed\xb0\x80",
+"\xed\xa0\x80\xed\xbf\xbf",
+"\xed\xad\xbf\xed\xb0\x80",
+"\xed\xad\xbf\xed\xbf\xbf",
+"\xed\xae\x80\xed\xb0\x80",
+"\xed\xae\x80\xed\xbf\xbf",
+"\xed\xaf\xbf\xed\xb0\x80",
+"\xed\xaf\xbf\xed\xbf\xbf",
+};
+
+boost::int64_t g_seed;
+
+void print_ascii_number(std::string& output, boost::int64_t val)
+{
+ const bool overflow = g_seed == 1;
+ const bool underflow = g_seed == 2;
+ const bool negative = g_seed == 3;
+ const bool double_negative = g_seed == 4;
+ const bool zero = g_seed == 5;
+ g_seed -= 5;
+
+ char const* numbers = "0123456789";
+ if (zero)
+ {
+ output += '0';
+ }
+ else if (underflow)
+ {
+ output += '-';
+ for (int i = 0; i < 100; ++i) output += numbers[rand() % 10];
+ return;
+ }
+ else if (overflow)
+ {
+ for (int i = 0; i < 100; ++i) output += numbers[rand() % 10];
+ return;
+ }
+ else
+ {
+ if (negative) output += '-';
+ else if (double_negative) output += "--";
+ char buf[50];
+ snprintf(buf, sizeof(buf), "%" PRId64 "", val);
+ output += buf;
+ }
+}
+
+void print_string(std::string& output, std::string str)
+{
+ const bool empty_string = g_seed == 1;
+ g_seed -= 1;
+ if (empty_string)
+ {
+ print_ascii_number(output, 0);
+ output += ':';
+ return;
+ }
+
+ const bool random_string = g_seed > 0 && g_seed <= 1000;
+ const int str_seed = g_seed - 1;
+ g_seed -= 1000;
+ if (random_string)
+ {
+ static mt19937 random_engine(str_seed);
+ uniform_int_distribution d(0, 255);
+ for (int i = 0; i < str.size(); ++i)
+ str[i] = d(random_engine);
+
+ print_ascii_number(output, str.size());
+ output += ':';
+ output += str;
+ return;
+ }
+
+ const int num_sequences = (sizeof(invalid_utf8_sequences)/sizeof(char const*));
+ const bool invalid_utf8 = g_seed <= num_sequences && g_seed > 0;
+
+ if (invalid_utf8)
+ str += invalid_utf8_sequences[g_seed-1];
+
+ g_seed -= num_sequences;
+
+ print_ascii_number(output, str.size());
+ output += ':';
+ output += str;
+}
+
+void print_terminate(std::string& output)
+{
+ const bool unterminated = g_seed == 1;
+ g_seed -= 1;
+ if (!unterminated) output += 'e';
+}
+
+void print_int(std::string& output, boost::int64_t value)
+{
+ const bool double_int = g_seed == 1;
+ g_seed -= 1;
+ if (double_int) output += 'i';
+ output += 'i';
+ print_ascii_number(output, value);
+ print_terminate(output);
+}
+
+void print_dict(std::string& output)
+{
+ const bool double_dict = g_seed == 1;
+ g_seed -= 1;
+ if (double_dict) output += 'd';
+ output += 'd';
+}
+
+void print_list(std::string& output)
+{
+ const bool double_list = g_seed == 1;
+ g_seed -= 1;
+ if (double_list) output += 'l';
+ output += 'l';
+}
+
+void render_arbitrary_item(std::string& out)
+{
+ if (g_seed <= 0) return;
+
+ std::string option;
+ print_int(option, 1337);
+ if (g_seed <= 0)
+ {
+ out += option;
+ return;
+ }
+
+ option.clear();
+ print_string(option, "abcdefgh");
+ if (g_seed <= 0)
+ {
+ out += option;
+ return;
+ }
+
+ option.clear();
+ print_dict(option);
+ print_string(option, "abcdefgh");
+ print_int(option, 1337);
+ print_terminate(option);
+ if (g_seed <= 0)
+ {
+ out += option;
+ return;
+ }
+
+ option.clear();
+ print_list(option);
+ print_string(option, "abcdefgh");
+ print_terminate(option);
+ if (g_seed <= 0)
+ {
+ out += option;
+ return;
+ }
+}
+
+void render_variant(std::string& out, lazy_entry const& e)
+{
+ switch (e.type())
+ {
+ case lazy_entry::dict_t:
+ print_dict(out);
+ for (int i = 0; i < e.dict_size(); ++i)
+ {
+ std::pair item = e.dict_at(i);
+ const bool duplicate = g_seed == 1;
+ const bool skipped = g_seed == 2;
+ g_seed -= 2;
+ if (duplicate)
+ {
+ print_string(out, item.first);
+ render_variant(out, *item.second);
+ }
+ if (!skipped)
+ {
+ print_string(out, item.first);
+ render_variant(out, *item.second);
+ }
+
+ render_arbitrary_item(out);
+ }
+ print_terminate(out);
+ break;
+ case lazy_entry::list_t:
+ print_list(out);
+ for (int i = 0; i < e.list_size(); ++i)
+ {
+ const bool duplicate = g_seed == 1;
+ const bool skipped = g_seed == 2;
+ g_seed -= 2;
+ if (duplicate) render_variant(out, *e.list_at(i));
+
+ render_arbitrary_item(out);
+
+ if (!skipped) render_variant(out, *e.list_at(i));
+ }
+ print_terminate(out);
+ break;
+ case lazy_entry::int_t:
+ print_int(out, e.int_value());
+ break;
+ case lazy_entry::string_t:
+ print_string(out, e.string_value());
+ break;
+ default:
+ abort();
+ }
+}
+
+int load_file(std::string const& filename, std::vector& v
+ , libtorrent::error_code& ec, int limit = 8000000)
+{
+ ec.clear();
+ FILE* f = fopen(filename.c_str(), "rb");
+ if (f == NULL)
+ {
+ ec.assign(errno, boost::system::generic_category());
+ return -1;
+ }
+
+ int r = fseek(f, 0, SEEK_END);
+ if (r != 0)
+ {
+ ec.assign(errno, boost::system::generic_category());
+ fclose(f);
+ return -1;
+ }
+ long s = ftell(f);
+ if (s < 0)
+ {
+ ec.assign(errno, boost::system::generic_category());
+ fclose(f);
+ return -1;
+ }
+
+ if (s > limit)
+ {
+ fclose(f);
+ return -2;
+ }
+
+ r = fseek(f, 0, SEEK_SET);
+ if (r != 0)
+ {
+ ec.assign(errno, boost::system::generic_category());
+ fclose(f);
+ return -1;
+ }
+
+ v.resize(s);
+ if (s == 0)
+ {
+ fclose(f);
+ return 0;
+ }
+
+ r = fread(&v[0], 1, v.size(), f);
+ if (r < 0)
+ {
+ ec.assign(errno, boost::system::generic_category());
+ fclose(f);
+ return -1;
+ }
+
+ fclose(f);
+
+ if (r != s) return -3;
+
+ return 0;
+}
+
+int main(int argc, char const* argv[])
+{
+ std::vector buf;
+ libtorrent::error_code ec;
+
+ if (argc < 2)
+ {
+ fprintf(stderr, "usage: fuzz_torrent torrent-file [torrent-file ...]\n");
+ return 1;
+ }
+
+ --argc;
+ ++argv;
+ for (;argc > 0; --argc, ++argv)
+ {
+ int ret = load_file(*argv, buf, ec);
+ if (ret < 0)
+ {
+ fprintf(stderr, "ERROR loading file: %s\n%s\n"
+ , *argv, ec.message().c_str());
+ continue;
+ }
+
+ lazy_entry e;
+ if (buf.size() == 0 || lazy_bdecode(&buf[0], &buf[0] + buf.size(), e, ec) != 0)
+ {
+ fprintf(stderr, "ERROR parsing file: %s\n%s\n"
+ , *argv, ec.message().c_str());
+ continue;
+ }
+
+ std::string test_buffer;
+ int i = 0;
+ for (i = 0; i < 10000000; ++i)
+ {
+ g_seed = i;
+ test_buffer.clear();
+ render_variant(test_buffer, e);
+
+ libtorrent::error_code ec;
+ libtorrent::torrent_info t(test_buffer.c_str(), test_buffer.size(), ec);
+
+ // TODO: add option to save to file unconditionally (to test other clients)
+ /*
+ {
+ fprintf(stderr, "saving %d\n", i);
+ char filename[100];
+ snprintf(filename, sizeof(filename), "torrents/fuzz-%d.torrent", i);
+ FILE* f = fopen(filename, "wb+");
+ if (f == 0)
+ {
+ fprintf(stderr, "ERROR saving file: (%d) %s\n", errno, strerror(errno));
+ return 1;
+ }
+ fwrite(test_buffer.c_str(), test_buffer.size(), 1, f);
+ fclose(f);
+ }
+ */
+ if (g_seed > 0) break;
+ }
+ fprintf(stderr, "tested %d variants of %s\n", i, *argv);
+ }
+ return 0;
+}
+