Skip to content

Commit

Permalink
add base64decode() for i2p alphabet and simplify base32encode()
Browse files Browse the repository at this point in the history
  • Loading branch information
arvidn committed Apr 10, 2023
1 parent 4323b0c commit 87c38dc
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 52 deletions.
2 changes: 1 addition & 1 deletion fuzzers/src/base32encode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ POSSIBILITY OF SUCH DAMAGE.

extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{
lt::base32encode({reinterpret_cast<char const*>(data), size});
lt::base32encode_i2p({reinterpret_cast<char const*>(data), static_cast<int>(size)});
return 0;
}

17 changes: 3 additions & 14 deletions include/libtorrent/aux_/escape_string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,12 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/string_view.hpp"
#include "libtorrent/flags.hpp"
#if TORRENT_USE_I2P
#include <vector>
#include "libtorrent/span.hpp"
#endif

namespace libtorrent {

// hidden
using encode_string_flags_t = flags::bitfield_flag<std::uint8_t, struct encode_string_flags_tag>;

namespace string
{
// use lower case alphabet used with i2p
constexpr encode_string_flags_t lowercase = 0_bit;
// don't insert padding
constexpr encode_string_flags_t no_padding = 1_bit;
// shortcut used for addresses as sha256 hashes
constexpr encode_string_flags_t i2p = lowercase | no_padding;
}

TORRENT_EXTRA_EXPORT std::string unescape_string(string_view s, error_code& ec);
// replaces all disallowed URL characters by their %-encoding
TORRENT_EXTRA_EXPORT std::string escape_string(string_view str);
Expand All @@ -80,7 +68,8 @@ namespace libtorrent {
TORRENT_EXTRA_EXPORT std::string base64encode(std::string const& s);
#if TORRENT_USE_I2P
// encodes a string using the base32 scheme
TORRENT_EXTRA_EXPORT std::string base32encode(span<char const> s, encode_string_flags_t flags = {});
TORRENT_EXTRA_EXPORT std::string base32encode_i2p(span<char const> s);
TORRENT_EXTRA_EXPORT std::vector<char> base64decode_i2p(string_view s);
#endif
TORRENT_EXTRA_EXPORT std::string base32decode(string_view s);

Expand Down
18 changes: 16 additions & 2 deletions src/bt_peer_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -489,8 +489,22 @@ namespace {
{
p.flags |= peer_info::i2p_socket;
auto const* pi = peer_info_struct();
sha256_hash const b32_hash = hasher256(pi->dest()).final();
p.set_i2p_destination(b32_hash);
if (pi != nullptr)
{
try
{
sha256_hash const b32_addr = hasher256(base64decode_i2p(pi->dest())).final();
p.set_i2p_destination(b32_addr);
}
catch (lt::system_error const&)
{
p.set_i2p_destination(sha256_hash());
}
}
else
{
p.set_i2p_destination(sha256_hash());
}
}
#endif
if (is_utp(get_socket())) p.flags |= peer_info::utp_socket;
Expand Down
81 changes: 60 additions & 21 deletions src/escape_string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include <algorithm>
#include <mutex>
#include <cstring>
#include <vector>

#ifdef TORRENT_WINDOWS
#include "libtorrent/aux_/windows.hpp"
Expand All @@ -54,6 +55,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/aux_/escape_string.hpp"
#include "libtorrent/string_util.hpp" // for to_string
#include "libtorrent/aux_/array.hpp"
#include "libtorrent/aux_/byteswap.hpp"

namespace libtorrent {

Expand Down Expand Up @@ -297,23 +299,70 @@ namespace libtorrent {
}

#if TORRENT_USE_I2P
std::string base32encode(span<char const> s, encode_string_flags_t const flags)

namespace {
std::uint32_t map_base64_char(char const c)
{
if (c >= 'A' && c <= 'Z')
return std::uint32_t(c - 'A');
if (c >= 'a' && c <= 'z')
return std::uint32_t(26 + c - 'a');
if (c >= '0' && c <= '9')
return std::uint32_t(52 + c - '0');
if (c == '-') return 62;
if (c == '~') return 63;
throw system_error(error_code(lt::errors::invalid_escaped_string));
}
}

// this decodes the i2p alphabet
std::vector<char> base64decode_i2p(string_view s)
{
static char const base32_table_canonical[] =
std::uint32_t output = 0;

std::vector<char> ret;
int bit_offset = 18;
for (auto const c : s)
{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '2', '3', '4', '5', '6', '7'
};
static char const base32_table_lowercase[] =
if (c == '=') break;
output |= map_base64_char(c) << bit_offset;
if (bit_offset == 0)
{
output = aux::host_to_network(output);
aux::array<char, 4> tmp;
std::memcpy(tmp.data(), &output, 4);
ret.push_back(tmp[1]);
ret.push_back(tmp[2]);
ret.push_back(tmp[3]);
output = 0;
bit_offset = 18;
}
else
{
bit_offset -= 6;
}
}
if (bit_offset < 18)
{
output = aux::host_to_network(output);
aux::array<char, 4> tmp;
std::memcpy(tmp.data(), &output, 4);
ret.push_back(tmp[1]);
if (bit_offset < 6)
ret.push_back(tmp[2]);
}
return ret;
}

std::string base32encode_i2p(span<char const> s)
{
static char const base32_table[] =
{
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', '2', '3', '4', '5', '6', '7'
};
char const *base32_table = (flags & string::lowercase) ? base32_table_lowercase : base32_table_canonical;

static aux::array<int, 6> const input_output_mapping{{{0, 2, 4, 5, 7, 8}}};

Expand All @@ -323,7 +372,7 @@ namespace libtorrent {
std::string ret;
for (auto i = s.begin(); i != s.end();)
{
int available_input = std::min(int(inbuf.size()), int(s.end() - i));
int const available_input = std::min(int(inbuf.size()), int(s.end() - i));

// clear input buffer
inbuf.fill(0);
Expand All @@ -345,18 +394,8 @@ namespace libtorrent {
// write output
int const num_out = input_output_mapping[available_input];
for (int j = 0; j < num_out; ++j)
{
ret += base32_table[outbuf[j]];
}

if (!(flags & string::no_padding))
{
// write pad
for (int j = 0; j < int(outbuf.size()) - num_out; ++j)
{
ret += '=';
}
}
// i2p does not use padding
}
return ret;
}
Expand Down
2 changes: 1 addition & 1 deletion src/torrent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3557,7 +3557,7 @@ namespace {
{
torrent_state st = get_peer_list_state();
peer_entry p;
std::string destination = base32encode(i.destination, string::i2p);
std::string destination = base32encode_i2p(i.destination);
destination += ".b32.i2p";

ADD_OUTSTANDING_ASYNC("torrent::on_i2p_resolve");
Expand Down
62 changes: 49 additions & 13 deletions test/test_string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,32 @@ TORRENT_TEST(to_string)
TEST_CHECK(to_string(-999999999999999999).data() == std::string("-999999999999999999"));
}

namespace {

template <size_t N>
std::vector<char> to_vec(char const (&str)[N])
{
return std::vector<char>(&str[0], &str[N - 1]);
}

std::string to_str(std::vector<char> const& v)
{
return std::string(v.begin(), v.end());
}

// convert the standard base64 alphabet to the i2p aphabet
std::string transcode_alphabet(std::string in)
{
std::string ret;
std::transform(in.begin(), in.end(), std::back_inserter(ret), [](char const c) {
if (c == '+') return '-';
if (c == '/') return '~';
return c;
});
return ret;
}
}

TORRENT_TEST(base64)
{
// base64 test vectors from http://www.faqs.org/rfcs/rfc4648.html
Expand All @@ -221,31 +247,41 @@ TORRENT_TEST(base64)
TEST_CHECK(base64encode("foob") == "Zm9vYg==");
TEST_CHECK(base64encode("fooba") == "Zm9vYmE=");
TEST_CHECK(base64encode("foobar") == "Zm9vYmFy");
#if TORRENT_USE_I2P
TEST_CHECK(base64decode_i2p("") == to_vec(""));
TEST_CHECK(base64decode_i2p("Zg==") == to_vec("f"));
TEST_CHECK(base64decode_i2p("Zm8=") == to_vec("fo"));
TEST_CHECK(base64decode_i2p("Zm9v") == to_vec("foo"));
TEST_CHECK(base64decode_i2p("Zm9vYg==") == to_vec("foob"));
TEST_CHECK(base64decode_i2p("Zm9vYmE=") == to_vec("fooba"));
TEST_CHECK(base64decode_i2p("Zm9vYmFy") == to_vec("foobar"));

std::vector<char> test;
for (int i = 0; i < 255; ++i)
test.push_back(char(i));
TEST_CHECK(base64decode_i2p(transcode_alphabet(base64encode(to_str(test)))) == test);
#endif
}

TORRENT_TEST(base32)
{
// base32 test vectors from http://www.faqs.org/rfcs/rfc4648.html

#if TORRENT_USE_I2P
TEST_CHECK(base32encode(""_sv) == "");
TEST_CHECK(base32encode("f"_sv) == "MY======");
TEST_CHECK(base32encode("fo"_sv) == "MZXQ====");
TEST_CHECK(base32encode("foo"_sv) == "MZXW6===");
TEST_CHECK(base32encode("foob"_sv) == "MZXW6YQ=");
TEST_CHECK(base32encode("fooba"_sv) == "MZXW6YTB");
TEST_CHECK(base32encode("foobar"_sv) == "MZXW6YTBOI======");

// base32 for i2p
TEST_CHECK(base32encode("fo"_sv, string::no_padding) == "MZXQ");
TEST_CHECK(base32encode("foob"_sv, string::i2p) == "mzxw6yq");
TEST_CHECK(base32encode("foobar"_sv, string::lowercase) == "mzxw6ytboi======");
// i2p uses lower case and no padding
TEST_CHECK(base32encode_i2p(""_sv) == "");
TEST_CHECK(base32encode_i2p("f"_sv) == "my");
TEST_CHECK(base32encode_i2p("fo"_sv) == "mzxq");
TEST_CHECK(base32encode_i2p("foo"_sv) == "mzxw6");
TEST_CHECK(base32encode_i2p("foob"_sv) == "mzxw6yq");
TEST_CHECK(base32encode_i2p("fooba"_sv) == "mzxw6ytb");
TEST_CHECK(base32encode_i2p("foobar"_sv) == "mzxw6ytboi");

std::string test;
for (int i = 0; i < 255; ++i)
test += char(i);

TEST_CHECK(base32decode(base32encode(test)) == test);
TEST_CHECK(base32decode(base32encode_i2p(test)) == test);
#endif // TORRENT_USE_I2P

TEST_CHECK(base32decode("") == "");
Expand Down

0 comments on commit 87c38dc

Please sign in to comment.