diff --git a/Transmission.xcodeproj/project.pbxproj b/Transmission.xcodeproj/project.pbxproj index 3a43b0808..1fb391ef0 100644 --- a/Transmission.xcodeproj/project.pbxproj +++ b/Transmission.xcodeproj/project.pbxproj @@ -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 = ""; }; A25964A5106D73A800453B31 /* announcer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = announcer.h; sourceTree = ""; }; A25BFD63167BED3B0039D1AA /* variant-benc.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "variant-benc.cc"; sourceTree = ""; }; - A25BFD64167BED3B0039D1AA /* variant-common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "variant-common.h"; sourceTree = ""; }; A25BFD65167BED3B0039D1AA /* variant-json.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "variant-json.cc"; sourceTree = ""; }; A25BFD67167BED3B0039D1AA /* variant.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = variant.cc; sourceTree = ""; }; A25BFD68167BED3B0039D1AA /* variant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = variant.h; sourceTree = ""; }; @@ -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 */, diff --git a/daemon/daemon.cc b/daemon/daemon.cc index d0747fd9d..1e345661b 100644 --- a/daemon/daemon.cc +++ b/daemon/daemon.cc @@ -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; } diff --git a/gtk/Application.cc b/gtk/Application.cc index c37356277..a7f1c4901 100644 --- a/gtk/Application.cc +++ b/gtk/Application.cc @@ -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) diff --git a/libtransmission/CMakeLists.txt b/libtransmission/CMakeLists.txt index 5ef5be2a4..82d3efebd 100644 --- a/libtransmission/CMakeLists.txt +++ b/libtransmission/CMakeLists.txt @@ -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 diff --git a/libtransmission/announce-list.cc b/libtransmission/announce-list.cc index 646ee2eed..c5dd52054 100644 --- a/libtransmission/announce-list.cc +++ b/libtransmission/announce-list.cc @@ -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; } diff --git a/libtransmission/benc.h b/libtransmission/benc.h index dd9b4f20e..8ba9f1d86 100644 --- a/libtransmission/benc.h +++ b/libtransmission/benc.h @@ -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; } diff --git a/libtransmission/makemeta.cc b/libtransmission/makemeta.cc index cdf15d3d2..0f98979bb 100644 --- a/libtransmission/makemeta.cc +++ b/libtransmission/makemeta.cc @@ -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; } diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc index 6b72e6065..9c2d9087a 100644 --- a/libtransmission/peer-msgs.cc +++ b/libtransmission/peer-msgs.cc @@ -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); } diff --git a/libtransmission/resume.cc b/libtransmission/resume.cc index 379ace7c1..8051f1b8a 100644 --- a/libtransmission/resume.cc +++ b/libtransmission/resume.cc @@ -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{}; - 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); diff --git a/libtransmission/rpc-server.cc b/libtransmission/rpc-server.cc index 199248d13..6e4d928ec 100644 --- a/libtransmission/rpc-server.cc +++ b/libtransmission/rpc-server.cc @@ -351,7 +351,7 @@ void rpc_response_func(tr_session* /*session*/, tr_variant* content, void* user_ { auto* data = static_cast(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); } } diff --git a/libtransmission/session.cc b/libtransmission/session.cc index 0080615c5..492a9b1fe 100644 --- a/libtransmission/session.cc +++ b/libtransmission/session.cc @@ -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); diff --git a/libtransmission/stats.cc b/libtransmission/stats.cc index b37a8a064..eb3eacb18 100644 --- a/libtransmission/stats.cc +++ b/libtransmission/stats.cc @@ -14,52 +14,49 @@ using namespace std::literals; +namespace +{ +std::optional 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, 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); } diff --git a/libtransmission/torrent-magnet.cc b/libtransmission/torrent-magnet.cc index dce76f493..4f62208fb 100644 --- a/libtransmission/torrent-magnet.cc +++ b/libtransmission/torrent-magnet.cc @@ -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{}; diff --git a/libtransmission/tr-dht.cc b/libtransmission/tr-dht.cc index c6040b89b..2884adabd 100644 --- a/libtransmission/tr-dht.cc +++ b/libtransmission/tr-dht.cc @@ -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); diff --git a/libtransmission/variant-benc.cc b/libtransmission/variant-benc.cc index ed0fcc206..ddc3658fa 100644 --- a/libtransmission/variant-benc.cc +++ b/libtransmission/variant-benc.cc @@ -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 stack_; std::optional 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_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(vout); auto const [buf, buflen] = out->reserve_space(64U); auto* walk = reinterpret_cast(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(vout)->add(val->val.b ? "i1e"sv : "i0e"sv); + static_cast(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(vout), sv); + saveStringImpl(static_cast(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{}; - 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(vout), { std::data(buf), static_cast(out - std::data(buf)) }); } -void saveDictBeginFunc(tr_variant const* /*val*/, void* vbuf) +void saveDictBeginFunc(tr_variant const& /*val*/, void* vbuf) { static_cast(vbuf)->push_back('d'); } -void saveListBeginFunc(tr_variant const* /*val*/, void* vbuf) +void saveListBeginFunc(tr_variant const& /*val*/, void* vbuf) { static_cast(vbuf)->push_back('l'); } -void saveContainerEndFunc(tr_variant const* /*val*/, void* vbuf) +void saveContainerEndFunc(tr_variant const& /*val*/, void* vbuf) { static_cast(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(); } diff --git a/libtransmission/variant-common.h b/libtransmission/variant-common.h deleted file mode 100644 index a8029ad54..000000000 --- a/libtransmission/variant-common.h +++ /dev/null @@ -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 // int64_t -#include -#include -#include - -#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 tr_bencParseInt(std::string_view* benc_inout); - -/** @brief Private function that's exposed here only for unit tests */ -[[nodiscard]] std::optional 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); diff --git a/libtransmission/variant-json.cc b/libtransmission/variant-json.cc index 33af3bfef..a49de9c77 100644 --- a/libtransmission/variant-json.cc +++ b/libtransmission/variant-json.cc @@ -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 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_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 = ⊤ - 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(std::data(input)), std::size(input)); - /* parse it */ - jsonsl_feed(jsn, static_cast(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{}; - 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(vdata); data->out.add(std::data(buf), static_cast(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(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(vdata); @@ -553,13 +541,13 @@ void jsonRealFunc(tr_variant const* val, void* vdata) auto* walk = reinterpret_cast(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(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(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(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(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(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)) diff --git a/libtransmission/variant.cc b/libtransmission/variant.cc index 0b443d326..d7383a3d4 100644 --- a/libtransmission/variant.cc +++ b/libtransmission/variant.cc @@ -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(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(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_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_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{}; 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{}; tr_file_read(filename, buf, error)) - { - return tr_variantFromBuf(setme, opts, buf, nullptr, error); - } - - return false; + return true; } diff --git a/libtransmission/variant.h b/libtransmission/variant.h index b2a2280d5..85e73cba9 100644 --- a/libtransmission/variant.h +++ b/libtransmission/variant.h @@ -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 -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(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 parse(std::string_view input); + + template + [[nodiscard]] std::optional parse(CharSpan const& input) + { + return parse(std::string_view{ std::data(input), std::size(input) }); + } + + [[nodiscard]] std::optional 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 parse_json(std::string_view input); + [[nodiscard]] std::optional 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 { diff --git a/qt/Prefs.cc b/qt/Prefs.cc index d6272fb0c..d0a3406f7 100644 --- a/qt/Prefs.cc +++ b/qt/Prefs.cc @@ -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, ¤t_settings); - tr_variantToFile(&file_settings, TR_VARIANT_FMT_JSON, file.fileName().toStdString()); - tr_variantClear(&file_settings); + tr_variantMergeDicts(&*settings, ¤t_settings); + serde.to_file(*settings, filename); + tr_variantClear(&*settings); // cleanup tr_variantClear(¤t_settings); diff --git a/qt/RpcClient.cc b/qt/RpcClient.cc index 2116c292a..4396627c6 100644 --- a/qt/RpcClient.cc +++ b/qt/RpcClient.cc @@ -137,7 +137,7 @@ void RpcClient::sendNetworkRequest(TrVariantPtr json, QFutureInterfacepost(*request_, json_data); reply->setProperty(RequestDataPropertyKey, QVariant::fromValue(json)); reply->setProperty(RequestFutureinterfacePropertyKey, QVariant::fromValue(promise)); @@ -164,7 +164,7 @@ void RpcClient::sendLocalRequest(TrVariantPtr json, QFutureInterfaceverbose_) { - 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); diff --git a/tests/libtransmission/dht-test.cc b/tests/libtransmission/dht-test.cc index a43d44712..dc722c637 100644 --- a/tests/libtransmission/dht-test.cc +++ b/tests/libtransmission/dht-test.cc @@ -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); } }; diff --git a/tests/libtransmission/json-test.cc b/tests/libtransmission/json-test.cc index 20ff62a77..474efac60 100644 --- a/tests/libtransmission/json-test.cc +++ b/tests/libtransmission/json-test.cc @@ -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); } diff --git a/tests/libtransmission/makemeta-test.cc b/tests/libtransmission/makemeta-test.cc index 7036c832e..53b8ec5aa 100644 --- a/tests/libtransmission/makemeta-test.cc +++ b/tests/libtransmission/makemeta-test.cc @@ -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) diff --git a/tests/libtransmission/variant-test.cc b/tests/libtransmission/variant-test.cc index c52e5037e..0df8e4bf6 100644 --- a/tests/libtransmission/variant-test.cc +++ b/tests/libtransmission/variant-test.cc @@ -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{}; 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); } } } diff --git a/utils/edit.cc b/utils/edit.cc index 159ae5938..564b87b8e 100644 --- a/utils/edit.cc +++ b/utils/edit.cc @@ -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); diff --git a/utils/remote.cc b/utils/remote.cc index 9d9a77558..6c43bf92f 100644 --- a/utils/remote.cc +++ b/utils/remote.cc @@ -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))); diff --git a/utils/show.cc b/utils/show.cc index cb97b350e..f58c81aeb 100644 --- a/utils/show.cc +++ b/utils/show.cc @@ -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))