epee: optimize uri conversion functions

- greatly reduced # of string allocations (only 1 per function)
- fixed typo in function name (conver -> convert)
- used more standard library functions to simplify manual logic and remove
  pointer arithmetic
- removed all functions in header that were only used by these 2 functions
- replaced const std::string& parameter with std::string_view
- use compile-time generated lookup table for O(1) encoding of characters
This commit is contained in:
Jazeel Abdul-Jabbar 2025-12-19 21:19:25 -05:00
parent 48ad374b0d
commit 1de0750bf2
No known key found for this signature in database
3 changed files with 57 additions and 118 deletions

View file

@ -34,27 +34,8 @@ namespace epee
{
namespace net_utils
{
inline const char* get_hex_vals()
{
static constexpr const char hexVals[16] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
return hexVals;
}
inline const char* get_unsave_chars()
{
//static constexpr char unsave_chars[] = "\"<>%\\^[]`+$,@:;/!#?=&";
static constexpr const char unsave_chars[] = "\"<>%\\^[]`+$,@:;!#&";
return unsave_chars;
}
bool is_unsafe(unsigned char compare_char);
std::string dec_to_hex(char num, int radix);
int get_index(const char *s, char c);
std::string hex_to_dec_2bytes(const char *s);
std::string convert(char val);
std::string conver_to_url_format(const std::string& uri);
std::string convert_from_url_format(const std::string& uri);
std::string convert_to_url_format(std::string_view uri);
std::string convert_from_url_format(std::string_view uri);
namespace http
{
class abstract_http_client

View file

@ -1,3 +1,4 @@
#include <charconv>
#include <math.h>
#include "net/abstract_http_client.h"
#include "net/http_base.h"
@ -7,113 +8,70 @@
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "net.http"
namespace {
constexpr auto hex_byte_len = 2; // 00 to FF
constexpr char uri_escape_char = '%'; // placed before each byte i.e. %61
// immediately invoked lambda expression that creates a lookup table at compile-time.
// each safe byte for URL is '\0\0', each unsafe byte has the hex representation of the byte.
constexpr auto encoding_table = []() -> std::array<std::array<unsigned char, hex_byte_len>, 256> {
std::array<std::array<unsigned char, hex_byte_len>, 256> temp{}; // everything is zeroed-out.
std::string_view unsafe{"\"<>%\\^[]`+$,@:;!#&"};
for (std::size_t i = 0; i < temp.size(); ++i) {
if((i <= 32) || (i >= 123) || (unsafe.find(i) != unsafe.npos)) {
// encode the unsafe characters.
const auto first {i / 16};
const auto second {i % 16};
temp[i][0] = (first < 0xA) ? ('0' + first) : ('A' + (first - 0xA));
temp[i][1] = (second < 0xA) ? ('0' + second) : ('A' + (second - 0xA));
}
}
return temp;
}(); // note : all this is done at compile-time.
} // namespace
namespace epee
{
namespace net_utils
{
//----------------------------------------------------------------------------------------------------
bool is_unsafe(unsigned char compare_char)
std::string convert_to_url_format(std::string_view uri)
{
if(compare_char <= 32 || compare_char >= 123)
return true;
const char* punsave = get_unsave_chars();
for(int ichar_pos = 0; 0!=punsave[ichar_pos] ;ichar_pos++)
if(compare_char == punsave[ichar_pos])
return true;
return false;
}
//----------------------------------------------------------------------------------------------------
std::string dec_to_hex(char num, int radix)
{
int temp=0;
std::string csTmp;
int num_char;
num_char = (int) num;
if (num_char < 0)
num_char = 256 + num_char;
while (num_char >= radix)
{
temp = num_char % radix;
num_char = (int)floor((float)num_char / (float)radix);
csTmp = get_hex_vals()[temp];
std::string result (uri.size() * (hex_byte_len + 1), '\0'); // reserve the max possible size, shrink later.
auto write_it {result.begin()};
for (const unsigned char character : uri) {
const std::array<unsigned char, hex_byte_len>& encoding = encoding_table[character];
if (encoding.front() == '\0') {
*(write_it++) = character;
} else {
*write_it = uri_escape_char;
write_it = std::copy(encoding.begin(), encoding.end(), write_it + 1);
}
}
csTmp += get_hex_vals()[num_char];
if(csTmp.size() < 2)
{
csTmp += '0';
}
std::reverse(csTmp.begin(), csTmp.end());
//_mbsrev((unsigned char*)csTmp.data());
return csTmp;
}
//----------------------------------------------------------------------------------------------------
int get_index(const char *s, char c)
{
const char *ptr = (const char*)memchr(s, c, 16);
return ptr ? ptr-s : -1;
}
//----------------------------------------------------------------------------------------------------
std::string hex_to_dec_2bytes(const char *s)
{
const char *hex = get_hex_vals();
int i0 = get_index(hex, toupper(s[0]));
int i1 = get_index(hex, toupper(s[1]));
if (i0 < 0 || i1 < 0)
return std::string("%") + std::string(1, s[0]) + std::string(1, s[1]);
return std::string(1, i0 * 16 | i1);
}
//----------------------------------------------------------------------------------------------------
std::string convert(char val)
{
std::string csRet;
csRet += "%";
csRet += dec_to_hex(val, 16);
return csRet;
}
//----------------------------------------------------------------------------------------------------
std::string conver_to_url_format(const std::string& uri)
{
std::string result;
for(size_t i = 0; i!= uri.size(); i++)
{
if(is_unsafe(uri[i]))
result += convert(uri[i]);
else
result += uri[i];
}
result.resize(std::distance(result.begin(), write_it));
return result;
}
//----------------------------------------------------------------------------------------------------
std::string convert_from_url_format(const std::string& uri)
std::string convert_from_url_format(std::string_view uri)
{
std::string result;
for(size_t i = 0; i!= uri.size(); i++)
{
if(uri[i] == '%' && i + 2 < uri.size())
{
result += hex_to_dec_2bytes(uri.c_str() + i + 1);
i += 2;
std::string result (uri.size(), '\0'); // reserve the max possible size, shrink later.
auto write_it {result.begin()};
for (auto it {uri.cbegin()}, end {uri.cend()}; it < end; ++it) {
const auto character {*it};
if (character != uri_escape_char || std::distance(it, end) < (hex_byte_len + 1)) {
*(write_it++) = character;
continue;
}
else
result += uri[i];
unsigned char decoded{};
const auto hex_begin = it + 1; // skip '%'
const auto hex_end {hex_begin + hex_byte_len};
const auto [ptr, ec] = std::from_chars(hex_begin, hex_end, decoded, 16);
if (ec == std::errc{} && ptr == hex_end) {
*(write_it++) = decoded;
} else {
write_it = std::copy(it, hex_end, write_it); // in case of failure, copy the original string's contents.
}
it += hex_byte_len;
}
result.resize(std::distance(result.begin(), write_it));
return result;
}
//----------------------------------------------------------------------------------------------------

View file

@ -14850,12 +14850,12 @@ std::string wallet2::make_uri(const std::string &address, const std::string &pay
if (!recipient_name.empty())
{
uri += (n_fields++ ? "&" : "?") + std::string("recipient_name=") + epee::net_utils::conver_to_url_format(recipient_name);
uri += (n_fields++ ? "&" : "?") + std::string("recipient_name=") + epee::net_utils::convert_to_url_format(recipient_name);
}
if (!tx_description.empty())
{
uri += (n_fields++ ? "&" : "?") + std::string("tx_description=") + epee::net_utils::conver_to_url_format(tx_description);
uri += (n_fields++ ? "&" : "?") + std::string("tx_description=") + epee::net_utils::convert_to_url_format(tx_description);
}
return uri;