diff --git a/fuzzers/src/base32encode.cpp b/fuzzers/src/base32encode.cpp index 737e52b558f..5bc1cd63626 100644 --- a/fuzzers/src/base32encode.cpp +++ b/fuzzers/src/base32encode.cpp @@ -34,7 +34,7 @@ POSSIBILITY OF SUCH DAMAGE. extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size) { - lt::base32encode({reinterpret_cast(data), size}); + lt::base32encode_i2p({reinterpret_cast(data), static_cast(size)}); return 0; } diff --git a/include/libtorrent/aux_/escape_string.hpp b/include/libtorrent/aux_/escape_string.hpp index 5946b98858f..0f039dafe58 100644 --- a/include/libtorrent/aux_/escape_string.hpp +++ b/include/libtorrent/aux_/escape_string.hpp @@ -41,24 +41,12 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/string_view.hpp" #include "libtorrent/flags.hpp" #if TORRENT_USE_I2P +#include #include "libtorrent/span.hpp" #endif namespace libtorrent { - // hidden - using encode_string_flags_t = flags::bitfield_flag; - - 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); @@ -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 s, encode_string_flags_t flags = {}); + TORRENT_EXTRA_EXPORT std::string base32encode_i2p(span s); + TORRENT_EXTRA_EXPORT std::vector base64decode_i2p(string_view s); #endif TORRENT_EXTRA_EXPORT std::string base32decode(string_view s); diff --git a/src/bt_peer_connection.cpp b/src/bt_peer_connection.cpp index 535bb752030..fa480c06b8c 100644 --- a/src/bt_peer_connection.cpp +++ b/src/bt_peer_connection.cpp @@ -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; diff --git a/src/escape_string.cpp b/src/escape_string.cpp index c39ac2747a7..28a79f1eb54 100644 --- a/src/escape_string.cpp +++ b/src/escape_string.cpp @@ -40,6 +40,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #ifdef TORRENT_WINDOWS #include "libtorrent/aux_/windows.hpp" @@ -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 { @@ -297,23 +299,70 @@ namespace libtorrent { } #if TORRENT_USE_I2P - std::string base32encode(span 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 base64decode_i2p(string_view s) { - static char const base32_table_canonical[] = + std::uint32_t output = 0; + + std::vector 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 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 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 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 const input_output_mapping{{{0, 2, 4, 5, 7, 8}}}; @@ -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); @@ -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; } diff --git a/src/torrent.cpp b/src/torrent.cpp index 40fc382f009..4cc854b67b1 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -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"); diff --git a/test/test_string.cpp b/test/test_string.cpp index 3ed4fc966c3..1e8e50f0165 100644 --- a/test/test_string.cpp +++ b/test/test_string.cpp @@ -211,6 +211,32 @@ TORRENT_TEST(to_string) TEST_CHECK(to_string(-999999999999999999).data() == std::string("-999999999999999999")); } +namespace { + +template +std::vector to_vec(char const (&str)[N]) +{ + return std::vector(&str[0], &str[N - 1]); +} + +std::string to_str(std::vector 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 @@ -221,6 +247,20 @@ 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 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) @@ -228,24 +268,20 @@ 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("") == "");