refactor: use std::string_view in benc parser (#2182)

* refactor: use std::string_view to parse benc

* refactor: simplify private parsing API in variant.cc

* chore: remove unneeded includes

* refactor: merge tr_variantFromBenc and tr_variantFromBencFull
This commit is contained in:
Charles Kerr 2021-11-16 21:29:30 -06:00 committed by GitHub
parent 60b802a22b
commit 1ba81938bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 271 additions and 349 deletions

View File

@ -137,6 +137,18 @@ std::string tr_magnet_metainfo::magnet() const
return s;
}
static tr_quark announceToScrape(std::string_view announce)
{
auto buf = std::string{};
if (!tr_magnet_metainfo::convertAnnounceToScrape(buf, announce))
{
return TR_KEY_NONE;
}
return tr_quark_new(buf);
}
bool tr_magnet_metainfo::addTracker(tr_tracker_tier_t tier, std::string_view announce_sv)
{
announce_sv = tr_strvStrip(announce_sv);
@ -146,9 +158,8 @@ bool tr_magnet_metainfo::addTracker(tr_tracker_tier_t tier, std::string_view ann
return false;
}
auto buf = std::string{};
auto const announce_url = tr_quark_new(announce_sv);
auto const scrape_url = convertAnnounceToScrape(buf, announce_sv) ? tr_quark_new(buf) : TR_KEY_NONE;
auto const scrape_url = announceToScrape(announce_sv);
this->trackers.insert({ tier, { announce_url, scrape_url, tier } });
return true;
}

View File

@ -1241,7 +1241,7 @@ static void parseUtMetadata(tr_peerMsgsImpl* msgs, uint32_t msglen, struct evbuf
auto dict = tr_variant{};
char const* benc_end = nullptr;
if (tr_variantFromBencFull(&dict, std::string_view{ tmp, msglen }, &benc_end) == 0)
if (tr_variantFromBenc(&dict, std::string_view{ tmp, msglen }, &benc_end) == 0)
{
(void)tr_variantDictFindInt(&dict, TR_KEY_msg_type, &msg_type);
(void)tr_variantDictFindInt(&dict, TR_KEY_piece, &piece);

View File

@ -6,6 +6,7 @@
*
*/
#include <cstdlib>
#include <cctype> /* isdigit() */
#include <deque>
#include <cerrno>
@ -23,6 +24,8 @@
#include "variant.h"
#include "variant-common.h"
using namespace std::literals;
#define MAX_BENC_STR_LENGTH (128 * 1024 * 1024) /* arbitrary */
/***
@ -35,50 +38,45 @@
* You can have negative numbers such as i-3e. You cannot prefix the
* number with a zero such as i04e. However, i0e is valid.
* Example: i3e represents the integer "3"
* NOTE: The maximum number of bit of this integer is unspecified,
*
* The maximum number of bit of this integer is unspecified,
* but to handle it as a signed 64bit integer is mandatory to handle
* "large files" aka .torrent for more that 4Gbyte
*/
int tr_bencParseInt(void const* vbuf, void const* vbufend, uint8_t const** setme_end, int64_t* setme_val)
std::optional<int64_t> tr_bencParseInt(std::string_view* benc)
{
uint8_t const* const buf = (uint8_t const*)vbuf;
uint8_t const* const bufend = (uint8_t const*)vbufend;
if (buf >= bufend)
// find the beginning delimiter
auto walk = *benc;
if (std::size(walk) < 3 || walk.front() != 'i')
{
return EILSEQ;
return {};
}
if (*buf != 'i')
// find the ending delimiter
walk.remove_prefix(1);
auto const pos = walk.find('e');
if (pos == walk.npos)
{
return EILSEQ;
return {};
}
void const* begin = buf + 1;
void const* end = memchr(begin, 'e', (bufend - buf) - 1);
if (end == nullptr)
// leading zeroes are not allowed
if ((walk[0] == '0' && isdigit(walk[1])) || (walk[0] == '-' && walk[1] == '0' && isdigit(walk[2])))
{
return EILSEQ;
return {};
}
errno = 0;
char* endptr = nullptr;
int64_t val = evutil_strtoll(static_cast<char const*>(begin), &endptr, 10);
if (errno != 0 || endptr != end) /* incomplete parse */
auto const value = std::strtoll(std::data(walk), &endptr, 10);
if (errno != 0 || endptr != std::data(walk) + pos)
{
return EILSEQ;
return {};
}
if (val != 0 && *(char const*)begin == '0') /* no leading zeroes! */
{
return EILSEQ;
}
*setme_end = (uint8_t const*)end + 1;
*setme_val = val;
return 0;
walk.remove_prefix(pos + 1);
*benc = walk;
return value;
}
/**
@ -87,46 +85,36 @@ int tr_bencParseInt(void const* vbuf, void const* vbufend, uint8_t const** setme
* Note that there is no constant beginning delimiter, and no ending delimiter.
* Example: 4:spam represents the string "spam"
*/
int tr_bencParseStr(
void const* vbuf,
void const* vbufend,
uint8_t const** setme_end,
uint8_t const** setme_str,
size_t* setme_strlen)
std::optional<std::string_view> tr_bencParseStr(std::string_view* benc)
{
uint8_t const* const buf = (uint8_t const*)vbuf;
uint8_t const* const bufend = (uint8_t const*)vbufend;
if ((buf < bufend) && isdigit(*buf))
// find the ':' delimiter
auto const colon_pos = benc->find(':');
if (colon_pos == benc->npos)
{
void const* end = memchr(buf, ':', bufend - buf);
if (end != nullptr)
{
errno = 0;
char* ulend = nullptr;
size_t len = strtoul((char const*)buf, &ulend, 10);
if (errno == 0 && ulend == end && len <= MAX_BENC_STR_LENGTH)
{
uint8_t const* strbegin = (uint8_t const*)end + 1;
uint8_t const* strend = strbegin + len;
if (strbegin <= strend && strend <= bufend)
{
*setme_end = (uint8_t const*)end + 1 + len;
*setme_str = (uint8_t const*)end + 1;
*setme_strlen = len;
return 0;
}
}
}
return {};
}
*setme_end = nullptr;
*setme_str = nullptr;
*setme_strlen = 0;
return EILSEQ;
// get the string length
errno = 0;
char* ulend = nullptr;
auto const len = strtoul(std::data(*benc), &ulend, 10);
if (errno != 0 || ulend != std::data(*benc) + colon_pos || len >= MAX_BENC_STR_LENGTH)
{
return {};
}
// do we have `len` bytes of string data?
auto walk = *benc;
walk.remove_prefix(colon_pos + 1);
if (std::size(walk) < len)
{
return {};
}
auto const string = walk.substr(0, len);
walk.remove_prefix(len);
*benc = walk;
return string;
}
static tr_variant* get_node(std::deque<tr_variant*>& stack, std::optional<tr_quark>& dict_key, tr_variant* top, int* err)
@ -164,24 +152,17 @@ static tr_variant* get_node(std::deque<tr_variant*>& stack, std::optional<tr_qua
* easier to read, but was vulnerable to a smash-stacking
* attack via maliciously-crafted bencoded data. (#667)
*/
int tr_variantParseBenc(void const* buf_in, void const* bufend_in, tr_variant* top, char const** setme_end)
int tr_variantParseBenc(tr_variant& top, std::string_view benc, char const** setme_end)
{
int err = 0;
auto const* buf = static_cast<uint8_t const*>(buf_in);
auto const* const bufend = static_cast<uint8_t const*>(bufend_in);
auto stack = std::deque<tr_variant*>{};
auto key = std::optional<tr_quark>{};
if ((buf_in == nullptr) || (bufend_in == nullptr) || (top == nullptr))
{
return EINVAL;
}
tr_variantInit(&top, 0);
tr_variantInit(top, 0);
while (buf != bufend)
int err = 0;
for (;;)
{
if (buf > bufend) /* no more text to parse... */
if (std::empty(benc))
{
err = EILSEQ;
}
@ -191,90 +172,94 @@ int tr_variantParseBenc(void const* buf_in, void const* bufend_in, tr_variant* t
break;
}
if (*buf == 'i') /* int */
switch (benc.front())
{
uint8_t const* end = nullptr;
auto val = int64_t{};
if ((err = tr_bencParseInt(buf, bufend, &end, &val)) != 0)
case 'i': // int
{
break;
}
auto const value = tr_bencParseInt(&benc);
if (!value)
{
break;
}
buf = end;
tr_variant* const v = get_node(stack, key, top, &err);
if (v != nullptr)
{
tr_variantInitInt(v, val);
}
}
else if (*buf == 'l') /* list */
{
++buf;
tr_variant* const v = get_node(stack, key, top, &err);
if (v != nullptr)
{
tr_variantInitList(v, 0);
stack.push_back(v);
}
}
else if (*buf == 'd') /* dict */
{
++buf;
tr_variant* const v = get_node(stack, key, top, &err);
if (v != nullptr)
{
tr_variantInitDict(v, 0);
stack.push_back(v);
}
}
else if (*buf == 'e') /* end of list or dict */
{
++buf;
if (std::empty(stack) || key)
{
err = EILSEQ;
break;
}
stack.pop_back();
if (std::empty(stack))
{
break;
}
}
else if (isdigit(*buf)) /* string? */
{
uint8_t const* end = nullptr;
uint8_t const* str = nullptr;
auto str_len = size_t{};
if ((err = tr_bencParseStr(buf, bufend, &end, &str, &str_len)) != 0)
{
break;
}
buf = end;
auto const sv = std::string_view{ reinterpret_cast<char const*>(str), str_len };
if (!key && !std::empty(stack) && tr_variantIsDict(stack.back()))
{
key = tr_quark_new(sv);
}
else
{
tr_variant* const v = get_node(stack, key, top, &err);
tr_variant* const v = get_node(stack, key, &top, &err);
if (v != nullptr)
{
tr_variantInitStr(v, sv);
tr_variantInitInt(v, *value);
}
break;
}
}
else /* invalid bencoded text... march past it */
{
++buf;
case 'l': // list
{
benc.remove_prefix(1);
tr_variant* const v = get_node(stack, key, &top, &err);
if (v != nullptr)
{
tr_variantInitList(v, 0);
stack.push_back(v);
}
break;
}
case 'd': // dict
{
benc.remove_prefix(1);
tr_variant* const v = get_node(stack, key, &top, &err);
if (v != nullptr)
{
tr_variantInitDict(v, 0);
stack.push_back(v);
}
break;
}
case 'e': // end of list or dict
{
benc.remove_prefix(1);
if (std::empty(stack) || key)
{
err = EILSEQ;
break;
}
stack.pop_back();
break;
}
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': // string?
{
auto const sv = tr_bencParseStr(&benc);
if (!sv)
{
break;
}
if (!key && !std::empty(stack) && tr_variantIsDict(stack.back()))
{
key = tr_quark_new(*sv);
}
else
{
tr_variant* const v = get_node(stack, key, &top, &err);
if (v != nullptr)
{
tr_variantInitStr(v, *sv);
}
}
break;
}
default: // invalid bencoded text... march past it
benc.remove_prefix(1);
break;
}
if (std::empty(stack))
@ -283,7 +268,7 @@ int tr_variantParseBenc(void const* buf_in, void const* bufend_in, tr_variant* t
}
}
if (err == 0 && (top->type == 0 || !std::empty(stack)))
if (err == 0 && (top.type == 0 || !std::empty(stack)))
{
err = EILSEQ;
}
@ -292,13 +277,13 @@ int tr_variantParseBenc(void const* buf_in, void const* bufend_in, tr_variant* t
{
if (setme_end != nullptr)
{
*setme_end = (char const*)buf;
*setme_end = std::data(benc);
}
}
else if (top->type != 0)
else if (top.type != 0)
{
tr_variantFree(top);
tr_variantInit(top, 0);
tr_variantFree(&top);
tr_variantInit(&top, 0);
}
return err;
@ -329,12 +314,12 @@ static void saveBoolFunc(tr_variant const* val, void* vevbuf)
static void saveRealFunc(tr_variant const* val, void* vevbuf)
{
char buf[128];
int const len = tr_snprintf(buf, sizeof(buf), "%f", val->val.d);
auto buf = std::array<char, 64>{};
int const len = tr_snprintf(std::data(buf), std::size(buf), "%f", val->val.d);
auto* evbuf = static_cast<struct evbuffer*>(vevbuf);
auto* evbuf = static_cast<evbuffer*>(vevbuf);
evbuffer_add_printf(evbuf, "%d:", len);
evbuffer_add(evbuf, buf, len);
evbuffer_add(evbuf, std::data(buf), len);
}
static void saveStringFunc(tr_variant const* v, void* vevbuf)

View File

@ -12,7 +12,10 @@
#error only libtransmission/variant-*.c should #include this header.
#endif
#include "tr-macros.h"
#include <optional>
#include <string_view>
struct tr_variant;
using VariantWalkFunc = void (*)(tr_variant const* val, void* user_data);
@ -35,18 +38,12 @@ void tr_variantToBufBenc(tr_variant const* top, struct evbuffer* buf);
void tr_variantInit(tr_variant* v, char type);
/* source - such as a filename. Only when logging an error */
int tr_jsonParse(char const* source, void const* vbuf, size_t len, tr_variant* setme_benc, char const** setme_end);
/** @brief Private function that's exposed here only for unit tests */
std::optional<int64_t> tr_bencParseInt(std::string_view* benc_inout);
/** @brief Private function that's exposed here only for unit tests */
int tr_bencParseInt(void const* buf, void const* bufend, uint8_t const** setme_end, int64_t* setme_val);
std::optional<std::string_view> tr_bencParseStr(std::string_view* benc_inout);
/** @brief Private function that's exposed here only for unit tests */
int tr_bencParseStr(
void const* buf,
void const* bufend,
uint8_t const** setme_end,
uint8_t const** setme_str,
size_t* setme_strlen);
int tr_variantParseBenc(tr_variant& setme, std::string_view benc, char const** setme_end);
int tr_variantParseBenc(void const* buf, void const* end, tr_variant* top, char const** setme_end);
int tr_variantParseJson(tr_variant& setme, std::string_view benc, char const** setme_end);

View File

@ -15,7 +15,6 @@
#include <cstring>
#include <event2/buffer.h> /* evbuffer_add() */
#include <event2/util.h> /* evutil_strtoll() */
#define LIBTRANSMISSION_VARIANT_MODULE
@ -40,7 +39,6 @@ struct json_wrapper_data
size_t keylen;
struct evbuffer* keybuf;
struct evbuffer* strbuf;
char const* source;
std::deque<tr_variant*> stack;
/* A very common pattern is for a container's children to be similar,
@ -80,19 +78,7 @@ static void error_handler(jsonsl_t jsn, jsonsl_error_t error, jsonsl_state_st* /
{
auto* data = static_cast<struct json_wrapper_data*>(jsn->data);
if (data->source != nullptr)
{
tr_logAddError(
"JSON parse failed in %s at pos %zu: %s -- remaining text \"%.16s\"",
data->source,
jsn->pos,
jsonsl_strerror(error),
buf);
}
else
{
tr_logAddError("JSON parse failed at pos %zu: %s -- remaining text \"%.16s\"", jsn->pos, jsonsl_strerror(error), buf);
}
tr_logAddError("JSON parse failed at pos %zu: %s -- remaining text \"%.16s\"", jsn->pos, jsonsl_strerror(error), buf);
data->error = EILSEQ;
}
@ -334,7 +320,7 @@ static void action_callback_POP(
{
char const* begin = jsn->base + state->pos_begin;
data->has_content = true;
tr_variantInitInt(get_node(jsn), evutil_strtoll(begin, nullptr, 10));
tr_variantInitInt(get_node(jsn), std::strtoll(begin, nullptr, 10));
}
else if ((state->special_flags & JSONSL_SPECIALf_BOOLEAN) != 0)
{
@ -350,7 +336,7 @@ static void action_callback_POP(
}
}
int tr_jsonParse(char const* source, void const* vbuf, size_t len, tr_variant* setme_variant, char const** setme_end)
int tr_variantParseJson(tr_variant& setme, std::string_view benc, char const** setme_end)
{
auto data = json_wrapper_data{};
@ -364,15 +350,14 @@ int tr_jsonParse(char const* source, void const* vbuf, size_t len, tr_variant* s
data.error = 0;
data.has_content = false;
data.key = nullptr;
data.top = setme_variant;
data.top = &setme;
data.stack = {};
data.source = source;
data.keybuf = evbuffer_new();
data.strbuf = evbuffer_new();
data.preallocGuess = {};
/* parse it */
jsonsl_feed(jsn, static_cast<jsonsl_char_t const*>(vbuf), len);
jsonsl_feed(jsn, static_cast<jsonsl_char_t const*>(std::data(benc)), std::size(benc));
/* EINVAL if there was no content */
if (data.error == 0 && !data.has_content)
@ -383,7 +368,7 @@ int tr_jsonParse(char const* source, void const* vbuf, size_t len, tr_variant* s
/* maybe set the end ptr */
if (setme_end != nullptr)
{
*setme_end = ((char const*)vbuf) + jsn->pos;
*setme_end = std::data(benc) + jsn->pos;
}
/* cleanup */
@ -643,14 +628,10 @@ static void jsonListBeginFunc(tr_variant const* val, void* vdata)
static void jsonContainerEndFunc(tr_variant const* val, void* vdata)
{
auto* data = static_cast<struct jsonWalk*>(vdata);
bool emptyContainer = false;
jsonPopParent(data);
if (!emptyContainer)
{
jsonIndent(data);
}
jsonIndent(data);
if (tr_variantIsDict(val))
{

View File

@ -1292,13 +1292,7 @@ int tr_variantToFile(tr_variant const* v, tr_variant_fmt fmt, char const* filena
****
***/
static int tr_variantFromBuf(
tr_variant* setme,
tr_variant_fmt fmt,
void const* buf,
size_t buflen,
char const* optional_source,
char const** setme_end)
static int tr_variantFromBuf(tr_variant& setme, tr_variant_fmt fmt, std::string_view buf, char const** setme_end)
{
/* parse with LC_NUMERIC="C" to ensure a "." decimal separator */
struct locale_context locale_ctx;
@ -1309,11 +1303,11 @@ static int tr_variantFromBuf(
{
case TR_VARIANT_FMT_JSON:
case TR_VARIANT_FMT_JSON_LEAN:
err = tr_jsonParse(optional_source, buf, buflen, setme, setme_end);
err = tr_variantParseJson(setme, buf, setme_end);
break;
default /* TR_VARIANT_FMT_BENC */:
err = tr_variantParseBenc(buf, (char const*)buf + buflen, setme, setme_end);
err = tr_variantParseBenc(setme, buf, setme_end);
break;
}
@ -1322,40 +1316,31 @@ static int tr_variantFromBuf(
return err;
}
int tr_variantFromBenc(tr_variant* setme, std::string_view benc)
int tr_variantFromBenc(tr_variant* setme, std::string_view benc, char const** setme_end)
{
return tr_variantFromBuf(setme, TR_VARIANT_FMT_BENC, std::data(benc), std::size(benc), nullptr, nullptr);
}
int tr_variantFromBencFull(tr_variant* setme, std::string_view benc, char const** setme_end)
{
return tr_variantFromBuf(setme, TR_VARIANT_FMT_BENC, std::data(benc), std::size(benc), nullptr, setme_end);
return tr_variantFromBuf(*setme, TR_VARIANT_FMT_BENC, benc, setme_end);
}
int tr_variantFromJson(tr_variant* setme, std::string_view json)
{
return tr_variantFromBuf(setme, TR_VARIANT_FMT_JSON, std::data(json), std::size(json), nullptr, nullptr);
return tr_variantFromBuf(*setme, TR_VARIANT_FMT_JSON, json, nullptr);
}
bool tr_variantFromFile(tr_variant* setme, tr_variant_fmt fmt, char const* filename, tr_error** error)
{
bool ret = false;
auto buflen = size_t{};
uint8_t* const buf = tr_loadFile(filename, &buflen, error);
if (buf != nullptr)
auto buf = std::vector<char>{};
if (!tr_loadFile(buf, filename, error))
{
if (tr_variantFromBuf(setme, fmt, buf, buflen, filename, nullptr) == 0)
{
ret = true;
}
else
{
tr_error_set_literal(error, 0, _("Unable to parse file content"));
}
tr_free(buf);
return false;
}
return ret;
auto sv = std::string_view{ std::data(buf), std::size(buf) };
auto const error_code = tr_variantFromBuf(*setme, fmt, sv, nullptr);
if (error_code != 0)
{
tr_error_set_literal(error, error_code, _("Unable to parse file content"));
return false;
}
return true;
}

View File

@ -115,9 +115,7 @@ struct evbuffer* tr_variantToBuf(tr_variant const* variant, tr_variant_fmt fmt);
/* TR_VARIANT_FMT_JSON_LEAN and TR_VARIANT_FMT_JSON are equivalent here. */
bool tr_variantFromFile(tr_variant* setme, tr_variant_fmt fmt, char const* filename, struct tr_error** error);
int tr_variantFromBenc(tr_variant* setme, std::string_view benc);
int tr_variantFromBencFull(tr_variant* setme, std::string_view benc, char const** setme_end);
int tr_variantFromBenc(tr_variant* setme, std::string_view benc, char const** setme_end = nullptr);
int tr_variantFromJson(tr_variant* setme, std::string_view json);

View File

@ -34,11 +34,6 @@ protected:
s.erase(std::find_if_not(s.rbegin(), s.rend(), ::isspace).base(), s.end());
return s;
}
auto bencParseInt(std::string const& in, uint8_t const** end, int64_t* val)
{
return tr_bencParseInt(in.data(), in.data() + in.size(), end, val);
}
};
#ifndef _WIN32
@ -94,150 +89,120 @@ TEST_F(VariantTest, getType)
TEST_F(VariantTest, parseInt)
{
auto const in = std::string{ "i64e" };
auto constexpr InitVal = int64_t{ 888 };
auto constexpr Benc = "i64e"sv;
auto constexpr ExpectVal = int64_t{ 64 };
uint8_t const* end = {};
auto val = int64_t{ InitVal };
auto const err = bencParseInt(in, &end, &val);
EXPECT_EQ(0, err);
EXPECT_EQ(ExpectVal, val);
EXPECT_EQ(reinterpret_cast<decltype(end)>(in.data() + in.size()), end);
auto benc = Benc;
auto const value = tr_bencParseInt(&benc);
EXPECT_TRUE(value);
EXPECT_EQ(ExpectVal, *value);
EXPECT_EQ(std::data(Benc) + std::size(Benc), std::data(benc));
}
TEST_F(VariantTest, parseIntWithMissingEnd)
{
auto const in = std::string{ "i64" };
auto constexpr InitVal = int64_t{ 888 };
auto constexpr Benc = "i64"sv;
uint8_t const* end = {};
auto val = int64_t{ InitVal };
auto const err = bencParseInt(in, &end, &val);
EXPECT_EQ(EILSEQ, err);
EXPECT_EQ(InitVal, val);
EXPECT_EQ(nullptr, end);
auto benc = Benc;
EXPECT_FALSE(tr_bencParseInt(&benc));
EXPECT_EQ(std::data(Benc), std::data(benc));
}
TEST_F(VariantTest, parseIntEmptyBuffer)
{
auto const in = std::string{};
auto constexpr InitVal = int64_t{ 888 };
auto constexpr Benc = ""sv;
uint8_t const* end = {};
auto val = int64_t{ InitVal };
auto const err = bencParseInt(in, &end, &val);
EXPECT_EQ(EILSEQ, err);
EXPECT_EQ(InitVal, val);
EXPECT_EQ(nullptr, end);
auto benc = Benc;
EXPECT_FALSE(tr_bencParseInt(&benc));
EXPECT_EQ(std::data(Benc), std::data(benc));
}
TEST_F(VariantTest, parseIntWithBadDigits)
{
auto const in = std::string{ "i6z4e" };
auto constexpr InitVal = int64_t{ 888 };
auto constexpr Benc = "i6z4e"sv;
uint8_t const* end = {};
auto val = int64_t{ InitVal };
auto const err = bencParseInt(in, &end, &val);
EXPECT_EQ(EILSEQ, err);
EXPECT_EQ(InitVal, val);
EXPECT_EQ(nullptr, end);
auto benc = Benc;
EXPECT_FALSE(tr_bencParseInt(&benc));
EXPECT_EQ(std::data(Benc), std::data(benc));
}
TEST_F(VariantTest, parseNegativeInt)
{
auto const in = std::string{ "i-3e" };
auto constexpr Benc = "i-3e"sv;
auto constexpr Expected = int64_t{ -3 };
uint8_t const* end = {};
auto val = int64_t{};
auto const err = bencParseInt(in, &end, &val);
EXPECT_EQ(0, err);
EXPECT_EQ(-3, val);
EXPECT_EQ(reinterpret_cast<decltype(end)>(in.data() + in.size()), end);
auto benc = Benc;
auto const value = tr_bencParseInt(&benc);
EXPECT_TRUE(value);
EXPECT_EQ(Expected, *value);
EXPECT_EQ(std::data(Benc) + std::size(Benc), std::data(benc));
}
TEST_F(VariantTest, parseNegativeWithLeadingZero)
{
auto constexpr Benc = "i-03e"sv;
auto benc = Benc;
EXPECT_FALSE(tr_bencParseInt(&benc));
EXPECT_EQ(std::data(Benc), std::data(benc));
}
TEST_F(VariantTest, parseIntZero)
{
auto const in = std::string{ "i0e" };
auto constexpr Benc = "i0e"sv;
auto constexpr Expected = int64_t{ 0 };
uint8_t const* end = {};
auto val = int64_t{};
auto const err = bencParseInt(in, &end, &val);
EXPECT_EQ(0, err);
EXPECT_EQ(0, val);
EXPECT_EQ(reinterpret_cast<decltype(end)>(in.data() + in.size()), end);
auto benc = Benc;
auto const value = tr_bencParseInt(&benc);
EXPECT_TRUE(value);
EXPECT_EQ(Expected, *value);
EXPECT_EQ(std::data(Benc) + std::size(Benc), std::data(benc));
}
TEST_F(VariantTest, parseIntWithLeadingZero)
{
auto const in = std::string{ "i04e" };
auto constexpr InitVal = int64_t{ 888 };
auto constexpr Benc = "i04e"sv;
uint8_t const* end = {};
auto val = int64_t{ InitVal };
auto const err = bencParseInt(in, &end, &val);
EXPECT_EQ(EILSEQ, err); // no leading zeroes allowed
EXPECT_EQ(InitVal, val);
EXPECT_EQ(nullptr, end);
auto benc = Benc;
EXPECT_FALSE(tr_bencParseInt(&benc));
EXPECT_EQ(std::data(Benc), std::data(benc));
}
TEST_F(VariantTest, str)
{
auto buf = std::array<uint8_t, 128>{};
int err;
int n;
uint8_t const* end;
uint8_t const* str;
size_t len;
// string len is designed to overflow
n = tr_snprintf(buf.data(), buf.size(), "%zu:boat", size_t(SIZE_MAX - 2));
err = tr_bencParseStr(&buf[0], &buf[n], &end, &str, &len);
EXPECT_EQ(EILSEQ, err);
EXPECT_EQ(size_t{}, len);
EXPECT_EQ(nullptr, str);
EXPECT_EQ(nullptr, end);
auto benc = "99999999999999999999:boat"sv;
auto inout = benc;
auto value = tr_bencParseStr(&inout);
EXPECT_FALSE(value);
EXPECT_EQ(benc, inout);
// good string
n = tr_snprintf(buf.data(), buf.size(), "4:boat");
err = tr_bencParseStr(&buf[0], &buf[n], &end, &str, &len);
EXPECT_EQ(0, err);
EXPECT_EQ(size_t{ 4 }, len);
EXPECT_EQ(0, memcmp("boat", str, len));
EXPECT_EQ(buf.data() + n, end);
str = nullptr;
end = nullptr;
len = 0;
inout = benc = "4:boat";
value = tr_bencParseStr(&inout);
EXPECT_TRUE(value);
EXPECT_EQ("boat"sv, *value);
EXPECT_EQ(std::data(benc) + std::size(benc), std::data(inout));
// string goes past end of buffer
err = tr_bencParseStr(&buf[0], &buf[n - 1], &end, &str, &len);
EXPECT_EQ(EILSEQ, err);
EXPECT_EQ(size_t{}, len);
EXPECT_EQ(nullptr, str);
EXPECT_EQ(nullptr, end);
inout = benc = "4:boa"sv;
value = tr_bencParseStr(&inout);
EXPECT_FALSE(value);
EXPECT_EQ(benc, inout);
// empty string
n = tr_snprintf(buf.data(), buf.size(), "0:");
err = tr_bencParseStr(&buf[0], &buf[n], &end, &str, &len);
EXPECT_EQ(0, err);
EXPECT_EQ(size_t{}, len);
EXPECT_EQ('\0', *str);
EXPECT_EQ(buf.data() + n, end);
str = nullptr;
end = nullptr;
len = 0;
inout = benc = "0:"sv;
value = tr_bencParseStr(&inout);
EXPECT_TRUE(value);
EXPECT_EQ(""sv, *value);
EXPECT_EQ(std::data(benc) + std::size(benc), std::data(inout));
// short string
n = tr_snprintf(buf.data(), buf.size(), "3:boat");
err = tr_bencParseStr(&buf[0], &buf[n], &end, &str, &len);
EXPECT_EQ(0, err);
EXPECT_EQ(size_t{ 3 }, len);
EXPECT_EQ(0, memcmp("boa", str, len));
EXPECT_EQ(buf.data() + 5, end);
str = nullptr;
end = nullptr;
len = 0;
inout = benc = "3:boat";
value = tr_bencParseStr(&inout);
EXPECT_TRUE(value);
EXPECT_EQ("boa"sv, *value);
EXPECT_EQ(std::data(benc) + benc.find('t'), std::data(inout));
}
TEST_F(VariantTest, parse)
@ -246,7 +211,7 @@ TEST_F(VariantTest, parse)
auto i = int64_t{};
auto val = tr_variant{};
char const* end;
auto err = tr_variantFromBencFull(&val, benc, &end);
auto err = tr_variantFromBenc(&val, benc, &end);
EXPECT_EQ(0, err);
EXPECT_TRUE(tr_variantGetInt(&val, &i));
EXPECT_EQ(int64_t(64), i);
@ -254,7 +219,7 @@ TEST_F(VariantTest, parse)
tr_variantFree(&val);
benc = "li64ei32ei16ee"sv;
err = tr_variantFromBencFull(&val, benc, &end);
err = tr_variantFromBenc(&val, benc, &end);
EXPECT_EQ(0, err);
EXPECT_EQ(std::data(benc) + std::size(benc), end);
EXPECT_EQ(size_t{ 3 }, tr_variantListSize(&val));
@ -275,12 +240,12 @@ TEST_F(VariantTest, parse)
end = nullptr;
benc = "lllee"sv;
err = tr_variantFromBencFull(&val, benc, &end);
err = tr_variantFromBenc(&val, benc, &end);
EXPECT_NE(0, err);
EXPECT_EQ(nullptr, end);
benc = "le"sv;
err = tr_variantFromBencFull(&val, benc, &end);
err = tr_variantFromBenc(&val, benc, &end);
EXPECT_EQ(0, err);
EXPECT_EQ(std::data(benc) + std::size(benc), end);
@ -315,7 +280,7 @@ TEST_F(VariantTest, bencParseAndReencode)
{
tr_variant val;
char const* end = nullptr;
auto const err = tr_variantFromBencFull(&val, test.benc, &end);
auto const err = tr_variantFromBenc(&val, test.benc, &end);
if (!test.is_good)
{
EXPECT_NE(0, err);
@ -340,7 +305,7 @@ TEST_F(VariantTest, bencSortWhenSerializing)
tr_variant val;
char const* end;
auto const err = tr_variantFromBencFull(&val, In, &end);
auto const err = tr_variantFromBenc(&val, In, &end);
EXPECT_EQ(0, err);
EXPECT_EQ(std::data(In) + std::size(In), end);
@ -360,7 +325,7 @@ TEST_F(VariantTest, bencMalformedTooManyEndings)
tr_variant val;
char const* end;
auto const err = tr_variantFromBencFull(&val, In, &end);
auto const err = tr_variantFromBenc(&val, In, &end);
EXPECT_EQ(0, err);
EXPECT_EQ(std::data(In) + std::size(ExpectedOut), end);
@ -482,7 +447,7 @@ TEST_F(VariantTest, stackSmash)
// confirm that it parses
char const* end;
tr_variant val;
auto err = tr_variantFromBencFull(&val, in, &end);
auto err = tr_variantFromBenc(&val, in, &end);
EXPECT_EQ(0, err);
EXPECT_EQ(in.data() + in.size(), end);