refactor: add tr_variant_serde (#5903)

This commit is contained in:
Charles Kerr 2023-08-17 11:02:45 -05:00 committed by GitHub
parent 9df4adf9b6
commit a4d205612a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 587 additions and 643 deletions

View File

@ -168,7 +168,6 @@
A25964A6106D73A800453B31 /* announcer.cc in Sources */ = {isa = PBXBuildFile; fileRef = A25964A4106D73A800453B31 /* announcer.cc */; };
A25964A7106D73A800453B31 /* announcer.h in Headers */ = {isa = PBXBuildFile; fileRef = A25964A5106D73A800453B31 /* announcer.h */; };
A25BFD69167BED3B0039D1AA /* variant-benc.cc in Sources */ = {isa = PBXBuildFile; fileRef = A25BFD63167BED3B0039D1AA /* variant-benc.cc */; };
A25BFD6A167BED3B0039D1AA /* variant-common.h in Headers */ = {isa = PBXBuildFile; fileRef = A25BFD64167BED3B0039D1AA /* variant-common.h */; };
A25BFD6B167BED3B0039D1AA /* variant-json.cc in Sources */ = {isa = PBXBuildFile; fileRef = A25BFD65167BED3B0039D1AA /* variant-json.cc */; };
A25BFD6D167BED3B0039D1AA /* variant.cc in Sources */ = {isa = PBXBuildFile; fileRef = A25BFD67167BED3B0039D1AA /* variant.cc */; };
A25BFD6E167BED3B0039D1AA /* variant.h in Headers */ = {isa = PBXBuildFile; fileRef = A25BFD68167BED3B0039D1AA /* variant.h */; };
@ -927,7 +926,6 @@
A25964A4106D73A800453B31 /* announcer.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = announcer.cc; sourceTree = "<group>"; };
A25964A5106D73A800453B31 /* announcer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = announcer.h; sourceTree = "<group>"; };
A25BFD63167BED3B0039D1AA /* variant-benc.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "variant-benc.cc"; sourceTree = "<group>"; };
A25BFD64167BED3B0039D1AA /* variant-common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "variant-common.h"; sourceTree = "<group>"; };
A25BFD65167BED3B0039D1AA /* variant-json.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "variant-json.cc"; sourceTree = "<group>"; };
A25BFD67167BED3B0039D1AA /* variant.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = variant.cc; sourceTree = "<group>"; };
A25BFD68167BED3B0039D1AA /* variant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = variant.h; sourceTree = "<group>"; };
@ -1825,7 +1823,6 @@
BEFC1DF10C07861A00B0BB3C /* utils.h */,
C843FC8329C51B9400491854 /* utils.mm */,
A25BFD63167BED3B0039D1AA /* variant-benc.cc */,
A25BFD64167BED3B0039D1AA /* variant-common.h */,
A25BFD65167BED3B0039D1AA /* variant-json.cc */,
A25BFD67167BED3B0039D1AA /* variant.cc */,
A25BFD68167BED3B0039D1AA /* variant.h */,
@ -2334,7 +2331,6 @@
A23F29A1132A447400E9A83B /* announcer-common.h in Headers */,
A2EE726F14DCCC950093C99A /* port-forwarding-natpmp.h in Headers */,
A2D77451154CC25700A62B93 /* WebSeedTableView.h in Headers */,
A25BFD6A167BED3B0039D1AA /* variant-common.h in Headers */,
A25BFD6E167BED3B0039D1AA /* variant.h in Headers */,
A2EA52321686AC0D00180493 /* quark.h in Headers */,
A2AF23C916B44FA0003BC59E /* log.h in Headers */,

View File

@ -935,8 +935,7 @@ bool tr_daemon::init(int argc, char const* const argv[], bool* foreground, int*
if (dumpSettings)
{
auto const str = tr_variantToStr(&settings_, TR_VARIANT_FMT_JSON);
fprintf(stderr, "%s", str.c_str());
fmt::print("{:s}\n", tr_variant_serde::json().to_string(settings_));
goto EXIT_EARLY;
}

View File

@ -481,15 +481,14 @@ bool Application::Impl::on_rpc_changed_idle(tr_rpc_callback_type type, tr_torren
tr_variantInitDict(&tmp, 100);
tr_sessionGetSettings(session, &tmp);
auto const serde = tr_variant_serde::benc();
for (int i = 0; tr_variantDictChild(&tmp, i, &key, &newval); ++i)
{
bool changed = true;
if (tr_variant const* oldval = tr_variantDictFind(oldvals, key); oldval != nullptr)
{
auto const a = tr_variantToStr(oldval, TR_VARIANT_FMT_BENC);
auto const b = tr_variantToStr(newval, TR_VARIANT_FMT_BENC);
changed = a != b;
changed = serde.to_string(*oldval) != serde.to_string(*newval);
}
if (changed)

View File

@ -159,7 +159,6 @@ target_sources(${TR_NAME}
utils.h
utils.mm
variant-benc.cc
variant-common.h
variant-converters.cc
variant-json.cc
variant.cc

View File

@ -12,6 +12,7 @@
#include "libtransmission/transmission.h"
#include "libtransmission/announce-list.h"
#include "libtransmission/error.h"
#include "libtransmission/quark.h"
#include "libtransmission/torrent-metainfo.h"
#include "libtransmission/utils.h"
@ -236,11 +237,14 @@ bool tr_announce_list::can_add(tr_url_parsed_t const& announce) const noexcept
bool tr_announce_list::save(std::string_view torrent_file, tr_error** error) const
{
// load the torrent file
auto metainfo = tr_variant{};
if (!tr_variantFromFile(&metainfo, TR_VARIANT_PARSE_BENC, torrent_file, error))
auto serde = tr_variant_serde::benc();
auto ometainfo = serde.parse_file(torrent_file);
if (!ometainfo)
{
tr_error_propagate(error, &serde.error_);
return false;
}
auto& metainfo = *ometainfo;
// remove the old fields
tr_variantDictRemove(&metainfo, TR_KEY_announce);
@ -271,9 +275,13 @@ bool tr_announce_list::save(std::string_view torrent_file, tr_error** error) con
}
// confirm that it's good by parsing it back again
auto const contents = tr_variantToStr(&metainfo, TR_VARIANT_FMT_BENC);
auto const contents = serde.to_string(metainfo);
tr_variantClear(&metainfo);
if (auto tm = tr_torrent_metainfo{}; !tm.parse_benc(contents, error))
if (auto tmp = serde.parse(contents); tmp)
{
tr_variantClear(&*tmp);
}
else
{
return false;
}

View File

@ -384,6 +384,11 @@ bool parse(
}
}
if (setme_end != nullptr)
{
*setme_end = std::data(benc);
}
if (err != 0)
{
errno = err;
@ -406,11 +411,6 @@ bool parse(
return false;
}
if (setme_end != nullptr)
{
*setme_end = std::data(benc);
}
return true;
}

View File

@ -377,7 +377,7 @@ std::string tr_metainfo_builder::benc(tr_error** error) const
tr_variantDictAddStr(info_dict, TR_KEY_source, source_);
}
auto ret = tr_variantToStr(&top, TR_VARIANT_FMT_BENC);
auto ret = tr_variant_serde::benc().to_string(top);
tr_variantClear(&top);
return ret;
}

View File

@ -1023,7 +1023,7 @@ void sendLtepHandshake(tr_peerMsgsImpl* msgs)
}
}
protocol_send_message(msgs, BtPeerMsgs::Ltep, LtepMessages::Handshake, tr_variantToStr(&val, TR_VARIANT_FMT_BENC));
protocol_send_message(msgs, BtPeerMsgs::Ltep, LtepMessages::Handshake, tr_variant_serde::benc().to_string(val));
/* cleanup */
tr_variantClear(&val);
@ -1033,8 +1033,8 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
{
auto const handshake_sv = payload.to_string_view();
auto val = tr_variant{};
if (!tr_variantFromBuf(&val, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, handshake_sv) || !tr_variantIsDict(&val))
auto var = tr_variant_serde::benc().inplace().parse(handshake_sv);
if (!var || !tr_variantIsDict(&*var))
{
logtrace(msgs, "GET extended-handshake, couldn't get dictionary");
return;
@ -1045,7 +1045,7 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
/* does the peer prefer encrypted connections? */
auto i = int64_t{};
auto pex = tr_pex{};
if (tr_variantDictFindInt(&val, TR_KEY_e, &i))
if (tr_variantDictFindInt(&*var, TR_KEY_e, &i))
{
msgs->encryption_preference = i != 0 ? EncryptionPreference::Yes : EncryptionPreference::No;
@ -1059,7 +1059,7 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
msgs->peerSupportsPex = false;
msgs->peerSupportsMetadataXfer = false;
if (tr_variant* sub = nullptr; tr_variantDictFindDict(&val, TR_KEY_m, &sub))
if (tr_variant* sub = nullptr; tr_variantDictFindDict(&*var, TR_KEY_m, &sub))
{
if (tr_variantDictFindInt(sub, TR_KEY_ut_pex, &i))
{
@ -1085,13 +1085,13 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
}
/* look for metainfo size (BEP 9) */
if (tr_variantDictFindInt(&val, TR_KEY_metadata_size, &i) && tr_torrentSetMetadataSizeHint(msgs->torrent, i))
if (tr_variantDictFindInt(&*var, TR_KEY_metadata_size, &i) && tr_torrentSetMetadataSizeHint(msgs->torrent, i))
{
msgs->metadata_size_hint = i;
}
/* look for upload_only (BEP 21) */
if (tr_variantDictFindInt(&val, TR_KEY_upload_only, &i))
if (tr_variantDictFindInt(&*var, TR_KEY_upload_only, &i))
{
pex.flags |= ADDED_F_SEED_FLAG;
}
@ -1100,13 +1100,13 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
// Client name and version (as a utf-8 string). This is a much more
// reliable way of identifying the client than relying on the
// peer id encoding.
if (auto sv = std::string_view{}; tr_variantDictFindStrView(&val, TR_KEY_v, &sv))
if (auto sv = std::string_view{}; tr_variantDictFindStrView(&*var, TR_KEY_v, &sv))
{
msgs->set_user_agent(tr_interned_string{ sv });
}
/* get peer's listening port */
if (tr_variantDictFindInt(&val, TR_KEY_p, &i) && i > 0)
if (tr_variantDictFindInt(&*var, TR_KEY_p, &i) && i > 0)
{
pex.port.setHost(i);
msgs->publish(tr_peer_event::GotPort(pex.port));
@ -1115,14 +1115,14 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
uint8_t const* addr = nullptr;
auto addr_len = size_t{};
if (msgs->io->is_incoming() && tr_variantDictFindRaw(&val, TR_KEY_ipv4, &addr, &addr_len) && addr_len == 4)
if (msgs->io->is_incoming() && tr_variantDictFindRaw(&*var, TR_KEY_ipv4, &addr, &addr_len) && addr_len == 4)
{
pex.addr.type = TR_AF_INET;
memcpy(&pex.addr.addr.addr4, addr, 4);
tr_peerMgrAddPex(msgs->torrent, TR_PEER_FROM_LTEP, &pex, 1);
}
if (msgs->io->is_incoming() && tr_variantDictFindRaw(&val, TR_KEY_ipv6, &addr, &addr_len) && addr_len == 16)
if (msgs->io->is_incoming() && tr_variantDictFindRaw(&*var, TR_KEY_ipv6, &addr, &addr_len) && addr_len == 16)
{
pex.addr.type = TR_AF_INET6;
memcpy(&pex.addr.addr.addr6, addr, 16);
@ -1130,12 +1130,12 @@ void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload)
}
/* get peer's maximum request queue size */
if (tr_variantDictFindInt(&val, TR_KEY_reqq, &i))
if (tr_variantDictFindInt(&*var, TR_KEY_reqq, &i))
{
msgs->reqq = i;
}
tr_variantClear(&val);
tr_variantClear(&*var);
}
void parseUtMetadata(tr_peerMsgsImpl* msgs, MessageReader& payload_in)
@ -1147,14 +1147,13 @@ void parseUtMetadata(tr_peerMsgsImpl* msgs, MessageReader& payload_in)
auto const tmp = payload_in.to_string_view();
auto const* const msg_end = std::data(tmp) + std::size(tmp);
auto dict = tr_variant{};
char const* benc_end = nullptr;
if (tr_variantFromBuf(&dict, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, tmp, &benc_end))
auto serde = tr_variant_serde::benc();
if (auto var = serde.inplace().parse(tmp); var)
{
(void)tr_variantDictFindInt(&dict, TR_KEY_msg_type, &msg_type);
(void)tr_variantDictFindInt(&dict, TR_KEY_piece, &piece);
(void)tr_variantDictFindInt(&dict, TR_KEY_total_size, &total_size);
tr_variantClear(&dict);
(void)tr_variantDictFindInt(&*var, TR_KEY_msg_type, &msg_type);
(void)tr_variantDictFindInt(&*var, TR_KEY_piece, &piece);
(void)tr_variantDictFindInt(&*var, TR_KEY_total_size, &total_size);
tr_variantClear(&*var);
}
logtrace(
@ -1166,6 +1165,8 @@ void parseUtMetadata(tr_peerMsgsImpl* msgs, MessageReader& payload_in)
/* NOOP */
}
auto const* const benc_end = serde.end();
if (msg_type == MetadataMsgType::Data && total_size == msgs->metadata_size_hint && !msgs->torrent->has_metainfo() &&
msg_end - benc_end <= MetadataPieceSize && piece * MetadataPieceSize + (msg_end - benc_end) <= total_size)
{
@ -1187,7 +1188,7 @@ void parseUtMetadata(tr_peerMsgsImpl* msgs, MessageReader& payload_in)
tr_variantInitDict(&v, 2);
tr_variantDictAddInt(&v, TR_KEY_msg_type, MetadataMsgType::Reject);
tr_variantDictAddInt(&v, TR_KEY_piece, piece);
protocol_send_message(msgs, BtPeerMsgs::Ltep, msgs->ut_metadata_id, tr_variantToStr(&v, TR_VARIANT_FMT_BENC));
protocol_send_message(msgs, BtPeerMsgs::Ltep, msgs->ut_metadata_id, serde.to_string(v));
tr_variantClear(&v);
}
}
@ -1201,17 +1202,15 @@ void parseUtPex(tr_peerMsgsImpl* msgs, MessageReader& payload)
return;
}
auto const tmp = payload.to_string_view();
if (tr_variant val; tr_variantFromBuf(&val, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, tmp))
if (auto var = tr_variant_serde::benc().inplace().parse(payload.to_string_view()); var)
{
uint8_t const* added = nullptr;
auto added_len = size_t{};
if (tr_variantDictFindRaw(&val, TR_KEY_added, &added, &added_len))
if (tr_variantDictFindRaw(&*var, TR_KEY_added, &added, &added_len))
{
uint8_t const* added_f = nullptr;
auto added_f_len = size_t{};
if (!tr_variantDictFindRaw(&val, TR_KEY_added_f, &added_f, &added_f_len))
if (!tr_variantDictFindRaw(&*var, TR_KEY_added_f, &added_f, &added_f_len))
{
added_f_len = 0;
added_f = nullptr;
@ -1222,11 +1221,11 @@ void parseUtPex(tr_peerMsgsImpl* msgs, MessageReader& payload)
tr_peerMgrAddPex(tor, TR_PEER_FROM_PEX, std::data(pex), std::size(pex));
}
if (tr_variantDictFindRaw(&val, TR_KEY_added6, &added, &added_len))
if (tr_variantDictFindRaw(&*var, TR_KEY_added6, &added, &added_len))
{
uint8_t const* added_f = nullptr;
auto added_f_len = size_t{};
if (!tr_variantDictFindRaw(&val, TR_KEY_added6_f, &added_f, &added_f_len))
if (!tr_variantDictFindRaw(&*var, TR_KEY_added6_f, &added_f, &added_f_len))
{
added_f_len = 0;
added_f = nullptr;
@ -1237,7 +1236,7 @@ void parseUtPex(tr_peerMsgsImpl* msgs, MessageReader& payload)
tr_peerMgrAddPex(tor, TR_PEER_FROM_PEX, std::data(pex), std::size(pex));
}
tr_variantClear(&val);
tr_variantClear(&*var);
}
}
@ -1812,7 +1811,7 @@ void updateMetadataRequests(tr_peerMsgsImpl* msgs, time_t now)
tr_variantInitDict(&tmp, 3);
tr_variantDictAddInt(&tmp, TR_KEY_msg_type, MetadataMsgType::Request);
tr_variantDictAddInt(&tmp, TR_KEY_piece, *piece);
protocol_send_message(msgs, BtPeerMsgs::Ltep, msgs->ut_metadata_id, tr_variantToStr(&tmp, TR_VARIANT_FMT_BENC));
protocol_send_message(msgs, BtPeerMsgs::Ltep, msgs->ut_metadata_id, tr_variant_serde::benc().to_string(tmp));
tr_variantClear(&tmp);
}
}
@ -1870,7 +1869,7 @@ namespace peer_pulse_helpers
msgs,
BtPeerMsgs::Ltep,
msgs->ut_metadata_id,
tr_variantToStr(&tmp, TR_VARIANT_FMT_BENC));
tr_variant_serde::benc().to_string(tmp));
tr_variantClear(&tmp);
return n_bytes_written;
}
@ -1885,7 +1884,7 @@ namespace peer_pulse_helpers
msgs,
BtPeerMsgs::Ltep,
msgs->ut_metadata_id,
tr_variantToStr(&tmp, TR_VARIANT_FMT_BENC),
tr_variant_serde::benc().to_string(tmp),
data);
tr_variantClear(&tmp);
return n_bytes_written;
@ -2114,7 +2113,7 @@ void tr_peerMsgsImpl::sendPex()
}
}
protocol_send_message(this, BtPeerMsgs::Ltep, this->ut_pex_id, tr_variantToStr(&val, TR_VARIANT_FMT_BENC));
protocol_send_message(this, BtPeerMsgs::Ltep, this->ut_pex_id, tr_variant_serde::benc().to_string(val));
tr_variantClear(&val);
}

View File

@ -619,10 +619,8 @@ auto loadProgress(tr_variant* dict, tr_torrent* tor)
// ---
auto loadFromFile(tr_torrent* tor, tr_resume::fields_t fields_to_load)
tr_resume::fields_t loadFromFile(tr_torrent* tor, tr_resume::fields_t fields_to_load)
{
auto fields_loaded = tr_resume::fields_t{};
TR_ASSERT(tr_isTorrent(tor));
auto const was_dirty = tor->is_dirty();
@ -631,22 +629,20 @@ auto loadFromFile(tr_torrent* tor, tr_resume::fields_t fields_to_load)
auto const filename = tor->resume_file();
if (!tr_sys_path_exists(filename))
{
return fields_loaded;
return {};
}
auto buf = std::vector<char>{};
tr_error* error = nullptr;
auto top = tr_variant{};
if (!tr_file_read(filename, buf, &error) ||
!tr_variantFromBuf(&top, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, buf, nullptr, &error))
auto serde = tr_variant_serde::benc();
auto otop = serde.parse_file(filename);
if (!otop)
{
tr_logAddDebugTor(tor, fmt::format("Couldn't read '{}': {}", filename, error->message));
tr_error_clear(&error);
return fields_loaded;
tr_logAddDebugTor(tor, fmt::format("Couldn't read '{}': {}", filename, serde.error_->message));
return {};
}
auto& top = *otop;
tr_logAddDebugTor(tor, fmt::format("Read resume file '{}'", filename));
auto fields_loaded = tr_resume::fields_t{};
auto i = int64_t{};
auto sv = std::string_view{};
@ -916,10 +912,10 @@ void save(tr_torrent* tor)
saveLabels(&top, tor);
saveGroup(&top, tor);
auto const resume_file = tor->resume_file();
if (auto const err = tr_variantToFile(&top, TR_VARIANT_FMT_BENC, resume_file); err != 0)
auto serde = tr_variant_serde::benc();
if (!serde.to_file(top, tor->resume_file()))
{
tor->set_local_error(fmt::format("Unable to save resume file: {:s}", tr_strerror(err)));
tor->set_local_error(fmt::format("Unable to save resume file: {:s}", serde.error_->message));
}
tr_variantClear(&top);

View File

@ -351,7 +351,7 @@ void rpc_response_func(tr_session* /*session*/, tr_variant* content, void* user_
{
auto* data = static_cast<struct rpc_response_data*>(user_data);
auto* const response = make_response(data->req, data->server, tr_variantToStr(content, TR_VARIANT_FMT_JSON_LEAN));
auto* const response = make_response(data->req, data->server, tr_variant_serde::json().compact().to_string(*content));
evhttp_add_header(data->req->output_headers, "Content-Type", "application/json; charset=UTF-8");
evhttp_send_reply(data->req, HTTP_OK, "OK", response);
evbuffer_free(response);
@ -361,18 +361,13 @@ void rpc_response_func(tr_session* /*session*/, tr_variant* content, void* user_
void handle_rpc_from_json(struct evhttp_request* req, tr_rpc_server* server, std::string_view json)
{
auto top = tr_variant{};
auto const have_content = tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, json);
auto otop = tr_variant_serde::json().inplace().parse(json);
tr_rpc_request_exec_json(
server->session,
have_content ? &top : nullptr,
rpc_response_func,
new rpc_response_data{ req, server });
tr_rpc_request_exec_json(server->session, otop ? &*otop : nullptr, rpc_response_func, new rpc_response_data{ req, server });
if (have_content)
if (otop)
{
tr_variantClear(&top);
tr_variantClear(&*otop);
}
}

View File

@ -71,9 +71,13 @@ auto constexpr BandwidthGroupsFilename = "bandwidth-groups.json"sv;
void bandwidthGroupRead(tr_session* session, std::string_view config_dir)
{
auto const filename = tr_pathbuf{ config_dir, '/', BandwidthGroupsFilename };
auto groups_dict = tr_variant{};
if (!tr_sys_path_exists(filename) || !tr_variantFromFile(&groups_dict, TR_VARIANT_PARSE_JSON, filename, nullptr) ||
!tr_variantIsDict(&groups_dict))
if (!tr_sys_path_exists(filename))
{
return;
}
auto groups_var = tr_variant_serde::json().parse_file(filename);
if (!groups_var)
{
return;
}
@ -81,7 +85,7 @@ void bandwidthGroupRead(tr_session* session, std::string_view config_dir)
auto idx = size_t{ 0 };
auto key = tr_quark{};
tr_variant* dict = nullptr;
while (tr_variantDictChild(&groups_dict, idx, &key, &dict))
while (tr_variantDictChild(&*groups_var, idx, &key, &dict))
{
++idx;
@ -110,10 +114,10 @@ void bandwidthGroupRead(tr_session* session, std::string_view config_dir)
group.honor_parent_limits(TR_DOWN, honors);
}
}
tr_variantClear(&groups_dict);
tr_variantClear(&*groups_var);
}
int bandwidthGroupWrite(tr_session const* session, std::string_view config_dir)
void bandwidthGroupWrite(tr_session const* session, std::string_view config_dir)
{
auto const& groups = session->bandwidthGroups();
@ -134,9 +138,8 @@ int bandwidthGroupWrite(tr_session const* session, std::string_view config_dir)
}
auto const filename = tr_pathbuf{ config_dir, '/', BandwidthGroupsFilename };
auto const ret = tr_variantToFile(&groups_dict, TR_VARIANT_FMT_JSON, filename);
tr_variant_serde::json().to_file(groups_dict, filename);
tr_variantClear(&groups_dict);
return ret;
}
} // namespace bandwidth_group_helpers
@ -501,10 +504,10 @@ bool tr_sessionLoadSettings(tr_variant* dict, char const* config_dir, char const
{
success = true;
}
else if (auto file_settings = tr_variant{}; tr_variantFromFile(&file_settings, TR_VARIANT_PARSE_JSON, filename))
else if (auto file_settings = tr_variant_serde::json().parse_file(filename); file_settings)
{
tr_variantMergeDicts(dict, &file_settings);
tr_variantClear(&file_settings);
tr_variantMergeDicts(dict, &*file_settings);
tr_variantClear(&*file_settings);
success = true;
}
else
@ -528,10 +531,10 @@ void tr_sessionSaveSettings(tr_session* session, char const* config_dir, tr_vari
tr_variantInitDict(&settings, 0);
/* the existing file settings are the fallback values */
if (auto file_settings = tr_variant{}; tr_variantFromFile(&file_settings, TR_VARIANT_PARSE_JSON, filename))
if (auto file_settings = tr_variant_serde::json().parse_file(filename); file_settings)
{
tr_variantMergeDicts(&settings, &file_settings);
tr_variantClear(&file_settings);
tr_variantMergeDicts(&settings, &*file_settings);
tr_variantClear(&*file_settings);
}
/* the client's settings override the file settings */
@ -547,7 +550,7 @@ void tr_sessionSaveSettings(tr_session* session, char const* config_dir, tr_vari
}
/* save the result */
tr_variantToFile(&settings, TR_VARIANT_FMT_JSON, filename);
tr_variant_serde::json().to_file(settings, filename);
/* cleanup */
tr_variantClear(&settings);

View File

@ -14,52 +14,49 @@
using namespace std::literals;
namespace
{
std::optional<tr_variant> load_stats(std::string_view config_dir)
{
if (auto filename = tr_pathbuf{ config_dir, "/stats.json"sv }; tr_sys_path_exists(filename))
{
return tr_variant_serde::json().parse_file(filename);
}
// maybe the user just upgraded from an old version of Transmission
// that was still using stats.benc
if (auto filename = tr_pathbuf{ config_dir, "/stats.benc"sv }; tr_sys_path_exists(filename))
{
return tr_variant_serde::benc().parse_file(filename);
}
return {};
}
} // namespace
tr_session_stats tr_stats::load_old_stats(std::string_view config_dir)
{
auto ret = tr_session_stats{};
auto top = tr_variant{};
auto filename = tr_pathbuf{ config_dir, "/stats.json"sv };
bool loaded = tr_sys_path_exists(filename) && tr_variantFromFile(&top, TR_VARIANT_PARSE_JSON, filename.sv(), nullptr);
if (!loaded)
if (auto stats = load_stats(config_dir); stats)
{
// maybe the user just upgraded from an old version of Transmission
// that was still using stats.benc
filename.assign(config_dir, "/stats.benc");
loaded = tr_sys_path_exists(filename) && tr_variantFromFile(&top, TR_VARIANT_PARSE_BENC, filename.sv(), nullptr);
}
auto const key_tgts = std::array<std::pair<tr_quark, uint64_t*>, 5>{
{ { TR_KEY_downloaded_bytes, &ret.downloadedBytes },
{ TR_KEY_files_added, &ret.filesAdded },
{ TR_KEY_seconds_active, &ret.secondsActive },
{ TR_KEY_session_count, &ret.sessionCount },
{ TR_KEY_uploaded_bytes, &ret.uploadedBytes } }
};
if (loaded)
{
auto i = int64_t{};
if (tr_variantDictFindInt(&top, TR_KEY_downloaded_bytes, &i))
for (auto& [key, tgt] : key_tgts)
{
ret.downloadedBytes = (uint64_t)i;
if (auto val = int64_t{}; tr_variantDictFindInt(&*stats, key, &val))
{
*tgt = val;
}
}
if (tr_variantDictFindInt(&top, TR_KEY_files_added, &i))
{
ret.filesAdded = (uint64_t)i;
}
if (tr_variantDictFindInt(&top, TR_KEY_seconds_active, &i))
{
ret.secondsActive = (uint64_t)i;
}
if (tr_variantDictFindInt(&top, TR_KEY_session_count, &i))
{
ret.sessionCount = (uint64_t)i;
}
if (tr_variantDictFindInt(&top, TR_KEY_uploaded_bytes, &i))
{
ret.uploadedBytes = (uint64_t)i;
}
tr_variantClear(&top);
tr_variantClear(&*stats);
}
return ret;
@ -68,7 +65,6 @@ tr_session_stats tr_stats::load_old_stats(std::string_view config_dir)
void tr_stats::save() const
{
auto const saveme = cumulative();
auto const filename = tr_pathbuf{ config_dir_, "/stats.json"sv };
auto top = tr_variant{};
tr_variantInitDict(&top, 5);
tr_variantDictAddInt(&top, TR_KEY_downloaded_bytes, saveme.downloadedBytes);
@ -76,7 +72,7 @@ void tr_stats::save() const
tr_variantDictAddInt(&top, TR_KEY_seconds_active, saveme.secondsActive);
tr_variantDictAddInt(&top, TR_KEY_session_count, saveme.sessionCount);
tr_variantDictAddInt(&top, TR_KEY_uploaded_bytes, saveme.uploadedBytes);
tr_variantToFile(&top, TR_VARIANT_FMT_JSON, filename);
tr_variant_serde::json().to_file(top, tr_pathbuf{ config_dir_, "/stats.json"sv });
tr_variantClear(&top);
}

View File

@ -229,19 +229,21 @@ bool use_new_metainfo(tr_torrent* tor, tr_error** error)
}
// checksum passed; now try to parse it as benc
auto info_dict_v = tr_variant{};
if (!tr_variantFromBuf(&info_dict_v, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, m->metadata, nullptr, error))
auto serde = tr_variant_serde::benc().inplace();
auto info_dict_v = serde.parse(m->metadata);
if (!info_dict_v)
{
tr_error_propagate(error, &serde.error_);
return false;
}
// yay we have an info dict. Let's make a torrent file
auto top_v = tr_variant{};
build_metainfo_except_info_dict(tor->metainfo_, &top_v);
tr_variantMergeDicts(tr_variantDictAddDict(&top_v, TR_KEY_info, 0), &info_dict_v);
auto const benc = tr_variantToStr(&top_v, TR_VARIANT_FMT_BENC);
tr_variantMergeDicts(tr_variantDictAddDict(&top_v, TR_KEY_info, 0), &*info_dict_v);
auto const benc = serde.to_string(top_v);
tr_variantClear(&top_v);
tr_variantClear(&info_dict_v);
tr_variantClear(&*info_dict_v);
// does this synthetic torrent file parse?
auto metainfo = tr_torrent_metainfo{};

View File

@ -459,7 +459,7 @@ private:
tr_variantDictAddRaw(&benc, TR_KEY_nodes6, std::data(compact6), out6 - std::data(compact6));
}
tr_variantToFile(&benc, TR_VARIANT_FMT_BENC, state_filename_);
tr_variant_serde::benc().to_file(benc, state_filename_);
tr_variantClear(&benc);
}
@ -471,17 +471,18 @@ private:
auto nodes = Nodes{};
if (auto dict = tr_variant{}; tr_variantFromFile(&dict, TR_VARIANT_PARSE_BENC, filename))
if (auto otop = tr_variant_serde::benc().parse_file(filename); otop)
{
if (auto sv = std::string_view{};
tr_variantDictFindStrView(&dict, TR_KEY_id, &sv) && std::size(sv) == std::size(id))
auto& top = *otop;
if (auto sv = std::string_view{}; tr_variantDictFindStrView(&top, TR_KEY_id, &sv) && std::size(sv) == std::size(id))
{
std::copy(std::begin(sv), std::end(sv), std::begin(id));
}
size_t raw_len = 0U;
std::byte const* raw = nullptr;
if (tr_variantDictFindRaw(&dict, TR_KEY_nodes, &raw, &raw_len) && raw_len % 6 == 0)
if (tr_variantDictFindRaw(&top, TR_KEY_nodes, &raw, &raw_len) && raw_len % 6 == 0)
{
auto* walk = raw;
auto const* const end = raw + raw_len;
@ -495,7 +496,7 @@ private:
}
}
if (tr_variantDictFindRaw(&dict, TR_KEY_nodes6, &raw, &raw_len) && raw_len % 18 == 0)
if (tr_variantDictFindRaw(&top, TR_KEY_nodes6, &raw, &raw_len) && raw_len % 18 == 0)
{
auto* walk = raw;
auto const* const end = raw + raw_len;
@ -509,7 +510,7 @@ private:
}
}
tr_variantClear(&dict);
tr_variantClear(&top);
}
return std::make_pair(id, nodes);

View File

@ -22,7 +22,6 @@
#include "libtransmission/quark.h"
#include "libtransmission/tr-buffer.h"
#include "libtransmission/utils.h"
#include "libtransmission/variant-common.h"
#include "libtransmission/variant.h"
struct tr_error;
@ -135,13 +134,13 @@ namespace parse_helpers
struct MyHandler : public transmission::benc::Handler
{
tr_variant* const top_;
int const parse_opts_;
bool inplace_;
std::deque<tr_variant*> stack_;
std::optional<tr_quark> key_;
MyHandler(tr_variant* top, int parse_opts)
MyHandler(tr_variant* top, bool inplace)
: top_{ top }
, parse_opts_{ parse_opts }
, inplace_{ inplace }
{
}
@ -172,7 +171,7 @@ struct MyHandler : public transmission::benc::Handler
return false;
}
if ((parse_opts_ & TR_VARIANT_PARSE_INPLACE) != 0)
if (inplace_)
{
tr_variantInitStrView(variant, sv);
}
@ -264,14 +263,21 @@ private:
} // namespace parse_helpers
} // namespace
bool tr_variantParseBenc(tr_variant& top, int parse_opts, std::string_view benc, char const** setme_end, tr_error** error)
std::optional<tr_variant> tr_variant_serde::parse_benc(std::string_view input)
{
using namespace parse_helpers;
using Stack = transmission::benc::ParserStack<512>;
auto top = tr_variant{};
auto stack = Stack{};
auto handler = MyHandler{ &top, parse_opts };
return transmission::benc::parse(benc, stack, handler, setme_end, error) && std::empty(stack);
auto handler = MyHandler{ &top, parse_inplace_ };
if (transmission::benc::parse(input, stack, handler, &end_, &error_) && std::empty(stack))
{
return top;
}
tr_variantClear(&top);
return {};
}
// ---
@ -282,20 +288,20 @@ namespace to_string_helpers
{
using OutBuf = libtransmission::StackBuffer<1024U * 8U, std::byte>;
void saveIntFunc(tr_variant const* val, void* vout)
void saveIntFunc(tr_variant const& /*var*/, int64_t const val, void* vout)
{
auto out = static_cast<OutBuf*>(vout);
auto const [buf, buflen] = out->reserve_space(64U);
auto* walk = reinterpret_cast<char*>(buf);
auto const* const begin = walk;
walk = fmt::format_to(walk, FMT_COMPILE("i{:d}e"), val->val.i);
walk = fmt::format_to(walk, FMT_COMPILE("i{:d}e"), val);
out->commit_space(walk - begin);
}
void saveBoolFunc(tr_variant const* val, void* vout)
void saveBoolFunc(tr_variant const& /*var*/, bool const val, void* vout)
{
static_cast<OutBuf*>(vout)->add(val->val.b ? "i1e"sv : "i0e"sv);
static_cast<OutBuf*>(vout)->add(val ? "i1e"sv : "i0e"sv);
}
void saveStringImpl(OutBuf* out, std::string_view sv)
@ -309,55 +315,52 @@ void saveStringImpl(OutBuf* out, std::string_view sv)
out->commit_space(walk - begin);
}
void saveStringFunc(tr_variant const* v, void* vout)
void saveStringFunc(tr_variant const& /*var*/, std::string_view const val, void* vout)
{
auto sv = std::string_view{};
(void)!tr_variantGetStrView(v, &sv);
saveStringImpl(static_cast<OutBuf*>(vout), sv);
saveStringImpl(static_cast<OutBuf*>(vout), val);
}
void saveRealFunc(tr_variant const* val, void* vout)
void saveRealFunc(tr_variant const& /*val*/, double const val, void* vout)
{
// the benc spec doesn't handle floats; save it as a string.
auto buf = std::array<char, 64>{};
auto const* const out = fmt::format_to(std::data(buf), FMT_COMPILE("{:f}"), val->val.d);
auto const* const out = fmt::format_to(std::data(buf), FMT_COMPILE("{:f}"), val);
saveStringImpl(static_cast<OutBuf*>(vout), { std::data(buf), static_cast<size_t>(out - std::data(buf)) });
}
void saveDictBeginFunc(tr_variant const* /*val*/, void* vbuf)
void saveDictBeginFunc(tr_variant const& /*val*/, void* vbuf)
{
static_cast<OutBuf*>(vbuf)->push_back('d');
}
void saveListBeginFunc(tr_variant const* /*val*/, void* vbuf)
void saveListBeginFunc(tr_variant const& /*val*/, void* vbuf)
{
static_cast<OutBuf*>(vbuf)->push_back('l');
}
void saveContainerEndFunc(tr_variant const* /*val*/, void* vbuf)
void saveContainerEndFunc(tr_variant const& /*val*/, void* vbuf)
{
static_cast<OutBuf*>(vbuf)->push_back('e');
}
struct VariantWalkFuncs const walk_funcs = {
saveIntFunc, //
saveBoolFunc, //
saveRealFunc, //
saveStringFunc, //
saveDictBeginFunc, //
saveListBeginFunc, //
saveContainerEndFunc, //
};
} // namespace to_string_helpers
} // namespace
std::string tr_variantToStrBenc(tr_variant const* top)
std::string tr_variant_serde::to_benc_string(tr_variant const& var)
{
using namespace to_string_helpers;
static auto constexpr Funcs = WalkFuncs{
saveIntFunc, //
saveBoolFunc, //
saveRealFunc, //
saveStringFunc, //
saveDictBeginFunc, //
saveListBeginFunc, //
saveContainerEndFunc, //
};
auto buf = OutBuf{};
tr_variantWalk(top, &walk_funcs, &buf, true);
walk(var, Funcs, &buf, true);
return buf.to_string();
}

View File

@ -1,48 +0,0 @@
// This file Copyright © 2008-2023 Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#pragma once
#ifndef LIBTRANSMISSION_VARIANT_MODULE
#error only libtransmission/variant-*.c should #include this header.
#endif
#include <cstdint> // int64_t
#include <optional>
#include <string>
#include <string_view>
#include "transmission.h"
#include "variant.h"
using VariantWalkFunc = void (*)(tr_variant const* val, void* user_data);
struct VariantWalkFuncs
{
VariantWalkFunc intFunc;
VariantWalkFunc boolFunc;
VariantWalkFunc realFunc;
VariantWalkFunc stringFunc;
VariantWalkFunc dictBeginFunc;
VariantWalkFunc listBeginFunc;
VariantWalkFunc containerEndFunc;
};
void tr_variantWalk(tr_variant const* top, VariantWalkFuncs const* walk_funcs, void* user_data, bool sort_dicts);
[[nodiscard]] std::string tr_variantToStrJson(tr_variant const* top, bool lean);
[[nodiscard]] std::string tr_variantToStrBenc(tr_variant const* top);
/** @brief Private function that's exposed here only for unit tests */
[[nodiscard]] std::optional<int64_t> tr_bencParseInt(std::string_view* benc_inout);
/** @brief Private function that's exposed here only for unit tests */
[[nodiscard]] std::optional<std::string_view> tr_bencParseStr(std::string_view* benc_inout);
bool tr_variantParseBenc(tr_variant& top, int parse_opts, std::string_view benc, char const** setme_end, tr_error** error);
bool tr_variantParseJson(tr_variant& setme, int opts, std::string_view json, char const** setme_end, tr_error** error);

View File

@ -33,7 +33,6 @@
#include "libtransmission/tr-assert.h"
#include "libtransmission/tr-buffer.h"
#include "libtransmission/utils.h"
#include "libtransmission/variant-common.h"
#include "libtransmission/variant.h"
using namespace std::literals;
@ -55,7 +54,7 @@ struct json_wrapper_data
tr_error* error;
std::deque<tr_variant*> stack;
tr_variant* top;
int parse_opts;
bool inplace = false;
/* A very common pattern is for a container's children to be similar,
* e.g. they may all be objects with the same set of keys. So when
@ -317,7 +316,7 @@ void action_callback_POP(jsonsl_t jsn, jsonsl_action_t /*action*/, struct jsonsl
if (state->type == JSONSL_T_STRING)
{
auto const [str, inplace] = extract_string(jsn, state, data->strbuf);
if (inplace && ((data->parse_opts & TR_VARIANT_PARSE_INPLACE) != 0))
if (inplace && data->inplace)
{
tr_variantInitStrView(get_node(jsn), str);
}
@ -373,53 +372,55 @@ void action_callback_POP(jsonsl_t jsn, jsonsl_action_t /*action*/, struct jsonsl
} // namespace parse_helpers
} // namespace
bool tr_variantParseJson(tr_variant& setme, int parse_opts, std::string_view json, char const** setme_end, tr_error** error)
std::optional<tr_variant> tr_variant_serde::parse_json(std::string_view input)
{
using namespace parse_helpers;
TR_ASSERT((parse_opts & TR_VARIANT_PARSE_JSON) != 0);
auto top = tr_variant{};
auto data = json_wrapper_data{};
data.error = nullptr;
data.size = std::size(input);
data.has_content = false;
data.key = ""sv;
data.inplace = parse_inplace_;
data.preallocGuess = {};
data.stack = {};
data.top = &top;
jsonsl_t jsn = jsonsl_new(MaxDepth);
auto jsn = jsonsl_new(MaxDepth);
jsn->action_callback_PUSH = action_callback_PUSH;
jsn->action_callback_POP = action_callback_POP;
jsn->error_callback = error_callback;
jsn->data = &data;
jsonsl_enable_all_callbacks(jsn);
data.error = nullptr;
data.size = std::size(json);
data.has_content = false;
data.key = ""sv;
data.parse_opts = parse_opts;
data.preallocGuess = {};
data.stack = {};
data.top = &setme;
// parse it
jsonsl_feed(jsn, static_cast<jsonsl_char_t const*>(std::data(input)), std::size(input));
/* parse it */
jsonsl_feed(jsn, static_cast<jsonsl_char_t const*>(std::data(json)), std::size(json));
/* EINVAL if there was no content */
// EINVAL if there was no content
if (data.error == nullptr && !data.has_content)
{
tr_error_set(&data.error, EINVAL, "No content");
}
/* maybe set the end ptr */
if (setme_end != nullptr)
{
*setme_end = std::data(json) + jsn->pos;
}
end_ = std::data(input) + jsn->pos;
/* cleanup */
auto const success = data.error == nullptr;
if (data.error != nullptr)
{
tr_error_propagate(error, &data.error);
tr_error_propagate(&error_, &data.error);
}
// cleanup
jsonsl_destroy(jsn);
return success;
if (error_ == nullptr)
{
return top;
}
tr_variantClear(&top);
return {};
}
// ---
@ -430,9 +431,10 @@ namespace to_string_helpers
{
struct ParentState
{
int variantType;
int childIndex;
int childCount;
bool is_map = false;
bool is_list = false;
size_t child_index;
size_t child_count;
};
struct JsonWalk
@ -465,54 +467,49 @@ void jsonIndent(struct JsonWalk* data)
void jsonChildFunc(struct JsonWalk* data)
{
if (!std::empty(data->parents))
if (std::empty(data->parents))
{
auto& pstate = data->parents.back();
return;
}
switch (pstate.variantType)
auto& parent_state = data->parents.back();
if (parent_state.is_map)
{
int const i = parent_state.child_index;
++parent_state.child_index;
if (i % 2 == 0)
{
case TR_VARIANT_TYPE_DICT:
{
int const i = pstate.childIndex;
++pstate.childIndex;
if (i % 2 == 0)
{
data->out.add(data->doIndent ? ": "sv : ":"sv);
}
else
{
bool const is_last = pstate.childIndex == pstate.childCount;
if (!is_last)
{
data->out.push_back(',');
jsonIndent(data);
}
}
break;
}
case TR_VARIANT_TYPE_LIST:
++pstate.childIndex;
if (bool const is_last = pstate.childIndex == pstate.childCount; !is_last)
data->out.add(data->doIndent ? ": "sv : ":"sv);
}
else
{
bool const is_last = parent_state.child_index == parent_state.child_count;
if (!is_last)
{
data->out.push_back(',');
jsonIndent(data);
}
break;
default:
break;
}
}
else if (parent_state.is_list)
{
++parent_state.child_index;
if (bool const is_last = parent_state.child_index == parent_state.child_count; !is_last)
{
data->out.push_back(',');
jsonIndent(data);
}
}
}
void jsonPushParent(struct JsonWalk* data, tr_variant const* v)
void jsonPushParent(struct JsonWalk* data, tr_variant const& v)
{
int const n_children = tr_variantIsDict(v) ? v->val.l.count * 2 : v->val.l.count;
data->parents.push_back({ v->type, 0, n_children });
auto const is_dict = tr_variantIsDict(&v);
auto const is_list = tr_variantIsList(&v);
auto const n_children = is_dict ? v.val.l.count * 2U : v.val.l.count;
data->parents.push_back({ is_dict, is_list, 0, n_children });
}
void jsonPopParent(struct JsonWalk* data)
@ -520,32 +517,23 @@ void jsonPopParent(struct JsonWalk* data)
data->parents.pop_back();
}
void jsonIntFunc(tr_variant const* val, void* vdata)
void jsonIntFunc(tr_variant const& /*var*/, int64_t const val, void* vdata)
{
auto buf = std::array<char, 64>{};
auto const* const out = fmt::format_to(std::data(buf), FMT_COMPILE("{:d}"), val->val.i);
auto const* const out = fmt::format_to(std::data(buf), FMT_COMPILE("{:d}"), val);
auto* const data = static_cast<JsonWalk*>(vdata);
data->out.add(std::data(buf), static_cast<size_t>(out - std::data(buf)));
jsonChildFunc(data);
}
void jsonBoolFunc(tr_variant const* val, void* vdata)
void jsonBoolFunc(tr_variant const& /*var*/, bool const val, void* vdata)
{
auto* data = static_cast<struct JsonWalk*>(vdata);
if (val->val.b)
{
data->out.add("true"sv);
}
else
{
data->out.add("false"sv);
}
data->out.add(val ? "true"sv : "false"sv);
jsonChildFunc(data);
}
void jsonRealFunc(tr_variant const* val, void* vdata)
void jsonRealFunc(tr_variant const& /*var*/, double const val, void* vdata)
{
auto* const data = static_cast<struct JsonWalk*>(vdata);
@ -553,13 +541,13 @@ void jsonRealFunc(tr_variant const* val, void* vdata)
auto* walk = reinterpret_cast<char*>(buf);
auto const* const begin = walk;
if (fabs(val->val.d - (int)val->val.d) < 0.00001)
if (fabs(val - (int)val) < 0.00001)
{
walk = fmt::format_to(walk, FMT_COMPILE("{:.0f}"), val->val.d);
walk = fmt::format_to(walk, FMT_COMPILE("{:.0f}"), val);
}
else
{
walk = fmt::format_to(walk, FMT_COMPILE("{:.4f}"), val->val.d);
walk = fmt::format_to(walk, FMT_COMPILE("{:.4f}"), val);
}
data->out.commit_space(walk - begin);
@ -586,13 +574,10 @@ void jsonRealFunc(tr_variant const* val, void* vdata)
return buf;
}
void jsonStringFunc(tr_variant const* val, void* vdata)
void jsonStringFunc(tr_variant const& /*var*/, std::string_view sv, void* vdata)
{
auto* const data = static_cast<struct JsonWalk*>(vdata);
auto sv = std::string_view{};
(void)!tr_variantGetStrView(val, &sv);
auto& out = data->out;
auto const [buf, buflen] = out.reserve_space(std::size(sv) * 6 + 2);
auto* walk = reinterpret_cast<char*>(buf);
@ -667,34 +652,33 @@ void jsonStringFunc(tr_variant const* val, void* vdata)
jsonChildFunc(data);
}
void jsonDictBeginFunc(tr_variant const* val, void* vdata)
void jsonDictBeginFunc(tr_variant const& var, void* vdata)
{
auto* const data = static_cast<struct JsonWalk*>(vdata);
jsonPushParent(data, val);
jsonPushParent(data, var);
data->out.push_back('{');
if (val->val.l.count != 0)
if (var.val.l.count != 0U)
{
jsonIndent(data);
}
}
void jsonListBeginFunc(tr_variant const* val, void* vdata)
void jsonListBeginFunc(tr_variant const& var, void* vdata)
{
size_t const n_children = tr_variantListSize(val);
auto* const data = static_cast<struct JsonWalk*>(vdata);
jsonPushParent(data, val);
jsonPushParent(data, var);
data->out.push_back('[');
if (n_children != 0)
if (var.val.l.count != 0U)
{
jsonIndent(data);
}
}
void jsonContainerEndFunc(tr_variant const* val, void* vdata)
void jsonContainerEndFunc(tr_variant const& var, void* vdata)
{
auto* const data = static_cast<struct JsonWalk*>(vdata);
@ -702,7 +686,7 @@ void jsonContainerEndFunc(tr_variant const* val, void* vdata)
jsonIndent(data);
if (tr_variantIsDict(val))
if (tr_variantIsDict(&var))
{
data->out.push_back('}');
}
@ -714,26 +698,25 @@ void jsonContainerEndFunc(tr_variant const* val, void* vdata)
jsonChildFunc(data);
}
struct VariantWalkFuncs const walk_funcs = {
jsonIntFunc, //
jsonBoolFunc, //
jsonRealFunc, //
jsonStringFunc, //
jsonDictBeginFunc, //
jsonListBeginFunc, //
jsonContainerEndFunc, //
};
} // namespace to_string_helpers
} // namespace
std::string tr_variantToStrJson(tr_variant const* top, bool lean)
std::string tr_variant_serde::to_json_string(tr_variant const& var) const
{
using namespace to_string_helpers;
auto data = JsonWalk{ !lean };
static auto constexpr Funcs = WalkFuncs{
jsonIntFunc, //
jsonBoolFunc, //
jsonRealFunc, //
jsonStringFunc, //
jsonDictBeginFunc, //
jsonListBeginFunc, //
jsonContainerEndFunc, //
};
tr_variantWalk(top, &walk_funcs, &data, true);
auto data = JsonWalk{ !compact_ };
walk(var, Funcs, &data, true);
auto& buf = data.out;
if (!std::empty(buf))

View File

@ -23,7 +23,6 @@
#include "libtransmission/quark.h"
#include "libtransmission/tr-assert.h"
#include "libtransmission/utils.h"
#include "libtransmission/variant-common.h"
#include "libtransmission/variant.h"
using namespace std::literals;
@ -693,10 +692,10 @@ private:
* easier to read, but was vulnerable to a smash-stacking
* attack via maliciously-crafted data. (#667)
*/
void tr_variantWalk(tr_variant const* top, struct VariantWalkFuncs const* walk_funcs, void* user_data, bool sort_dicts)
void tr_variant_serde::walk(tr_variant const& top, WalkFuncs const& walk_funcs, void* user_data, bool sort_dicts)
{
auto stack = VariantWalker{};
stack.emplace(top, sort_dicts);
stack.emplace(&top, sort_dicts);
while (!stack.empty())
{
@ -717,16 +716,17 @@ void tr_variantWalk(tr_variant const* top, struct VariantWalkFuncs const* walk_f
{
if (tr_variantIsDict(&node.v))
{
auto const keystr = tr_quark_get_string_view(v->key);
auto tmp = tr_variant{};
tr_variantInitQuark(&tmp, v->key);
walk_funcs->stringFunc(&tmp, user_data);
walk_funcs.string_func(tmp, keystr, user_data);
}
}
else // finished with this node
{
if (tr_variantIsContainer(&node.v))
{
walk_funcs->containerEndFunc(&node.v, user_data);
walk_funcs.container_end_func(node.v, user_data);
}
stack.pop();
@ -739,25 +739,25 @@ void tr_variantWalk(tr_variant const* top, struct VariantWalkFuncs const* walk_f
switch (v->type)
{
case TR_VARIANT_TYPE_INT:
walk_funcs->intFunc(v, user_data);
walk_funcs.int_func(*v, v->val.i, user_data);
break;
case TR_VARIANT_TYPE_BOOL:
walk_funcs->boolFunc(v, user_data);
walk_funcs.bool_func(*v, v->val.b, user_data);
break;
case TR_VARIANT_TYPE_REAL:
walk_funcs->realFunc(v, user_data);
walk_funcs.double_func(*v, v->val.d, user_data);
break;
case TR_VARIANT_TYPE_STR:
walk_funcs->stringFunc(v, user_data);
walk_funcs.string_func(*v, v->val.s.get(), user_data);
break;
case TR_VARIANT_TYPE_LIST:
if (v == &node.v)
{
walk_funcs->listBeginFunc(v, user_data);
walk_funcs.list_begin_func(*v, user_data);
}
else
{
@ -768,7 +768,7 @@ void tr_variantWalk(tr_variant const* top, struct VariantWalkFuncs const* walk_f
case TR_VARIANT_TYPE_DICT:
if (v == &node.v)
{
walk_funcs->dictBeginFunc(v, user_data);
walk_funcs.dict_begin_func(*v, user_data);
}
else
{
@ -787,43 +787,23 @@ void tr_variantWalk(tr_variant const* top, struct VariantWalkFuncs const* walk_f
// ---
namespace
{
namespace clear_helpers
{
void freeDummyFunc(tr_variant const* /*v*/, void* /*buf*/)
{
}
void freeStringFunc(tr_variant const* v, void* /*user_data*/)
{
const_cast<tr_variant*>(v)->val.s.clear();
}
void freeContainerEndFunc(tr_variant const* v, void* /*user_data*/)
{
delete[] v->val.l.vals;
}
VariantWalkFuncs constexpr FreeWalkFuncs = {
freeDummyFunc, //
freeDummyFunc, //
freeDummyFunc, //
freeStringFunc, //
freeDummyFunc, //
freeDummyFunc, //
freeContainerEndFunc, //
};
} // namespace clear_helpers
} // namespace
void tr_variantClear(tr_variant* clearme)
{
using namespace clear_helpers;
// clang-format off
tr_variant_serde::WalkFuncs cleanup_funcs = {
[](tr_variant const&, int64_t, void*) {},
[](tr_variant const&, bool, void*) {},
[](tr_variant const&, double, void*) {},
[](tr_variant const& var, std::string_view, void*){ const_cast<tr_variant&>(var).val.s.clear(); },
[](tr_variant const&, void*) {},
[](tr_variant const&, void*) {},
[](tr_variant const& var, void*) { delete[] var.val.l.vals; }
};
// clang-format on
if (!tr_variantIsEmpty(clearme))
{
tr_variantWalk(clearme, &FreeWalkFuncs, nullptr, false);
tr_variant_serde::walk(*clearme, cleanup_funcs, nullptr, false);
}
*clearme = {};
@ -992,71 +972,48 @@ void tr_variantMergeDicts(tr_variant* target, tr_variant const* source)
// ---
std::string tr_variantToStr(tr_variant const* v, tr_variant_fmt fmt)
tr_variant_serde::~tr_variant_serde()
{
switch (fmt)
{
case TR_VARIANT_FMT_JSON:
return tr_variantToStrJson(v, false);
case TR_VARIANT_FMT_JSON_LEAN:
return tr_variantToStrJson(v, true);
default: // TR_VARIANT_FMT_BENC:
return tr_variantToStrBenc(v);
}
tr_error_clear(&error_);
}
int tr_variantToFile(tr_variant const* v, tr_variant_fmt fmt, std::string_view filename)
std::optional<tr_variant> tr_variant_serde::parse(std::string_view input)
{
auto error_code = int{ 0 };
auto const contents = tr_variantToStr(v, fmt);
tr_error_clear(&error_);
return type_ == Type::Json ? parse_json(input) : parse_benc(input);
}
tr_error* error = nullptr;
tr_file_save(filename, contents, &error);
if (error != nullptr)
[[nodiscard]] std::optional<tr_variant> tr_variant_serde::parse_file(std::string_view filename)
{
TR_ASSERT_MSG(!parse_inplace_, "not supported in from_file()");
parse_inplace_ = false;
if (auto buf = std::vector<char>{}; tr_file_read(filename, buf, &error_))
{
return parse(buf);
}
return {};
}
std::string tr_variant_serde::to_string(tr_variant const& var) const
{
return type_ == Type::Json ? to_json_string(var) : to_benc_string(var);
}
bool tr_variant_serde::to_file(tr_variant const& var, std::string_view filename)
{
tr_file_save(filename, to_string(var), &error_);
if (error_ != nullptr)
{
tr_logAddError(fmt::format(
_("Couldn't save '{path}': {error} ({error_code})"),
fmt::arg("path", filename),
fmt::arg("error", error->message),
fmt::arg("error_code", error->code)));
error_code = error->code;
tr_error_clear(&error);
fmt::arg("error", error_->message),
fmt::arg("error_code", error_->code)));
return false;
}
return error_code;
}
// ---
bool tr_variantFromBuf(tr_variant* setme, int opts, std::string_view buf, char const** setme_end, tr_error** error)
{
// supported formats: benc, json
TR_ASSERT((opts & (TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_JSON)) != 0);
*setme = {};
auto const success = ((opts & TR_VARIANT_PARSE_BENC) != 0) ? tr_variantParseBenc(*setme, opts, buf, setme_end, error) :
tr_variantParseJson(*setme, opts, buf, setme_end, error);
if (!success)
{
tr_variantClear(setme);
}
return success;
}
bool tr_variantFromFile(tr_variant* setme, tr_variant_parse_opts opts, std::string_view filename, tr_error** error)
{
// can't do inplace when this function is allocating & freeing the memory...
TR_ASSERT((opts & TR_VARIANT_PARSE_INPLACE) == 0);
if (auto buf = std::vector<char>{}; tr_file_read(filename, buf, error))
{
return tr_variantFromBuf(setme, opts, buf, nullptr, error);
}
return false;
return true;
}

View File

@ -147,55 +147,6 @@ public:
*/
void tr_variantClear(tr_variant* clearme);
// --- Serialization / Deserialization
enum tr_variant_fmt
{
TR_VARIANT_FMT_BENC,
TR_VARIANT_FMT_JSON,
TR_VARIANT_FMT_JSON_LEAN /* saves bandwidth by omitting all whitespace. */
};
int tr_variantToFile(tr_variant const* variant, tr_variant_fmt fmt, std::string_view filename);
[[nodiscard]] std::string tr_variantToStr(tr_variant const* variant, tr_variant_fmt fmt);
enum tr_variant_parse_opts
{
TR_VARIANT_PARSE_BENC = (1 << 0),
TR_VARIANT_PARSE_JSON = (1 << 1),
TR_VARIANT_PARSE_INPLACE = (1 << 2)
};
bool tr_variantFromFile(
tr_variant* setme,
tr_variant_parse_opts opts,
std::string_view filename,
struct tr_error** error = nullptr);
bool tr_variantFromBuf(
tr_variant* setme,
int variant_parse_opts,
std::string_view buf,
char const** setme_end = nullptr,
tr_error** error = nullptr);
template<typename T>
bool tr_variantFromBuf(
tr_variant* setme,
int variant_parse_opts,
T const& buf,
char const** setme_end = nullptr,
tr_error** error = nullptr)
{
return tr_variantFromBuf(
setme,
variant_parse_opts,
std::string_view{ std::data(buf), static_cast<size_t>(std::size(buf)) },
setme_end,
error);
}
[[nodiscard]] constexpr bool tr_variantIsType(tr_variant const* b, int type)
{
return b != nullptr && b->type == type;
@ -340,6 +291,110 @@ bool tr_variantDictFindRaw(tr_variant* dict, tr_quark key, std::byte const** set
/* this is only quasi-supported. don't rely on it too heavily outside of libT */
void tr_variantMergeDicts(tr_variant* dict_target, tr_variant const* dict_source);
// tr_variant serializer / deserializer
class tr_variant_serde
{
public:
~tr_variant_serde();
static tr_variant_serde benc() noexcept
{
return tr_variant_serde{ Type::Benc };
}
static tr_variant_serde json() noexcept
{
return tr_variant_serde{ Type::Json };
}
// Serialize data as compactly as possible, e.g.
// omit pretty-printing JSON whitespace
constexpr tr_variant_serde& compact() noexcept
{
compact_ = true;
return *this;
}
// When set, assumes that the `input` passed to parse() is valid
// for the lifespan of the variant and we can use string_views of
// `input` instead of cloning new strings.
constexpr tr_variant_serde& inplace() noexcept
{
parse_inplace_ = true;
return *this;
}
// ---
[[nodiscard]] std::optional<tr_variant> parse(std::string_view input);
template<typename CharSpan>
[[nodiscard]] std::optional<tr_variant> parse(CharSpan const& input)
{
return parse(std::string_view{ std::data(input), std::size(input) });
}
[[nodiscard]] std::optional<tr_variant> parse_file(std::string_view filename);
[[nodiscard]] constexpr char const* end() const noexcept
{
return end_;
}
// ---
[[nodiscard]] std::string to_string(tr_variant const& var) const;
bool to_file(tr_variant const& var, std::string_view filename);
// ---
// Tracks errors when parsing / saving
tr_error* error_ = nullptr;
private:
friend void tr_variantClear(tr_variant* clearme);
enum class Type
{
Benc,
Json
};
struct WalkFuncs
{
void (*int_func)(tr_variant const& var, int64_t val, void* user_data);
void (*bool_func)(tr_variant const& var, bool val, void* user_data);
void (*double_func)(tr_variant const& var, double val, void* user_data);
void (*string_func)(tr_variant const& var, std::string_view val, void* user_data);
void (*dict_begin_func)(tr_variant const& var, void* user_data);
void (*list_begin_func)(tr_variant const& var, void* user_data);
void (*container_end_func)(tr_variant const& var, void* user_data);
};
tr_variant_serde(Type type)
: type_{ type }
{
}
[[nodiscard]] std::optional<tr_variant> parse_json(std::string_view input);
[[nodiscard]] std::optional<tr_variant> parse_benc(std::string_view input);
[[nodiscard]] std::string to_json_string(tr_variant const& var) const;
[[nodiscard]] static std::string to_benc_string(tr_variant const& var);
static void walk(tr_variant const& top, WalkFuncs const& walk_funcs, void* user_data, bool sort_dicts);
Type type_;
bool compact_ = false;
bool parse_inplace_ = false;
// This is set to the first unparsed character after `parse()`.
char const* end_ = nullptr;
};
namespace libtransmission
{

View File

@ -403,17 +403,20 @@ Prefs::~Prefs()
}
// update settings.json with our settings
tr_variant file_settings;
QFile const file(QDir(config_dir_).absoluteFilePath(QStringLiteral("settings.json")));
if (!tr_variantFromFile(&file_settings, TR_VARIANT_PARSE_JSON, file.fileName().toStdString(), nullptr))
auto serde = tr_variant_serde::json();
auto const file = QFile{ QDir{ config_dir_ }.absoluteFilePath(QStringLiteral("settings.json")) };
auto const filename = file.fileName().toStdString();
auto settings = serde.parse_file(filename);
if (!settings)
{
tr_variantInitDict(&file_settings, PREFS_COUNT);
auto empty_dict = tr_variant{};
tr_variantInitDict(&empty_dict, PREFS_COUNT);
settings = empty_dict;
}
tr_variantMergeDicts(&file_settings, &current_settings);
tr_variantToFile(&file_settings, TR_VARIANT_FMT_JSON, file.fileName().toStdString());
tr_variantClear(&file_settings);
tr_variantMergeDicts(&*settings, &current_settings);
serde.to_file(*settings, filename);
tr_variantClear(&*settings);
// cleanup
tr_variantClear(&current_settings);

View File

@ -137,7 +137,7 @@ void RpcClient::sendNetworkRequest(TrVariantPtr json, QFutureInterface<RpcRespon
request_ = request;
}
auto const json_data = QByteArray::fromStdString(tr_variantToStr(json.get(), TR_VARIANT_FMT_JSON_LEAN));
auto const json_data = QByteArray::fromStdString(tr_variant_serde::json().compact().to_string(*json));
QNetworkReply* reply = networkAccessManager()->post(*request_, json_data);
reply->setProperty(RequestDataPropertyKey, QVariant::fromValue(json));
reply->setProperty(RequestFutureinterfacePropertyKey, QVariant::fromValue(promise));
@ -164,7 +164,7 @@ void RpcClient::sendLocalRequest(TrVariantPtr json, QFutureInterface<RpcResponse
{
if (verbose_)
{
fmt::print("{:s}:{:d} sending req:\n{:s}\n", __FILE__, __LINE__, tr_variantToStr(json.get(), TR_VARIANT_FMT_JSON));
fmt::print("{:s}:{:d} sending req:\n{:s}\n", __FILE__, __LINE__, tr_variant_serde::json().to_string(*json));
}
local_requests_.try_emplace(tag, promise);
@ -216,7 +216,7 @@ void RpcClient::localSessionCallback(tr_session* s, tr_variant* response, void*
if (self->verbose_)
{
fmt::print("{:s}:{:d} got response:\n{:s}\n", __FILE__, __LINE__, tr_variantToStr(response, TR_VARIANT_FMT_JSON));
fmt::print("{:s}:{:d} got response:\n{:s}\n", __FILE__, __LINE__, tr_variant_serde::json().to_string(*response));
}
TrVariantPtr const json = createVariant();
@ -272,12 +272,15 @@ void RpcClient::networkRequestFinished(QNetworkReply* reply)
}
else
{
auto const json_data = reply->readAll().trimmed();
auto const json_data = reply->readAll().trimmed().toStdString();
auto const json = createVariant();
RpcResponse result;
if (tr_variantFromBuf(json.get(), TR_VARIANT_PARSE_JSON, json_data))
auto result = RpcResponse{};
if (auto top = tr_variant_serde::json().parse(json_data); top)
{
std::swap(*json, *top);
result = parseResponseData(*json);
tr_variantClear(&*top);
}
promise.setProgressValue(1);

View File

@ -140,7 +140,7 @@ protected:
addr.to_compact_ipv6(std::back_inserter(compact), port);
}
tr_variantDictAddRaw(&dict, TR_KEY_nodes6, std::data(compact), std::size(compact));
tr_variantToFile(&dict, TR_VARIANT_FMT_BENC, dat_file);
tr_variant_serde::benc().to_file(dict, dat_file);
tr_variantClear(&dict);
}
};

View File

@ -59,8 +59,7 @@ TEST_P(JSONTest, testElements)
" \"null\": null }"
};
tr_variant top;
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
auto top = tr_variant_serde::json().inplace().parse(in).value_or(tr_variant{});
EXPECT_TRUE(tr_variantIsDict(&top));
auto sv = std::string_view{};
@ -95,18 +94,19 @@ TEST_P(JSONTest, testElements)
TEST_P(JSONTest, testUtf8)
{
auto in = "{ \"key\": \"Letöltések\" }"sv;
tr_variant top;
auto sv = std::string_view{};
tr_quark const key = tr_quark_new("key"sv);
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
auto serde = tr_variant_serde::json();
serde.inplace();
auto top = serde.parse(in).value_or(tr_variant{});
EXPECT_TRUE(tr_variantIsDict(&top));
EXPECT_TRUE(tr_variantDictFindStrView(&top, key, &sv));
EXPECT_EQ("Letöltések"sv, sv);
tr_variantClear(&top);
in = R"({ "key": "\u005C" })"sv;
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
top = serde.parse(in).value_or(tr_variant{});
EXPECT_TRUE(tr_variantIsDict(&top));
EXPECT_TRUE(tr_variantDictFindStrView(&top, key, &sv));
EXPECT_EQ("\\"sv, sv);
@ -121,17 +121,17 @@ TEST_P(JSONTest, testUtf8)
* 6. Confirm that the result is UTF-8.
*/
in = R"({ "key": "Let\u00f6lt\u00e9sek" })"sv;
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
top = serde.parse(in).value_or(tr_variant{});
EXPECT_TRUE(tr_variantIsDict(&top));
EXPECT_TRUE(tr_variantDictFindStrView(&top, key, &sv));
EXPECT_EQ("Letöltések"sv, sv);
auto json = tr_variantToStr(&top, TR_VARIANT_FMT_JSON);
auto json = serde.to_string(top);
tr_variantClear(&top);
EXPECT_FALSE(std::empty(json));
EXPECT_NE(std::string::npos, json.find("\\u00f6"));
EXPECT_NE(std::string::npos, json.find("\\u00e9"));
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, json));
top = serde.parse(json).value_or(tr_variant{});
EXPECT_TRUE(tr_variantIsDict(&top));
EXPECT_TRUE(tr_variantDictFindStrView(&top, key, &sv));
EXPECT_EQ("Letöltések"sv, sv);
@ -145,13 +145,14 @@ TEST_P(JSONTest, testUtf16Surrogates)
tr_variantInitDict(&top, 1);
auto const key = tr_quark_new("key"sv);
tr_variantDictAddStr(&top, key, ThinkingFaceEmojiUtf8);
auto const json = tr_variantToStr(&top, TR_VARIANT_FMT_JSON_LEAN);
auto serde = tr_variant_serde::json();
auto const json = serde.compact().to_string(top);
EXPECT_NE(std::string::npos, json.find("ud83e"));
EXPECT_NE(std::string::npos, json.find("udd14"));
tr_variantClear(&top);
auto parsed = tr_variant{};
EXPECT_TRUE(tr_variantFromBuf(&parsed, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, json));
auto parsed = serde.parse(json).value_or(tr_variant{});
EXPECT_TRUE(tr_variantIsDict(&parsed));
auto value = std::string_view{};
EXPECT_TRUE(tr_variantDictFindStrView(&parsed, key, &value));
@ -161,7 +162,7 @@ TEST_P(JSONTest, testUtf16Surrogates)
TEST_P(JSONTest, test1)
{
auto const in = std::string{
static auto constexpr Input =
"{\n"
" \"headers\": {\n"
" \"type\": \"request\",\n"
@ -173,15 +174,14 @@ TEST_P(JSONTest, test1)
" \"ids\": [ 7, 10 ]\n"
" }\n"
" }\n"
"}\n"
};
"}\n"sv;
tr_variant top;
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
auto serde = tr_variant_serde::json();
auto top = serde.inplace().parse(Input).value_or(tr_variant{});
EXPECT_TRUE(tr_variantIsDict(&top));
auto sv = std::string_view{};
auto i = int64_t{};
EXPECT_TRUE(tr_variantIsDict(&top));
auto* headers = tr_variantDictFind(&top, tr_quark_new("headers"sv));
EXPECT_NE(nullptr, headers);
EXPECT_TRUE(tr_variantIsDict(headers));
@ -210,25 +210,22 @@ TEST_P(JSONTest, test1)
TEST_P(JSONTest, test2)
{
tr_variant top;
auto const in = std::string{ " " };
top.type = 0;
EXPECT_FALSE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
EXPECT_FALSE(tr_variantIsDict(&top));
static auto constexpr Input = " "sv;
auto top = tr_variant_serde::json().inplace().parse(Input);
EXPECT_FALSE(top.has_value());
}
TEST_P(JSONTest, test3)
{
auto const
in = "{ \"error\": 2,"
" \"errorString\": \"torrent not registered with this tracker 6UHsVW'*C\","
" \"eta\": 262792,"
" \"id\": 25,"
" \"leftUntilDone\": 2275655680 }"sv;
static auto constexpr Input =
"{ \"error\": 2,"
" \"errorString\": \"torrent not registered with this tracker 6UHsVW'*C\","
" \"eta\": 262792,"
" \"id\": 25,"
" \"leftUntilDone\": 2275655680 }"sv;
tr_variant top;
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
auto top = tr_variant_serde::json().inplace().parse(Input).value_or(tr_variant{});
EXPECT_TRUE(tr_variantIsDict(&top));
auto sv = std::string_view{};
EXPECT_TRUE(tr_variantDictFindStrView(&top, TR_KEY_errorString, &sv));
@ -239,14 +236,14 @@ TEST_P(JSONTest, test3)
TEST_P(JSONTest, unescape)
{
tr_variant top;
auto const in = std::string{ R"({ "string-1": "\/usr\/lib" })" };
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, in));
static auto constexpr Input = R"({ "string-1": "\/usr\/lib" })"sv;
auto top = tr_variant_serde::json().inplace().parse(Input).value_or(tr_variant{});
EXPECT_TRUE(tr_variantIsDict(&top));
auto sv = std::string_view{};
EXPECT_TRUE(tr_variantDictFindStrView(&top, tr_quark_new("string-1"sv), &sv));
EXPECT_EQ("/usr/lib"sv, sv);
tr_variantClear(&top);
}

View File

@ -234,19 +234,18 @@ TEST_F(MakemetaTest, announceSingleTracker)
// generate the torrent and parse it as a variant
EXPECT_EQ(nullptr, builder.make_checksums().get());
auto top = tr_variant{};
auto const benc = builder.benc();
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, benc));
auto top = tr_variant_serde::benc().parse(builder.benc());
EXPECT_TRUE(top.has_value());
// confirm there's an "announce" entry
auto single_announce = std::string_view{};
EXPECT_TRUE(tr_variantDictFindStrView(&top, TR_KEY_announce, &single_announce));
EXPECT_TRUE(tr_variantDictFindStrView(&*top, TR_KEY_announce, &single_announce));
EXPECT_EQ(SingleAnnounce, single_announce);
// confirm there's not an "announce-list" entry
EXPECT_EQ(nullptr, tr_variantDictFind(&top, TR_KEY_announce_list));
EXPECT_EQ(nullptr, tr_variantDictFind(&*top, TR_KEY_announce_list));
tr_variantClear(&top);
tr_variantClear(&*top);
}
TEST_F(MakemetaTest, announceMultiTracker)
@ -265,22 +264,21 @@ TEST_F(MakemetaTest, announceMultiTracker)
// generate the torrent and parse it as a variant
EXPECT_EQ(nullptr, builder.make_checksums().get());
auto top = tr_variant{};
auto const benc = builder.benc();
EXPECT_TRUE(tr_variantFromBuf(&top, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, benc));
auto top = tr_variant_serde::benc().parse(builder.benc());
EXPECT_TRUE(top.has_value());
// confirm there's an "announce" entry
auto single_announce = std::string_view{};
EXPECT_TRUE(tr_variantDictFindStrView(&top, TR_KEY_announce, &single_announce));
EXPECT_TRUE(tr_variantDictFindStrView(&*top, TR_KEY_announce, &single_announce));
EXPECT_EQ(builder.announce_list().at(0).announce.sv(), single_announce);
// confirm there's an "announce-list" entry
tr_variant* announce_list_variant = nullptr;
EXPECT_TRUE(tr_variantDictFindList(&top, TR_KEY_announce_list, &announce_list_variant));
EXPECT_TRUE(tr_variantDictFindList(&*top, TR_KEY_announce_list, &announce_list_variant));
EXPECT_NE(nullptr, announce_list_variant);
EXPECT_EQ(std::size(builder.announce_list()), tr_variantListSize(announce_list_variant));
tr_variantClear(&top);
tr_variantClear(&*top);
}
TEST_F(MakemetaTest, privateAndSourceHasDifferentInfoHash)

View File

@ -217,49 +217,48 @@ TEST_F(VariantTest, str)
TEST_F(VariantTest, parse)
{
auto serde = tr_variant_serde::benc();
serde.inplace();
auto benc = "i64e"sv;
auto var = serde.parse(benc).value_or(tr_variant{});
auto i = int64_t{};
auto val = tr_variant{};
char const* end = nullptr;
auto ok = tr_variantFromBuf(&val, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, benc, &end);
EXPECT_TRUE(ok);
EXPECT_TRUE(tr_variantGetInt(&val, &i));
EXPECT_EQ(int64_t(64), i);
EXPECT_EQ(std::data(benc) + std::size(benc), end);
tr_variantClear(&val);
EXPECT_TRUE(tr_variantGetInt(&var, &i));
EXPECT_EQ(64, i);
EXPECT_EQ(std::data(benc) + std::size(benc), serde.end());
tr_variantClear(&var);
benc = "li64ei32ei16ee"sv;
ok = tr_variantFromBuf(&val, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, benc, &end);
EXPECT_TRUE(ok);
EXPECT_EQ(std::data(benc) + std::size(benc), end);
EXPECT_EQ(size_t{ 3 }, tr_variantListSize(&val));
EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&val, 0), &i));
var = serde.parse(benc).value_or(tr_variant{});
EXPECT_TRUE(tr_variantIsList(&var));
EXPECT_EQ(std::data(benc) + std::size(benc), serde.end());
EXPECT_EQ(3, tr_variantListSize(&var));
EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&var, 0), &i));
EXPECT_EQ(64, i);
EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&val, 1), &i));
EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&var, 1), &i));
EXPECT_EQ(32, i);
EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&val, 2), &i));
EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&var, 2), &i));
EXPECT_EQ(16, i);
EXPECT_EQ(benc, tr_variantToStr(&val, TR_VARIANT_FMT_BENC));
tr_variantClear(&val);
end = nullptr;
EXPECT_EQ(benc, serde.to_string(var));
tr_variantClear(&var);
benc = "lllee"sv;
ok = tr_variantFromBuf(&val, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, benc, &end);
EXPECT_FALSE(ok);
EXPECT_EQ(nullptr, end);
var = serde.parse(benc).value_or(tr_variant{});
EXPECT_TRUE(tr_variantIsEmpty(&var));
EXPECT_EQ(std::data(benc) + std::size(benc), serde.end());
tr_variantClear(&var);
benc = "le"sv;
EXPECT_TRUE(tr_variantFromBuf(&val, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, benc, &end));
EXPECT_EQ(std::data(benc) + std::size(benc), end);
EXPECT_EQ(benc, tr_variantToStr(&val, TR_VARIANT_FMT_BENC));
tr_variantClear(&val);
var = serde.parse(benc).value_or(tr_variant{});
EXPECT_TRUE(tr_variantIsList(&var));
EXPECT_EQ(std::data(benc) + std::size(benc), serde.end());
EXPECT_EQ(benc, serde.to_string(var));
tr_variantClear(&var);
benc = "d20:"sv;
end = nullptr;
ok = tr_variantFromBuf(&val, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, benc, &end);
EXPECT_FALSE(ok);
EXPECT_EQ(nullptr, end);
var = serde.parse(benc).value_or(tr_variant{});
EXPECT_TRUE(tr_variantIsEmpty(&var));
EXPECT_EQ(std::data(benc) + 1U, serde.end());
}
TEST_F(VariantTest, bencParseAndReencode)
@ -282,64 +281,67 @@ TEST_F(VariantTest, bencParseAndReencode)
{ " "sv, false },
} };
auto serde = tr_variant_serde::benc();
serde.inplace();
for (auto const& test : Tests)
{
tr_variant val;
char const* end = nullptr;
auto const is_good = tr_variantFromBuf(&val, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, test.benc, &end);
auto var = serde.parse(test.benc);
EXPECT_EQ(test.is_good, is_good);
if (is_good)
EXPECT_EQ(test.is_good, var.has_value());
if (var)
{
EXPECT_EQ(test.benc.data() + test.benc.size(), end);
EXPECT_EQ(test.benc, tr_variantToStr(&val, TR_VARIANT_FMT_BENC));
tr_variantClear(&val);
EXPECT_EQ(test.benc.data() + test.benc.size(), serde.end());
EXPECT_EQ(test.benc, serde.to_string(*var));
tr_variantClear(&*var);
}
}
}
TEST_F(VariantTest, bencSortWhenSerializing)
{
auto constexpr In = "lld1:bi32e1:ai64eeee"sv;
auto constexpr ExpectedOut = "lld1:ai64e1:bi32eeee"sv;
static auto constexpr In = "lld1:bi32e1:ai64eeee"sv;
static auto constexpr ExpectedOut = "lld1:ai64e1:bi32eeee"sv;
tr_variant val;
char const* end = nullptr;
auto const ok = tr_variantFromBuf(&val, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, In, &end);
EXPECT_TRUE(ok);
EXPECT_EQ(std::data(In) + std::size(In), end);
EXPECT_EQ(ExpectedOut, tr_variantToStr(&val, TR_VARIANT_FMT_BENC));
auto serde = tr_variant_serde::benc();
auto var = serde.inplace().parse(In);
EXPECT_TRUE(var.has_value());
EXPECT_EQ(std::data(In) + std::size(In), serde.end());
EXPECT_EQ(ExpectedOut, serde.to_string(*var));
tr_variantClear(&val);
tr_variantClear(&*var);
}
TEST_F(VariantTest, bencMalformedTooManyEndings)
{
auto constexpr In = "leee"sv;
auto constexpr ExpectedOut = "le"sv;
static auto constexpr In = "leee"sv;
static auto constexpr ExpectedOut = "le"sv;
tr_variant val;
char const* end = nullptr;
auto const ok = tr_variantFromBuf(&val, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, In, &end);
EXPECT_TRUE(ok);
EXPECT_EQ(std::data(In) + std::size(ExpectedOut), end);
EXPECT_EQ(ExpectedOut, tr_variantToStr(&val, TR_VARIANT_FMT_BENC));
auto serde = tr_variant_serde::benc();
auto var = serde.inplace().parse(In);
EXPECT_TRUE(var.has_value());
EXPECT_EQ(std::data(In) + std::size(ExpectedOut), serde.end());
EXPECT_EQ(ExpectedOut, serde.to_string(*var));
tr_variantClear(&val);
tr_variantClear(&*var);
}
TEST_F(VariantTest, bencMalformedNoEnding)
{
auto constexpr In = "l1:a1:b1:c"sv;
tr_variant val;
EXPECT_FALSE(tr_variantFromBuf(&val, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, In));
static auto constexpr In = "l1:a1:b1:c"sv;
auto serde = tr_variant_serde::benc();
auto const var = serde.inplace().parse(In);
EXPECT_FALSE(var.has_value());
}
TEST_F(VariantTest, bencMalformedIncompleteString)
{
auto constexpr In = "1:"sv;
tr_variant val;
EXPECT_FALSE(tr_variantFromBuf(&val, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, In));
static auto constexpr In = "1:"sv;
auto serde = tr_variant_serde::benc();
auto const var = serde.inplace().parse(In);
EXPECT_FALSE(var.has_value());
}
TEST_F(VariantTest, bencToJson)
@ -359,13 +361,15 @@ TEST_F(VariantTest, bencToJson)
R"({"args":{"status":[],"status2":[]},"result":"success"})"sv } }
};
auto benc_serde = tr_variant_serde::benc();
auto json_serde = tr_variant_serde::json();
benc_serde.inplace();
json_serde.compact();
for (auto const& test : Tests)
{
tr_variant top;
tr_variantFromBuf(&top, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, test.benc);
auto const str = tr_variantToStr(&top, TR_VARIANT_FMT_JSON_LEAN);
EXPECT_EQ(test.expected, stripWhitespace(str));
auto top = benc_serde.parse(test.benc).value_or(tr_variant{});
EXPECT_EQ(test.expected, stripWhitespace(json_serde.to_string(top)));
tr_variantClear(&top);
}
}
@ -433,15 +437,11 @@ TEST_F(VariantTest, stackSmash)
std::string const in = std::string(Depth, 'l') + std::string(Depth, 'e');
// confirm that it fails instead of crashing
char const* end = nullptr;
tr_variant val;
tr_error* error = nullptr;
auto ok = tr_variantFromBuf(&val, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, in, &end, &error);
EXPECT_NE(nullptr, error);
EXPECT_EQ(E2BIG, error->code);
EXPECT_FALSE(ok);
tr_error_clear(&error);
auto serde = tr_variant_serde::benc();
auto var = serde.inplace().parse(in);
EXPECT_FALSE(var.has_value());
EXPECT_NE(nullptr, serde.error_);
EXPECT_EQ(E2BIG, serde.error_ != nullptr ? serde.error_->code : 0);
}
TEST_F(VariantTest, boolAndIntRecast)
@ -547,24 +547,23 @@ TEST_F(VariantTest, dictFindType)
TEST_F(VariantTest, variantFromBufFuzz)
{
auto benc_serde = tr_variant_serde::json();
auto json_serde = tr_variant_serde::json();
auto buf = std::vector<char>{};
for (size_t i = 0; i < 100000; ++i)
{
buf.resize(tr_rand_int(4096U));
tr_rand_buffer(std::data(buf), std::size(buf));
// std::cerr << '[' << tr_base64_encode({ std::data(buf), std::size(buf) }) << ']' << std::endl;
if (auto top = tr_variant{};
tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, buf, nullptr, nullptr))
if (auto var = benc_serde.inplace().parse(buf); var)
{
tr_variantClear(&top);
tr_variantClear(&*var);
}
if (auto top = tr_variant{};
tr_variantFromBuf(&top, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, buf, nullptr, nullptr))
if (auto var = json_serde.inplace().parse(buf); var)
{
tr_variantClear(&top);
tr_variantClear(&*var);
}
}
}

View File

@ -356,20 +356,20 @@ int tr_main(int argc, char* argv[])
return EXIT_FAILURE;
}
auto serde = tr_variant_serde::benc();
for (auto const& filename : options.files)
{
tr_variant top;
bool changed = false;
tr_error* error = nullptr;
fmt::print("{:s}\n", filename);
if (!tr_variantFromFile(&top, TR_VARIANT_PARSE_BENC, filename, &error))
auto otop = serde.parse_file(filename);
if (!otop)
{
fmt::print("\tError reading file: {:s}\n", error->message);
tr_error_free(error);
fmt::print("\tError reading file: {:s}\n", serde.error_->message);
continue;
}
auto& top = *otop;
if (options.deleteme != nullptr)
{
@ -394,7 +394,7 @@ int tr_main(int argc, char* argv[])
if (changed)
{
++changedCount;
tr_variantToFile(&top, TR_VARIANT_FMT_BENC, filename);
serde.to_file(top, filename);
}
tr_variantClear(&top);

View File

@ -2149,7 +2149,6 @@ static void filterIds(tr_variant* top, Config& config)
}
static int processResponse(char const* rpcurl, std::string_view response, Config& config)
{
auto top = tr_variant{};
auto status = int{ EXIT_SUCCESS };
if (config.debug)
@ -2163,13 +2162,14 @@ static int processResponse(char const* rpcurl, std::string_view response, Config
return status;
}
if (!tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, response))
if (auto otop = tr_variant_serde::json().inplace().parse(response); !otop)
{
tr_logAddWarn(fmt::format("Unable to parse response '{}'", response));
status |= EXIT_FAILURE;
}
else
{
auto& top = *otop;
int64_t tag = -1;
auto sv = std::string_view{};
@ -2258,14 +2258,14 @@ static int processResponse(char const* rpcurl, std::string_view response, Config
}
}
}
tr_variantClear(&top);
}
}
else
{
status |= EXIT_FAILURE;
}
tr_variantClear(&top);
}
return status;
@ -2339,8 +2339,7 @@ static void tr_curl_easy_cleanup(CURL* curl)
static int flush(char const* rpcurl, tr_variant* benc, Config& config)
{
int status = EXIT_SUCCESS;
auto const json = tr_variantToStr(benc, TR_VARIANT_FMT_JSON_LEAN);
auto const json = tr_variant_serde::json().compact().to_string(*benc);
auto const scheme = config.use_ssl ? "https"sv : "http"sv;
auto const rpcurl_http = fmt::format(FMT_STRING("{:s}://{:s}"), scheme, rpcurl);
@ -2355,6 +2354,7 @@ static int flush(char const* rpcurl, tr_variant* benc, Config& config)
fmt::print(stderr, "posting:\n--------\n{:s}\n--------\n", json);
}
auto status = EXIT_SUCCESS;
if (auto const res = curl_easy_perform(curl); res != CURLE_OK)
{
tr_logAddWarn(fmt::format(" ({}) {}", rpcurl_http, curl_easy_strerror(res)));

View File

@ -359,12 +359,13 @@ void doScrape(tr_torrent_metainfo const& metainfo)
}
// print it out
auto top = tr_variant{};
if (!tr_variantFromBuf(&top, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, response.body))
auto otop = tr_variant_serde::benc().inplace().parse(response.body);
if (!!otop)
{
fmt::print("error parsing scrape response\n");
continue;
}
auto& top = *otop;
bool matched = false;
if (tr_variant* files = nullptr; tr_variantDictFindDict(&top, TR_KEY_files, &files))