From 450f1dcadc43641bd807624c263b1e8a54ba22ec Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 19 Oct 2022 11:42:08 -0500 Subject: [PATCH] refactor: extract `tr_buffer` class from `tr_peerIo` (#3986) --- Transmission.xcodeproj/project.pbxproj | 4 + libtransmission/CMakeLists.txt | 1 + libtransmission/handshake.cc | 75 +++--- libtransmission/peer-io.cc | 166 ++++++------ libtransmission/peer-io.h | 30 +-- libtransmission/peer-msgs.cc | 219 ++++++++-------- libtransmission/tr-buffer.h | 335 +++++++++++++++++++++++++ libtransmission/variant-benc.cc | 61 ++--- libtransmission/variant-common.h | 4 +- libtransmission/variant-json.cc | 163 ++++++------ libtransmission/variant.cc | 33 +-- libtransmission/variant.h | 6 +- qt/RpcClient.h | 1 - tests/libtransmission/CMakeLists.txt | 1 + tests/libtransmission/buffer-test.cc | 63 +++++ 15 files changed, 731 insertions(+), 431 deletions(-) create mode 100644 libtransmission/tr-buffer.h create mode 100644 tests/libtransmission/buffer-test.cc diff --git a/Transmission.xcodeproj/project.pbxproj b/Transmission.xcodeproj/project.pbxproj index 601af3397..9bb4cb2d0 100644 --- a/Transmission.xcodeproj/project.pbxproj +++ b/Transmission.xcodeproj/project.pbxproj @@ -184,6 +184,7 @@ A267927C130DFF2700CB7464 /* libutp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A2E38544130DFEE3001F501B /* libutp.a */; }; A2679294130E00A000CB7464 /* tr-utp.cc in Sources */ = {isa = PBXBuildFile; fileRef = A2679292130E00A000CB7464 /* tr-utp.cc */; }; A2679295130E00A000CB7464 /* tr-utp.h in Headers */ = {isa = PBXBuildFile; fileRef = A2679293130E00A000CB7464 /* tr-utp.h */; }; + A263C6B1F6718E2486DB20E0 /* tr-buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = A263C6B1F6718E2486DB20E1 /* tr-buffer.h */; }; A26AF21A0D2DA35A00FF7140 /* FileOutlineController.mm in Sources */ = {isa = PBXBuildFile; fileRef = A26AF2190D2DA35A00FF7140 /* FileOutlineController.mm */; }; A26AF27E0D2DBDDF00FF7140 /* AddWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = A26AF27C0D2DBDDF00FF7140 /* AddWindow.xib */; }; A26AF2840D2DC27C00FF7140 /* AddWindowController.mm in Sources */ = {isa = PBXBuildFile; fileRef = A26AF2830D2DC27C00FF7140 /* AddWindowController.mm */; }; @@ -909,6 +910,7 @@ A2661D3B12D0E51B004F69D5 /* FilterBarView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FilterBarView.mm; sourceTree = ""; }; A2679292130E00A000CB7464 /* tr-utp.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "tr-utp.cc"; sourceTree = ""; }; A2679293130E00A000CB7464 /* tr-utp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "tr-utp.h"; sourceTree = ""; }; + A263C6B1F6718E2486DB20E1 /* tr-buffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "tr-buffer.h"; sourceTree = ""; }; A26AF1050D2855FC00FF7140 /* ru */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; A26AF1070D2855FC00FF7140 /* ru */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; A26AF2180D2DA35A00FF7140 /* FileOutlineController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileOutlineController.h; sourceTree = ""; }; @@ -1717,6 +1719,7 @@ A284214212DA663E00FBDDBB /* tr-udp.cc */, A2679292130E00A000CB7464 /* tr-utp.cc */, A2679293130E00A000CB7464 /* tr-utp.h */, + A263C6B1F6718E2486DB20E1 /* tr-buffer.h */, BEFC1DF50C07861A00B0BB3C /* transmission.h */, A24621360C769CF400088E81 /* trevent.cc */, A24621350C769CF400088E81 /* trevent.h */, @@ -2204,6 +2207,7 @@ 11524394C75E57E52CD9ADF2 /* dns-ev.h in Headers */, C1077A4F183EB29600634C22 /* error.h in Headers */, A2679295130E00A000CB7464 /* tr-utp.h in Headers */, + A2679295130E00A000CB7464 /* tr-buffer.h in Headers */, A23F29A1132A447400E9A83B /* announcer-common.h in Headers */, A2EE726F14DCCC950093C99A /* port-forwarding-natpmp.h in Headers */, A2D77451154CC25700A62B93 /* WebSeedTableView.h in Headers */, diff --git a/libtransmission/CMakeLists.txt b/libtransmission/CMakeLists.txt index bf3360b67..d21c4119b 100644 --- a/libtransmission/CMakeLists.txt +++ b/libtransmission/CMakeLists.txt @@ -149,6 +149,7 @@ set(${PROJECT_NAME}_PUBLIC_HEADERS tr-getopt.h tr-macros.h tr-strbuf.h + tr-buffer.h transmission.h utils.h variant.h diff --git a/libtransmission/handshake.cc b/libtransmission/handshake.cc index 32740c280..6a3af34a7 100644 --- a/libtransmission/handshake.cc +++ b/libtransmission/handshake.cc @@ -10,8 +10,6 @@ #include #include -#include - #include #include "transmission.h" @@ -23,6 +21,7 @@ #include "peer-io.h" #include "timer.h" #include "tr-assert.h" +#include "tr-buffer.h" #include "utils.h" using namespace std::literals; @@ -376,13 +375,12 @@ static constexpr uint32_t getCryptoSelect(tr_encryption_mode encryption_mode, ui static ReadState readYb(tr_handshake* handshake, tr_peerIo* peer_io) { - auto const* const peek = peer_io->peek(std::size(HandshakeName)); - if (peek == nullptr) + if (peer_io->readBufferSize() < std::size(HandshakeName)) { return READ_LATER; } - bool const is_encrypted = !std::equal(std::begin(HandshakeName), std::end(HandshakeName), peek); + bool const is_encrypted = !peer_io->readBufferStartsWith(HandshakeName); auto peer_public_key = DH::key_bigend_t{}; if (is_encrypted && (peer_io->readBufferSize() < std::size(peer_public_key))) { @@ -405,11 +403,10 @@ static ReadState readYb(tr_handshake* handshake, tr_peerIo* peer_io) /* now send these: HASH('req1', S), HASH('req2', SKEY) xor HASH('req3', S), * ENCRYPT(VC, crypto_provide, len(PadC), PadC, len(IA)), ENCRYPT(IA) */ - evbuffer* const outbuf = evbuffer_new(); + auto outbuf = libtransmission::Buffer{}; /* HASH('req1', S) */ - auto const req1 = tr_sha1::digest("req1"sv, handshake->dh.secret()); - evbuffer_add(outbuf, std::data(req1), std::size(req1)); + outbuf.add(tr_sha1::digest("req1"sv, handshake->dh.secret())); auto const info_hash = peer_io->torrentHash(); if (!info_hash) @@ -422,29 +419,29 @@ static ReadState readYb(tr_handshake* handshake, tr_peerIo* peer_io) { auto const req2 = tr_sha1::digest("req2"sv, *info_hash); auto const req3 = tr_sha1::digest("req3"sv, handshake->dh.secret()); - auto buf = tr_sha1_digest_t{}; - for (size_t i = 0, n = std::size(buf); i < n; ++i) + auto x_or = tr_sha1_digest_t{}; + for (size_t i = 0, n = std::size(x_or); i < n; ++i) { - buf[i] = req2[i] ^ req3[i]; + x_or[i] = req2[i] ^ req3[i]; } - evbuffer_add(outbuf, std::data(buf), std::size(buf)); + outbuf.add(x_or); } /* ENCRYPT(VC, crypto_provide, len(PadC), PadC * PadC is reserved for future extensions to the handshake... * standard practice at this time is for it to be zero-length */ - peer_io->writeBuf(outbuf, false); + peer_io->write(outbuf, false); peer_io->encryptInit(peer_io->isIncoming(), handshake->dh, *info_hash); - evbuffer_add(outbuf, std::data(VC), std::size(VC)); - evbuffer_add_uint32(outbuf, handshake->cryptoProvide()); - evbuffer_add_uint16(outbuf, 0); + outbuf.add(VC); + outbuf.addUint32(handshake->cryptoProvide()); + outbuf.addUint16(0); /* ENCRYPT len(IA)), ENCRYPT(IA) */ if (auto msg = std::array{}; buildHandshakeMessage(handshake, std::data(msg))) { - evbuffer_add_uint16(outbuf, std::size(msg)); - evbuffer_add(outbuf, std::data(msg), std::size(msg)); + outbuf.addUint16(std::size(msg)); + outbuf.add(msg); handshake->haveSentBitTorrentHandshake = true; } else @@ -454,10 +451,7 @@ static ReadState readYb(tr_handshake* handshake, tr_peerIo* peer_io) /* send it */ setReadState(handshake, AWAITING_VC); - peer_io->writeBuf(outbuf, false); - - /* cleanup */ - evbuffer_free(outbuf); + peer_io->write(outbuf, false); return READ_NOW; } @@ -473,14 +467,13 @@ static ReadState readVC(tr_handshake* handshake, tr_peerIo* peer_io) for (size_t i = 0; i < PadbMaxlen; ++i) { - auto const* const peek = peer_io->peek(std::size(needle)); - if (peek == nullptr) + if (peer_io->readBufferSize() < std::size(needle)) { tr_logAddTraceHand(handshake, "not enough bytes... returning read_more"); return READ_LATER; } - if (std::equal(std::begin(needle), std::end(needle), peek)) + if (peer_io->readBufferStartsWith(needle)) { tr_logAddTraceHand(handshake, "got it!"); // We already know it's a match; now we just need to @@ -561,17 +554,14 @@ static ReadState readHandshake(tr_handshake* handshake, tr_peerIo* peer_io) { tr_logAddTraceHand(handshake, fmt::format("payload: need {}, got {}", IncomingHandshakeLen, peer_io->readBufferSize())); - auto const* const peek = peer_io->peek(IncomingHandshakeLen); - if (peek == nullptr) + if (peer_io->readBufferSize() < IncomingHandshakeLen) { return READ_LATER; } handshake->haveReadAnythingFromPeer = true; - // peek instead of reading, because if we decide the handshake is - // encrypted we'll pass the unconsumed buffer to AWAITING_YA - if (std::equal(std::begin(HandshakeName), std::end(HandshakeName), peek)) // unencrypted + if (peer_io->readBufferStartsWith(HandshakeName)) // unencrypted { if (handshake->encryption_mode == TR_ENCRYPTION_REQUIRED) { @@ -710,14 +700,13 @@ static ReadState readPadA(tr_handshake* handshake, tr_peerIo* peer_io) for (size_t i = 0; i < PadaMaxlen; ++i) { - auto const* const peek = peer_io->peek(std::size(needle)); - if (peek == nullptr) + if (peer_io->readBufferSize() < std::size(needle)) { tr_logAddTraceHand(handshake, "not enough bytes... returning read_more"); return READ_LATER; } - if (std::equal(std::begin(needle), std::end(needle), peek)) + if (peer_io->readBufferStartsWith(needle)) { tr_logAddTraceHand(handshake, "found it... looking setting to awaiting_crypto_provide"); peer_io->readBufferDrain(std::size(needle)); @@ -839,11 +828,11 @@ static ReadState readIA(tr_handshake* handshake, tr_peerIo* peer_io) **/ peer_io->encryptInit(peer_io->isIncoming(), handshake->dh, *peer_io->torrentHash()); - evbuffer* const outbuf = evbuffer_new(); + auto outbuf = libtransmission::Buffer{}; // send VC tr_logAddTraceHand(handshake, "sending vc"); - evbuffer_add(outbuf, std::data(VC), std::size(VC)); + outbuf.add(VC); /* send crypto_select */ uint32_t const crypto_select = getCryptoSelect(handshake->encryption_mode, handshake->crypto_provide); @@ -851,12 +840,11 @@ static ReadState readIA(tr_handshake* handshake, tr_peerIo* peer_io) if (crypto_select != 0) { tr_logAddTraceHand(handshake, fmt::format("selecting crypto mode '{}'", crypto_select)); - evbuffer_add_uint32(outbuf, crypto_select); + outbuf.addUint32(crypto_select); } else { tr_logAddTraceHand(handshake, "peer didn't offer an encryption mode we like."); - evbuffer_free(outbuf); return tr_handshakeDone(handshake, false); } @@ -865,15 +853,13 @@ static ReadState readIA(tr_handshake* handshake, tr_peerIo* peer_io) /* ENCRYPT(VC, crypto_provide, len(PadD), PadD * PadD is reserved for future extensions to the handshake... * standard practice at this time is for it to be zero-length */ - { - uint16_t const len = 0; - evbuffer_add_uint16(outbuf, len); - } + outbuf.addUint16(0); /* maybe de-encrypt our connection */ if (crypto_select == CryptoProvidePlaintext) { - peer_io->writeBuf(outbuf, false); + peer_io->write(outbuf, false); + TR_ASSERT(std::empty(outbuf)); } tr_logAddTraceHand(handshake, "sending handshake"); @@ -881,7 +867,7 @@ static ReadState readIA(tr_handshake* handshake, tr_peerIo* peer_io) /* send our handshake */ if (auto msg = std::array{}; buildHandshakeMessage(handshake, std::data(msg))) { - evbuffer_add(outbuf, std::data(msg), std::size(msg)); + outbuf.add(msg); handshake->haveSentBitTorrentHandshake = true; } else @@ -890,8 +876,7 @@ static ReadState readIA(tr_handshake* handshake, tr_peerIo* peer_io) } /* send it out */ - peer_io->writeBuf(outbuf, false); - evbuffer_free(outbuf); + peer_io->write(outbuf, false); /* now await the handshake */ setState(handshake, AWAITING_PAYLOAD_STREAM); diff --git a/libtransmission/peer-io.cc b/libtransmission/peer-io.cc index 1c8259b33..65578ffff 100644 --- a/libtransmission/peer-io.cc +++ b/libtransmission/peer-io.cc @@ -131,9 +131,9 @@ static void canReadWrapper(tr_peerIo* io_in) while (!done && !err) { size_t piece = 0; - size_t const old_len = evbuffer_get_length(io->inbuf.get()); + size_t const old_len = io->readBufferSize(); int const ret = io->canRead(io.get(), io->userData, &piece); - size_t const used = old_len - evbuffer_get_length(io->inbuf.get()); + size_t const used = old_len - io->readBufferSize(); unsigned int const overhead = guessPacketOverhead(used); if (piece != 0 || piece != used) @@ -157,7 +157,7 @@ static void canReadWrapper(tr_peerIo* io_in) switch (ret) { case READ_NOW: - if (evbuffer_get_length(io->inbuf.get()) != 0) + if (io->readBufferSize() != 0) { continue; } @@ -190,7 +190,7 @@ static void event_read_cb(evutil_socket_t fd, short /*event*/, void* vio) io->pendingEvents &= ~EV_READ; - unsigned int const curlen = evbuffer_get_length(io->inbuf.get()); + unsigned int const curlen = io->readBufferSize(); unsigned int howmuch = curlen >= max ? 0 : max - curlen; howmuch = io->bandwidth().clamp(TR_DOWN, howmuch); @@ -203,10 +203,8 @@ static void event_read_cb(evutil_socket_t fd, short /*event*/, void* vio) return; } - EVUTIL_SET_SOCKET_ERROR(0); - auto const res = evbuffer_read(io->inbuf.get(), fd, (int)howmuch); - int const e = EVUTIL_SOCKET_ERROR(); - + tr_error* error = nullptr; + auto const res = io->inbuf.addSocket(fd, howmuch, &error); if (res > 0) { io->setEnabled(dir, true); @@ -222,36 +220,33 @@ static void event_read_cb(evutil_socket_t fd, short /*event*/, void* vio) { what |= BEV_EVENT_EOF; } - else if (res == -1) + if (error != nullptr) { - if (e == EAGAIN || e == EINTR) + if (error->code == EAGAIN || error->code == EINTR) { io->setEnabled(dir, true); return; } what |= BEV_EVENT_ERROR; - } - tr_logAddDebugIo( - io, - fmt::format("event_read_cb err: res:{}, what:{}, errno:{} ({})", res, what, e, tr_net_strerror(e))); + tr_logAddDebugIo( + io, + fmt::format("event_read_cb err: res:{}, what:{}, errno:{} ({})", res, what, error->code, error->message)); + } if (io->gotError != nullptr) { io->gotError(io, what, io->userData); } } + + tr_error_clear(&error); } static int tr_evbuffer_write(tr_peerIo* io, int fd, size_t howmuch) { - EVUTIL_SET_SOCKET_ERROR(0); - int const n = evbuffer_write_atmost(io->outbuf.get(), fd, howmuch); - int const e = EVUTIL_SOCKET_ERROR(); - tr_logAddTraceIo(io, fmt::format("wrote {} to peer ({})", n, (n == -1 ? tr_net_strerror(e).c_str() : ""))); - - return n; + return io->outbuf.toSocket(fd, howmuch); } static void event_write_cb(evutil_socket_t fd, short /*event*/, void* vio) @@ -265,10 +260,10 @@ static void event_write_cb(evutil_socket_t fd, short /*event*/, void* vio) tr_logAddTraceIo(io, "libevent says this peer is ready to write"); - // Write as much as possible. Since the socket is non-blocking, write() - // will return if it can't write any more data without blocking. + /* Write as much as possible, since the socket is non-blocking, write() will + * return if it can't write any more data without blocking */ auto constexpr Dir = TR_UP; - size_t const howmuch = io->bandwidth().clamp(Dir, evbuffer_get_length(io->outbuf.get())); + auto const howmuch = io->bandwidth().clamp(Dir, std::size(io->outbuf)); // if we don't have any bandwidth left, stop writing if (howmuch < 1) @@ -283,7 +278,7 @@ static void event_write_cb(evutil_socket_t fd, short /*event*/, void* vio) auto const should_retry = n_written == -1 && (err == 0 || err == EAGAIN || err == EINTR || err == EINPROGRESS); // schedule another write if we have more data to write & think future writes would succeed - if ((evbuffer_get_length(io->outbuf.get()) != 0) && (n_written > 0 || should_retry)) + if (!std::empty(io->outbuf) && (n_written > 0 || should_retry)) { io->setEnabled(Dir, true); } @@ -323,12 +318,7 @@ static void maybeSetCongestionAlgorithm(tr_socket_t socket, std::string const& a void tr_peerIo::readBufferAdd(void const* data, size_t n_bytes) { - if (auto const rc = evbuffer_add(inbuf.get(), data, n_bytes); rc < 0) - { - tr_logAddWarn(_("Couldn't write to peer")); - return; - } - + inbuf.add(data, n_bytes); setEnabled(TR_DOWN, true); canReadWrapper(this); } @@ -348,7 +338,7 @@ static void utp_on_writable(tr_peerIo* io) tr_logAddTraceIo(io, "libutp says this peer is ready to write"); int const n = tr_peerIoTryWrite(io, SIZE_MAX); - io->setEnabled(TR_UP, n != 0 && evbuffer_get_length(io->outbuf.get()) != 0); + io->setEnabled(TR_UP, n != 0 && !std::empty(io->outbuf)); } static void utp_on_state_change(tr_peerIo* const io, int const state) @@ -807,7 +797,7 @@ static unsigned int getDesiredOutputBufferSize(tr_peerIo const* io, uint64_t now size_t tr_peerIo::getWriteBufferSpace(uint64_t now) const { size_t const desired_len = getDesiredOutputBufferSize(this, now); - size_t const current_len = evbuffer_get_length(outbuf.get()); + size_t const current_len = std::size(outbuf); return desired_len > current_len ? desired_len - current_len : 0U; } @@ -815,57 +805,33 @@ size_t tr_peerIo::getWriteBufferSpace(uint64_t now) const *** **/ -static inline void processBuffer(tr_peerIo& io, evbuffer* buffer, size_t offset, size_t size) +void tr_peerIo::write(libtransmission::Buffer& buf, bool is_piece_data) { - struct evbuffer_ptr pos = {}; - auto iov = evbuffer_iovec{}; + auto const n_bytes = std::size(buf); - evbuffer_ptr_set(buffer, &pos, offset, EVBUFFER_PTR_SET); - - do + auto const old_size = std::size(outbuf); + outbuf.add(buf); + for (auto iter = std::begin(outbuf) + old_size, end = std::end(outbuf); iter != end; ++iter) { - if (evbuffer_peek(buffer, size, &pos, &iov, 1) <= 0) - { - break; - } - - io.encrypt(iov.iov_len, iov.iov_base); - - TR_ASSERT(size >= iov.iov_len); - size -= iov.iov_len; - } while (evbuffer_ptr_set(buffer, &pos, iov.iov_len, EVBUFFER_PTR_ADD) == 0); - - TR_ASSERT(size == 0); -} - -void tr_peerIo::writeBuf(struct evbuffer* buf, bool is_piece_data) -{ - size_t const n_bytes = evbuffer_get_length(buf); - - if (isEncrypted()) - { - processBuffer(*this, buf, 0, n_bytes); + encrypt(1, &*iter); } - evbuffer_add_buffer(outbuf.get(), buf); outbuf_info.emplace_back(n_bytes, is_piece_data); } -void tr_peerIo::writeBytes(void const* writeme, size_t writeme_len, bool is_piece_data) +void tr_peerIo::writeBytes(void const* bytes, size_t n_bytes, bool is_piece_data) { - auto iov = evbuffer_iovec{}; - evbuffer_reserve_space(outbuf.get(), writeme_len, &iov, 1); - iov.iov_len = writeme_len; - memcpy(iov.iov_base, writeme, iov.iov_len); + auto const old_size = std::size(outbuf); - if (isEncrypted()) + outbuf.reserve(old_size + n_bytes); + outbuf.add(bytes, n_bytes); + + for (auto iter = std::begin(outbuf) + old_size, end = std::end(outbuf); iter != end; ++iter) { - encrypt(iov.iov_len, iov.iov_base); + encrypt(1, &*iter); } - evbuffer_commit_space(outbuf.get(), &iov, 1); - - outbuf_info.emplace_back(writeme_len, is_piece_data); + outbuf_info.emplace_back(n_bytes, is_piece_data); } /*** @@ -918,7 +884,7 @@ void tr_peerIo::readBytes(void* bytes, size_t byte_count) { TR_ASSERT(readBufferSize() >= byte_count); - evbuffer_remove(inbuf.get(), bytes, byte_count); + inbuf.toBuf(bytes, byte_count); if (isEncrypted()) { @@ -971,7 +937,7 @@ static int tr_peerIoTryRead(tr_peerIo* io, size_t howmuch) /* UTP_RBDrained notifies libutp that your read buffer is empty. * It opens up the congestion window by sending an ACK (soonish) * if one was not going to be sent. */ - if (evbuffer_get_length(io->inbuf.get()) == 0) + if (io->readBufferSize() == 0) { utp_read_drained(io->socket.handle.utp); } @@ -980,31 +946,38 @@ static int tr_peerIoTryRead(tr_peerIo* io, size_t howmuch) case TR_PEER_SOCKET_TYPE_TCP: { - EVUTIL_SET_SOCKET_ERROR(0); - res = evbuffer_read(io->inbuf.get(), io->socket.handle.tcp, (int)howmuch); - int const e = EVUTIL_SOCKET_ERROR(); + tr_error* error = nullptr; + res = io->inbuf.addSocket(io->socket.handle.tcp, howmuch, &error); - tr_logAddTraceIo(io, fmt::format("read {} from peer ({})", res, res == -1 ? tr_net_strerror(e).c_str() : "")); - - if (evbuffer_get_length(io->inbuf.get()) != 0) + if (io->readBufferSize() != 0) { canReadWrapper(io); } - if (res <= 0 && io->gotError != nullptr && e != EAGAIN && e != EINTR && e != EINPROGRESS) + if (error != nullptr) { - short what = BEV_EVENT_READING | BEV_EVENT_ERROR; - - if (res == 0) + if (error->code != EAGAIN && error->code != EINTR && error->code != EINPROGRESS && io->gotError != nullptr) { - what |= BEV_EVENT_EOF; + short what = BEV_EVENT_READING | BEV_EVENT_ERROR; + + if (res == 0) + { + what |= BEV_EVENT_EOF; + } + + tr_logAddTraceIo( + io, + fmt::format( + "tr_peerIoTryRead err: res:{} what:{}, errno:{} ({})", + res, + what, + error->code, + error->message)); + + io->gotError(io, what, io->userData); } - tr_logAddTraceIo( - io, - fmt::format("tr_peerIoTryRead err: res:{} what:{}, errno:{} ({})", res, what, e, tr_net_strerror(e))); - - io->gotError(io, what, io->userData); + tr_error_clear(&error); } break; @@ -1019,7 +992,7 @@ static int tr_peerIoTryRead(tr_peerIo* io, size_t howmuch) static int tr_peerIoTryWrite(tr_peerIo* io, size_t howmuch) { - auto const old_len = size_t{ evbuffer_get_length(io->outbuf.get()) }; + auto const old_len = std::size(io->outbuf); tr_logAddTraceIo(io, fmt::format("in tr_peerIoTryWrite {}", howmuch)); howmuch = std::min(howmuch, old_len); @@ -1033,16 +1006,17 @@ static int tr_peerIoTryWrite(tr_peerIo* io, size_t howmuch) switch (io->socket.type) { case TR_PEER_SOCKET_TYPE_UTP: - n = utp_write(io->socket.handle.utp, evbuffer_pullup(io->outbuf.get(), howmuch), howmuch); - - if (n > 0) { - evbuffer_drain(io->outbuf.get(), n); - didWriteWrapper(io, n); + auto iov = io->outbuf.vecs(howmuch); + n = utp_writev(io->socket.handle.utp, reinterpret_cast(std::data(iov)), std::size(iov)); + if (n > 0) + { + io->outbuf.drain(n); + didWriteWrapper(io, n); + } + break; } - break; - case TR_PEER_SOCKET_TYPE_TCP: { EVUTIL_SET_SOCKET_ERROR(0); diff --git a/libtransmission/peer-io.h b/libtransmission/peer-io.h index 4db7958f5..d3168414f 100644 --- a/libtransmission/peer-io.h +++ b/libtransmission/peer-io.h @@ -31,6 +31,7 @@ #include "peer-mse.h" #include "peer-socket.h" #include "tr-assert.h" +#include "tr-buffer.h" class tr_peerIo; struct tr_bandwidth; @@ -128,26 +129,17 @@ public: std::string addrStr() const; - [[nodiscard]] auto readBuffer() noexcept - { - return inbuf.get(); - } - void readBufferDrain(size_t byte_count); [[nodiscard]] auto readBufferSize() const noexcept { - return evbuffer_get_length(inbuf.get()); + return std::size(inbuf); } - [[nodiscard]] std::byte const* peek(size_t n_bytes) const noexcept + template + [[nodiscard]] auto readBufferStartsWith(T const& t) const noexcept { - if (readBufferSize() < n_bytes) - { - return nullptr; - } - - return reinterpret_cast(evbuffer_pullup(inbuf.get(), n_bytes)); + return inbuf.startsWith(t); } void readBufferAdd(void const* data, size_t n_bytes); @@ -155,8 +147,12 @@ public: int flushOutgoingProtocolMsgs(); int flush(tr_direction dir, size_t byte_limit); - void writeBytes(void const* writeme, size_t writeme_len, bool is_piece_data); - void writeBuf(struct evbuffer* buf, bool is_piece_data); + void writeBytes(void const* bytes, size_t n_bytes, bool is_piece_data); + + // Write all the data from `buf`. + // This is a destructive add: `buf` is empty after this call. + void write(libtransmission::Buffer& buf, bool is_piece_data); + size_t getWriteBufferSpace(uint64_t now) const; [[nodiscard]] auto hasBandwidthLeft(tr_direction dir) noexcept @@ -252,8 +248,8 @@ public: tr_net_error_cb gotError = nullptr; void* userData = nullptr; - tr_evbuffer_ptr const inbuf = tr_evbuffer_ptr{ evbuffer_new() }; - tr_evbuffer_ptr const outbuf = tr_evbuffer_ptr{ evbuffer_new() }; + libtransmission::Buffer inbuf; + libtransmission::Buffer outbuf; std::deque> outbuf_info; diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc index cda6b310b..e1b3d1cfb 100644 --- a/libtransmission/peer-msgs.cc +++ b/libtransmission/peer-msgs.cc @@ -16,7 +16,6 @@ #include #include -#include #include #include @@ -24,8 +23,8 @@ #include "transmission.h" #include "cache.h" -#include "crypto-utils.h" #include "completion.h" +#include "crypto-utils.h" #include "file.h" #include "log.h" #include "peer-io.h" @@ -37,6 +36,8 @@ #include "torrent-magnet.h" #include "torrent.h" #include "tr-assert.h" +#include "tr-buffer.h" +#include "tr-dht.h" #include "utils.h" #include "variant.h" #include "version.h" @@ -256,7 +257,6 @@ public: : tr_peerMsgs{ torrent_in, atom_in } , outMessagesBatchPeriod{ LowPriorityIntervalSecs } , torrent{ torrent_in } - , outMessages{ evbuffer_new() } , io{ std::move(io_in) } , have_{ torrent_in->pieceCount() } , callback_{ callback } @@ -308,13 +308,11 @@ public: { this->io->clear(); } - - evbuffer_free(this->outMessages); } void dbgOutMessageLen() const { - logtrace(this, fmt::format(FMT_STRING("outMessage size is now {:d}"), evbuffer_get_length(outMessages))); + logtrace(this, fmt::format(FMT_STRING("outMessage size is now {:d}"), std::size(outMessages))); } void pokeBatchPeriod(int interval) @@ -608,12 +606,12 @@ private: { TR_ASSERT(isValidRequest(req)); - auto* const out = outMessages; - evbuffer_add_uint32(out, sizeof(uint8_t) + 3 * sizeof(uint32_t)); - evbuffer_add_uint8(out, BtPeerMsgs::Request); - evbuffer_add_uint32(out, req.index); - evbuffer_add_uint32(out, req.offset); - evbuffer_add_uint32(out, req.length); + auto& out = outMessages; + out.addUint32(sizeof(uint8_t) + 3 * sizeof(uint32_t)); + out.addUint8(BtPeerMsgs::Request); + out.addUint32(req.index); + out.addUint32(req.offset); + out.addUint32(req.length); logtrace(this, fmt::format(FMT_STRING("requesting {:d}:{:d}->{:d}..."), req.index, req.offset, req.length)); dbgOutMessageLen(); @@ -706,7 +704,7 @@ public: tr_torrent* const torrent; - evbuffer* const outMessages; /* all the non-piece messages */ + libtransmission::Buffer outMessages; /* all the non-piece messages */ std::shared_ptr const io; @@ -774,13 +772,13 @@ static void protocolSendReject(tr_peerMsgsImpl* msgs, struct peer_request const* { TR_ASSERT(msgs->io->supportsFEXT()); - struct evbuffer* out = msgs->outMessages; + auto& out = msgs->outMessages; - evbuffer_add_uint32(out, sizeof(uint8_t) + 3 * sizeof(uint32_t)); - evbuffer_add_uint8(out, BtPeerMsgs::FextReject); - evbuffer_add_uint32(out, req->index); - evbuffer_add_uint32(out, req->offset); - evbuffer_add_uint32(out, req->length); + out.addUint32(sizeof(uint8_t) + 3 * sizeof(uint32_t)); + out.addUint8(BtPeerMsgs::FextReject); + out.addUint32(req->index); + out.addUint32(req->offset); + out.addUint32(req->length); logtrace(msgs, fmt::format(FMT_STRING("rejecting {:d}:{:d}->{:d}..."), req->index, req->offset, req->length)); msgs->dbgOutMessageLen(); @@ -788,13 +786,13 @@ static void protocolSendReject(tr_peerMsgsImpl* msgs, struct peer_request const* static void protocolSendCancel(tr_peerMsgsImpl* msgs, peer_request const& req) { - struct evbuffer* out = msgs->outMessages; + auto& out = msgs->outMessages; - evbuffer_add_uint32(out, sizeof(uint8_t) + 3 * sizeof(uint32_t)); - evbuffer_add_uint8(out, BtPeerMsgs::Cancel); - evbuffer_add_uint32(out, req.index); - evbuffer_add_uint32(out, req.offset); - evbuffer_add_uint32(out, req.length); + out.addUint32(sizeof(uint8_t) + 3 * sizeof(uint32_t)); + out.addUint8(BtPeerMsgs::Cancel); + out.addUint32(req.index); + out.addUint32(req.offset); + out.addUint32(req.length); logtrace(msgs, fmt::format(FMT_STRING("cancelling {:d}:{:d}->{:d}..."), req.index, req.offset, req.length)); msgs->dbgOutMessageLen(); @@ -803,21 +801,21 @@ static void protocolSendCancel(tr_peerMsgsImpl* msgs, peer_request const& req) static void protocolSendPort(tr_peerMsgsImpl* msgs, tr_port port) { - struct evbuffer* out = msgs->outMessages; + auto& out = msgs->outMessages; logtrace(msgs, fmt::format(FMT_STRING("sending Port {:d}"), port.host())); - evbuffer_add_uint32(out, 3); - evbuffer_add_uint8(out, BtPeerMsgs::Port); - evbuffer_add_uint16(out, port.network()); + out.addUint32(3); + out.addUint8(BtPeerMsgs::Port); + out.addUint16(port.network()); } static void protocolSendHave(tr_peerMsgsImpl* msgs, tr_piece_index_t index) { - struct evbuffer* out = msgs->outMessages; + auto& out = msgs->outMessages; - evbuffer_add_uint32(out, sizeof(uint8_t) + sizeof(uint32_t)); - evbuffer_add_uint8(out, BtPeerMsgs::Have); - evbuffer_add_uint32(out, index); + out.addUint32(sizeof(uint8_t) + sizeof(uint32_t)); + out.addUint8(BtPeerMsgs::Have); + out.addUint32(index); logtrace(msgs, fmt::format(FMT_STRING("sending Have {:d}"), index)); msgs->dbgOutMessageLen(); @@ -826,10 +824,10 @@ static void protocolSendHave(tr_peerMsgsImpl* msgs, tr_piece_index_t index) static void protocolSendChoke(tr_peerMsgsImpl* msgs, bool choke) { - struct evbuffer* out = msgs->outMessages; + auto& out = msgs->outMessages; - evbuffer_add_uint32(out, sizeof(uint8_t)); - evbuffer_add_uint8(out, choke ? BtPeerMsgs::Choke : BtPeerMsgs::Unchoke); + out.addUint32(sizeof(uint8_t)); + out.addUint8(choke ? BtPeerMsgs::Choke : BtPeerMsgs::Unchoke); logtrace(msgs, choke ? "sending choke" : "sending unchoked"); msgs->dbgOutMessageLen(); @@ -840,10 +838,10 @@ static void protocolSendHaveAll(tr_peerMsgsImpl* msgs) { TR_ASSERT(msgs->io->supportsFEXT()); - struct evbuffer* out = msgs->outMessages; + auto& out = msgs->outMessages; - evbuffer_add_uint32(out, sizeof(uint8_t)); - evbuffer_add_uint8(out, BtPeerMsgs::FextHaveAll); + out.addUint32(sizeof(uint8_t)); + out.addUint8(BtPeerMsgs::FextHaveAll); logtrace(msgs, "sending HAVE_ALL..."); msgs->dbgOutMessageLen(); @@ -854,10 +852,10 @@ static void protocolSendHaveNone(tr_peerMsgsImpl* msgs) { TR_ASSERT(msgs->io->supportsFEXT()); - struct evbuffer* out = msgs->outMessages; + auto& out = msgs->outMessages; - evbuffer_add_uint32(out, sizeof(uint8_t)); - evbuffer_add_uint8(out, BtPeerMsgs::FextHaveNone); + out.addUint32(sizeof(uint8_t)); + out.addUint8(BtPeerMsgs::FextHaveNone); logtrace(msgs, "sending HAVE_NONE..."); msgs->dbgOutMessageLen(); @@ -872,11 +870,11 @@ static void sendInterest(tr_peerMsgsImpl* msgs, bool b) { TR_ASSERT(msgs != nullptr); - struct evbuffer* out = msgs->outMessages; + auto& out = msgs->outMessages; logtrace(msgs, b ? "Sending Interested" : "Sending Not Interested"); - evbuffer_add_uint32(out, sizeof(uint8_t)); - evbuffer_add_uint8(out, b ? BtPeerMsgs::Interested : BtPeerMsgs::NotInterested); + out.addUint32(sizeof(uint8_t)); + out.addUint8(b ? BtPeerMsgs::Interested : BtPeerMsgs::NotInterested); msgs->pokeBatchPeriod(HighPriorityIntervalSecs); msgs->dbgOutMessageLen(); @@ -914,7 +912,7 @@ static void cancelAllRequestsToClient(tr_peerMsgsImpl* msgs) static void sendLtepHandshake(tr_peerMsgsImpl* msgs) { - evbuffer* const out = msgs->outMessages; + auto& out = msgs->outMessages; auto const ipv6 = tr_globalIPv6(msgs->io->session); static tr_quark version_quark = 0; @@ -1009,17 +1007,16 @@ static void sendLtepHandshake(tr_peerMsgsImpl* msgs) } } - auto* const payload = tr_variantToBuf(&val, TR_VARIANT_FMT_BENC); + auto payload = tr_variantToStr(&val, TR_VARIANT_FMT_BENC); - evbuffer_add_uint32(out, 2 * sizeof(uint8_t) + evbuffer_get_length(payload)); - evbuffer_add_uint8(out, BtPeerMsgs::Ltep); - evbuffer_add_uint8(out, LtepMessages::Handshake); - evbuffer_add_buffer(out, payload); + out.addUint32(2 * sizeof(uint8_t) + std::size(payload)); + out.addUint8(BtPeerMsgs::Ltep); + out.addUint8(LtepMessages::Handshake); + out.add(payload); msgs->pokeBatchPeriod(ImmediatePriorityIntervalSecs); msgs->dbgOutMessageLen(); /* cleanup */ - evbuffer_free(payload); tr_variantClear(&val); } @@ -1175,25 +1172,24 @@ static void parseUtMetadata(tr_peerMsgsImpl* msgs, uint32_t msglen) } else { - evbuffer* const out = msgs->outMessages; + auto& out = msgs->outMessages; /* build the rejection message */ auto v = tr_variant{}; tr_variantInitDict(&v, 2); tr_variantDictAddInt(&v, TR_KEY_msg_type, MetadataMsgType::Reject); tr_variantDictAddInt(&v, TR_KEY_piece, piece); - evbuffer* const payload = tr_variantToBuf(&v, TR_VARIANT_FMT_BENC); + auto const payload = tr_variantToStr(&v, TR_VARIANT_FMT_BENC); /* write it out as a LTEP message to our outMessages buffer */ - evbuffer_add_uint32(out, 2 * sizeof(uint8_t) + evbuffer_get_length(payload)); - evbuffer_add_uint8(out, BtPeerMsgs::Ltep); - evbuffer_add_uint8(out, msgs->ut_metadata_id); - evbuffer_add_buffer(out, payload); + out.addUint32(2 * sizeof(uint8_t) + std::size(payload)); + out.addUint8(BtPeerMsgs::Ltep); + out.addUint8(msgs->ut_metadata_id); + out.add(payload); msgs->pokeBatchPeriod(HighPriorityIntervalSecs); msgs->dbgOutMessageLen(); /* cleanup */ - evbuffer_free(payload); tr_variantClear(&v); } } @@ -1930,27 +1926,26 @@ static void updateMetadataRequests(tr_peerMsgsImpl* msgs, time_t now) if (auto const piece = tr_torrentGetNextMetadataRequest(msgs->torrent, now); piece) { - evbuffer* const out = msgs->outMessages; + auto& out = msgs->outMessages; /* build the data message */ auto tmp = tr_variant{}; tr_variantInitDict(&tmp, 3); tr_variantDictAddInt(&tmp, TR_KEY_msg_type, MetadataMsgType::Request); tr_variantDictAddInt(&tmp, TR_KEY_piece, *piece); - auto* const payload = tr_variantToBuf(&tmp, TR_VARIANT_FMT_BENC); + auto const payload = tr_variantToStr(&tmp, TR_VARIANT_FMT_BENC); logtrace(msgs, fmt::format(FMT_STRING("requesting metadata piece #{:d}"), *piece)); /* write it out as a LTEP message to our outMessages buffer */ - evbuffer_add_uint32(out, 2 * sizeof(uint8_t) + evbuffer_get_length(payload)); - evbuffer_add_uint8(out, BtPeerMsgs::Ltep); - evbuffer_add_uint8(out, msgs->ut_metadata_id); - evbuffer_add_buffer(out, payload); + out.addUint32(2 * sizeof(uint8_t) + std::size(payload)); + out.addUint8(BtPeerMsgs::Ltep); + out.addUint8(msgs->ut_metadata_id); + out.add(payload); msgs->pokeBatchPeriod(HighPriorityIntervalSecs); msgs->dbgOutMessageLen(); /* cleanup */ - evbuffer_free(payload); tr_variantClear(&tmp); } } @@ -1989,7 +1984,7 @@ static size_t fillOutputBuffer(tr_peerMsgsImpl* msgs, time_t now) { size_t bytes_written = 0; struct peer_request req; - bool const have_messages = evbuffer_get_length(msgs->outMessages) != 0; + bool const have_messages = !std::empty(msgs->outMessages); bool const fext = msgs->io->supportsFEXT(); /** @@ -1998,17 +1993,15 @@ static size_t fillOutputBuffer(tr_peerMsgsImpl* msgs, time_t now) if (have_messages && msgs->outMessagesBatchedAt == 0) /* fresh batch */ { - logtrace( - msgs, - fmt::format(FMT_STRING("started an outMessages batch (length is {:d})"), evbuffer_get_length(msgs->outMessages))); + logtrace(msgs, fmt::format(FMT_STRING("started an outMessages batch (length is {:d})"), std::size(msgs->outMessages))); msgs->outMessagesBatchedAt = now; } else if (have_messages && now - msgs->outMessagesBatchedAt >= msgs->outMessagesBatchPeriod) { - size_t const len = evbuffer_get_length(msgs->outMessages); + auto const len = std::size(msgs->outMessages); /* flush the protocol messages */ logtrace(msgs, fmt::format(FMT_STRING("flushing outMessages... to {:p} (length is {:d})"), fmt::ptr(msgs->io), len)); - msgs->io->writeBuf(msgs->outMessages, false); + msgs->io->write(msgs->outMessages, false); msgs->clientSentAnythingAt = now; msgs->outMessagesBatchedAt = 0; msgs->outMessagesBatchPeriod = LowPriorityIntervalSecs; @@ -2026,7 +2019,7 @@ static size_t fillOutputBuffer(tr_peerMsgsImpl* msgs, time_t now) if (auto const piece_data = tr_torrentGetMetadataPiece(msgs->torrent, piece); piece_data) { - auto* const out = msgs->outMessages; + auto& out = msgs->outMessages; /* build the data message */ auto tmp = tr_variant{}; @@ -2034,18 +2027,17 @@ static size_t fillOutputBuffer(tr_peerMsgsImpl* msgs, time_t now) tr_variantDictAddInt(&tmp, TR_KEY_msg_type, MetadataMsgType::Data); tr_variantDictAddInt(&tmp, TR_KEY_piece, piece); tr_variantDictAddInt(&tmp, TR_KEY_total_size, msgs->torrent->infoDictSize()); - evbuffer* const payload = tr_variantToBuf(&tmp, TR_VARIANT_FMT_BENC); + auto const payload = tr_variantToStr(&tmp, TR_VARIANT_FMT_BENC); /* write it out as a LTEP message to our outMessages buffer */ - evbuffer_add_uint32(out, 2 * sizeof(uint8_t) + evbuffer_get_length(payload) + std::size(*piece_data)); - evbuffer_add_uint8(out, BtPeerMsgs::Ltep); - evbuffer_add_uint8(out, msgs->ut_metadata_id); - evbuffer_add_buffer(out, payload); - evbuffer_add(out, std::data(*piece_data), std::size(*piece_data)); + out.addUint32(2 * sizeof(uint8_t) + std::size(payload) + std::size(*piece_data)); + out.addUint8(BtPeerMsgs::Ltep); + out.addUint8(msgs->ut_metadata_id); + out.add(payload); + out.add(*piece_data); msgs->pokeBatchPeriod(HighPriorityIntervalSecs); msgs->dbgOutMessageLen(); - evbuffer_free(payload); tr_variantClear(&tmp); ok = true; @@ -2053,24 +2045,23 @@ static size_t fillOutputBuffer(tr_peerMsgsImpl* msgs, time_t now) if (!ok) /* send a rejection message */ { - evbuffer* const out = msgs->outMessages; + auto& out = msgs->outMessages; /* build the rejection message */ auto tmp = tr_variant{}; tr_variantInitDict(&tmp, 2); tr_variantDictAddInt(&tmp, TR_KEY_msg_type, MetadataMsgType::Reject); tr_variantDictAddInt(&tmp, TR_KEY_piece, piece); - evbuffer* const payload = tr_variantToBuf(&tmp, TR_VARIANT_FMT_BENC); + auto payload = tr_variantToStr(&tmp, TR_VARIANT_FMT_BENC); /* write it out as a LTEP message to our outMessages buffer */ - evbuffer_add_uint32(out, 2 * sizeof(uint8_t) + evbuffer_get_length(payload)); - evbuffer_add_uint8(out, BtPeerMsgs::Ltep); - evbuffer_add_uint8(out, msgs->ut_metadata_id); - evbuffer_add_buffer(out, payload); + out.addUint32(2 * sizeof(uint8_t) + std::size(payload)); + out.addUint8(BtPeerMsgs::Ltep); + out.addUint8(msgs->ut_metadata_id); + out.add(payload); msgs->pokeBatchPeriod(HighPriorityIntervalSecs); msgs->dbgOutMessageLen(); - evbuffer_free(payload); tr_variantClear(&tmp); } } @@ -2087,24 +2078,21 @@ static size_t fillOutputBuffer(tr_peerMsgsImpl* msgs, time_t now) if (msgs->isValidRequest(req) && msgs->torrent->hasPiece(req.index)) { uint32_t const msglen = 4 + 1 + 4 + 4 + req.length; - auto iov = evbuffer_iovec{}; - auto* const out = evbuffer_new(); - evbuffer_expand(out, msglen); + auto out = libtransmission::Buffer{}; + out.reserve(msglen); - evbuffer_add_uint32(out, sizeof(uint8_t) + 2 * sizeof(uint32_t) + req.length); - evbuffer_add_uint8(out, BtPeerMsgs::Piece); - evbuffer_add_uint32(out, req.index); - evbuffer_add_uint32(out, req.offset); - - evbuffer_reserve_space(out, req.length, &iov, 1); + out.addUint32(sizeof(uint8_t) + 2 * sizeof(uint32_t) + req.length); + out.addUint8(BtPeerMsgs::Piece); + out.addUint32(req.index); + out.addUint32(req.offset); + auto buf = std::array{}; bool err = msgs->session->cache->readBlock( msgs->torrent, msgs->torrent->pieceLoc(req.index, req.offset), req.length, - static_cast(iov.iov_base)) != 0; - iov.iov_len = req.length; - evbuffer_commit_space(out, &iov, 1); + std::data(buf)) != 0; + out.add(std::data(buf), req.length); /* check the piece if it needs checking... */ if (!err) @@ -2126,17 +2114,15 @@ static size_t fillOutputBuffer(tr_peerMsgsImpl* msgs, time_t now) } else { - size_t const n = evbuffer_get_length(out); logtrace(msgs, fmt::format(FMT_STRING("sending block {:d}:{:d}->{:d}"), req.index, req.offset, req.length)); + auto const n = std::size(out); TR_ASSERT(n == msglen); - msgs->io->writeBuf(out, true); + msgs->io->write(out, true); bytes_written += n; msgs->clientSentAnythingAt = now; msgs->blocks_sent_to_peer.add(tr_time(), 1); } - evbuffer_free(out); - if (err) { bytes_written = 0; @@ -2161,7 +2147,7 @@ static size_t fillOutputBuffer(tr_peerMsgsImpl* msgs, time_t now) if (msgs != nullptr && msgs->clientSentAnythingAt != 0 && now - msgs->clientSentAnythingAt > KeepaliveIntervalSecs) { logtrace(msgs, "sending a keepalive message"); - evbuffer_add_uint32(msgs->outMessages, 0); + msgs->outMessages.addUint32(0); msgs->pokeBatchPeriod(ImmediatePriorityIntervalSecs); } @@ -2209,13 +2195,13 @@ static void sendBitfield(tr_peerMsgsImpl* msgs) { TR_ASSERT(msgs->torrent->hasMetainfo()); - struct evbuffer* out = msgs->outMessages; + auto& out = msgs->outMessages; auto bytes = msgs->torrent->createPieceBitfield(); - evbuffer_add_uint32(out, sizeof(uint8_t) + bytes.size()); - evbuffer_add_uint8(out, BtPeerMsgs::Bitfield); - evbuffer_add(out, bytes.data(), std::size(bytes)); - logtrace(msgs, fmt::format(FMT_STRING("sending bitfield... outMessage size is now {:d}"), evbuffer_get_length(out))); + out.addUint32(sizeof(uint8_t) + bytes.size()); + out.addUint8(BtPeerMsgs::Bitfield); + out.add(bytes); + logtrace(msgs, fmt::format(FMT_STRING("sending bitfield... outMessage size is now {:d}"), std::size(out))); msgs->pokeBatchPeriod(ImmediatePriorityIntervalSecs); } @@ -2292,7 +2278,7 @@ void tr_peerMsgsImpl::sendPex() return; } - evbuffer* const out = this->outMessages; + auto& out = this->outMessages; // update msgs std::swap(old4, new4); @@ -2403,15 +2389,14 @@ void tr_peerMsgsImpl::sendPex() } /* write the pex message */ - auto* const payload = tr_variantToBuf(&val, TR_VARIANT_FMT_BENC); - evbuffer_add_uint32(out, 2 * sizeof(uint8_t) + evbuffer_get_length(payload)); - evbuffer_add_uint8(out, BtPeerMsgs::Ltep); - evbuffer_add_uint8(out, this->ut_pex_id); - evbuffer_add_buffer(out, payload); + auto payload = tr_variantToStr(&val, TR_VARIANT_FMT_BENC); + out.addUint32(2 * sizeof(uint8_t) + std::size(payload)); + out.addUint8(BtPeerMsgs::Ltep); + out.addUint8(this->ut_pex_id); + out.add(payload); this->pokeBatchPeriod(HighPriorityIntervalSecs); - logtrace(this, fmt::format(FMT_STRING("sending a pex message; outMessage size is now {:d}"), evbuffer_get_length(out))); + logtrace(this, fmt::format(FMT_STRING("sending a pex message; outMessage size is now {:d}"), std::size(out))); this->dbgOutMessageLen(); - evbuffer_free(payload); tr_variantClear(&val); } diff --git a/libtransmission/tr-buffer.h b/libtransmission/tr-buffer.h new file mode 100644 index 000000000..46d98692c --- /dev/null +++ b/libtransmission/tr-buffer.h @@ -0,0 +1,335 @@ +// This file Copyright 2022 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 + +#include +#include +#include +#include +#include +#include + +#include + +#include "error.h" +#include "net.h" // tr_socket_t +#include "utils.h" + +namespace libtransmission +{ + +class Buffer +{ +public: + using Iovec = evbuffer_iovec; + + class Iterator + { + public: + using difference_type = long; + using value_type = std::byte; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::random_access_iterator_tag; + + Iterator(evbuffer* buf, size_t offset) + : buf_{ buf } + { + setOffset(offset); + } + + [[nodiscard]] value_type& operator*() noexcept + { + return *reinterpret_cast(iov_.iov_base); + } + + [[nodiscard]] value_type operator*() const noexcept + { + return *reinterpret_cast(iov_.iov_base); + } + + [[nodiscard]] Iterator operator+(int n_bytes) + { + return Iterator(buf_, offset_ + n_bytes); + } + + Iterator& operator++() noexcept + { + if (iov_.iov_len > 1) + { + iov_.iov_base = reinterpret_cast(iov_.iov_base) + 1; + --iov_.iov_len; + ++offset_; + } + else + { + setOffset(offset_ + 1); + } + return *this; + } + + [[nodiscard]] constexpr bool operator==(Iterator const& that) const noexcept + { + return offset_ == that.offset_; + } + + [[nodiscard]] constexpr bool operator!=(Iterator const& that) const noexcept + { + return !(*this == that); + } + + private: + void setOffset(size_t offset) + { + offset_ = offset; + auto ptr = evbuffer_ptr{}; + evbuffer_ptr_set(buf_, &ptr, offset, EVBUFFER_PTR_SET); + evbuffer_peek(buf_, std::numeric_limits::max(), &ptr, &iov_, 1); + } + + evbuffer* const buf_; + Iovec iov_ = {}; + size_t offset_ = 0; + }; + + Buffer() = default; + Buffer(Buffer&&) = default; + Buffer(Buffer const&) = delete; + Buffer& operator=(Buffer const&) = delete; + Buffer& operator=(Buffer&&) = default; + + template + Buffer(T const& data) + { + add(std::data(data), std::size(data)); + } + + [[nodiscard]] auto size() const noexcept + { + return evbuffer_get_length(buf_.get()); + } + + [[nodiscard]] auto empty() const noexcept + { + return size() == 0U; + } + + [[nodiscard]] auto vecs(size_t n_bytes) const + { + auto chains = std::vector(evbuffer_peek(buf_.get(), n_bytes, nullptr, nullptr, 0)); + evbuffer_peek(buf_.get(), n_bytes, nullptr, std::data(chains), std::size(chains)); + return chains; + } + + [[nodiscard]] auto vecs() const + { + return vecs(size()); + } + + [[nodiscard]] auto begin() noexcept + { + return Iterator(buf_.get(), 0U); + } + + [[nodiscard]] auto end() noexcept + { + return Iterator(buf_.get(), size()); + } + + [[nodiscard]] auto begin() const noexcept + { + return Iterator(buf_.get(), 0U); + } + + [[nodiscard]] auto end() const noexcept + { + return Iterator(buf_.get(), size()); + } + + [[nodiscard]] auto cbegin() const noexcept + { + return Iterator(buf_.get(), 0U); + } + + [[nodiscard]] auto cend() const noexcept + { + return Iterator(buf_.get(), size()); + } + + template + [[nodiscard]] bool startsWith(T const& needle) const + { + auto const n_bytes = std::size(needle); + auto const needle_begin = reinterpret_cast(std::data(needle)); + auto const needle_end = needle_begin + n_bytes; + return n_bytes <= size() && std::equal(needle_begin, needle_end, cbegin()); + } + + auto toBuf(void* tgt, size_t n_bytes) + { + return evbuffer_remove(buf_.get(), tgt, n_bytes); + } + + [[nodiscard]] uint16_t toUint16() + { + auto tmp = uint16_t{}; + toBuf(&tmp, sizeof(tmp)); + return ntohs(tmp); + } + + [[nodiscard]] uint32_t toUint32() + { + auto tmp = uint32_t{}; + toBuf(&tmp, sizeof(tmp)); + return ntohl(tmp); + } + + [[nodiscard]] uint64_t toUint64() + { + auto tmp = uint64_t{}; + toBuf(&tmp, sizeof(tmp)); + return tr_ntohll(tmp); + } + + void drain(size_t n_bytes) + { + evbuffer_drain(buf_.get(), n_bytes); + } + + void clear() + { + drain(size()); + } + + // -1 on error, 0 on eof, >0 on n bytes written + auto toSocket(tr_socket_t sockfd, size_t n_bytes, tr_error** error = nullptr) + { + EVUTIL_SET_SOCKET_ERROR(0); + auto const res = evbuffer_write_atmost(buf_.get(), sockfd, n_bytes); + auto const err = EVUTIL_SOCKET_ERROR(); + if (res == -1) + { + tr_error_set(error, err, tr_net_strerror(err)); + } + return res; + } + + [[nodiscard]] Iovec alloc(size_t n_bytes) + { + auto iov = Iovec{}; + evbuffer_reserve_space(buf_.get(), static_cast(n_bytes), &iov, 1); + return iov; + } + + void commit(Iovec iov) + { + evbuffer_commit_space(buf_.get(), &iov, 1); + } + + void reserve(size_t n_bytes) + { + evbuffer_expand(buf_.get(), n_bytes - size()); + } + + // -1 on error, 0 on eof, >0 for num bytes read + auto addSocket(tr_socket_t sockfd, size_t n_bytes, tr_error** error = nullptr) + { + EVUTIL_SET_SOCKET_ERROR(0); + auto const res = evbuffer_read(buf_.get(), sockfd, static_cast(n_bytes)); + auto const err = EVUTIL_SOCKET_ERROR(); + if (res == -1) + { + tr_error_set(error, err, tr_net_strerror(err)); + } + return res; + } + + // Move all data from one buffer into another. + // This is a destructive add: the source buffer is empty after this call. + void add(Buffer& that) + { + evbuffer_add_buffer(buf_.get(), that.buf_.get()); + } + + void add(Buffer&& that) + { + evbuffer_add_buffer(buf_.get(), that.buf_.get()); + } + + void add(void const* bytes, size_t n_bytes) + { + evbuffer_add(buf_.get(), bytes, n_bytes); + } + + template + void add(T const& data) + { + add(std::data(data), std::size(data)); + } + + template< + typename T, + typename std::enable_if_t< + std::is_same_v || std::is_same_v || std::is_same_v>* = nullptr> + void push_back(T ch) + { + add(&ch, 1); + } + + void addUint8(uint8_t uch) + { + add(&uch, 1); + } + + void addUint16(uint16_t hs) + { + uint16_t const ns = htons(hs); + add(&ns, sizeof(ns)); + } + + void addHton16(uint16_t hs) + { + addUint16(hs); + } + + void addUint32(uint32_t hl) + { + uint32_t const nl = htonl(hl); + add(&nl, sizeof(nl)); + } + + void addHton32(uint32_t hl) + { + addUint32(hl); + } + + void addUint64(uint64_t hll) + { + uint64_t const nll = tr_htonll(hll); + add(&nll, sizeof(nll)); + } + + void addHton64(uint64_t hll) + { + addUint64(hll); + } + + [[nodiscard]] std::string toString() const + { + auto str = std::string{}; + str.reserve(size()); + for (auto const& by : *this) + { + str.push_back(*reinterpret_cast(&by)); + } + return str; + } + +private: + std::unique_ptr buf_{ evbuffer_new(), evbuffer_free }; +}; + +} // namespace libtransmission diff --git a/libtransmission/variant-benc.cc b/libtransmission/variant-benc.cc index 312194607..21d1b5124 100644 --- a/libtransmission/variant-benc.cc +++ b/libtransmission/variant-benc.cc @@ -9,8 +9,6 @@ #include #include -#include - #include #include @@ -19,8 +17,9 @@ #include "transmission.h" #include "benc.h" -#include "tr-assert.h" #include "quark.h" +#include "tr-assert.h" +#include "tr-buffer.h" #include "utils.h" #include "variant-common.h" #include "variant.h" @@ -274,70 +273,58 @@ bool tr_variantParseBenc(tr_variant& top, int parse_opts, std::string_view benc, ***** ****/ -static void saveIntFunc(tr_variant const* val, void* vevbuf) +using Buffer = libtransmission::Buffer; + +static void saveIntFunc(tr_variant const* val, void* vout) { auto buf = std::array{}; auto const out = fmt::format_to(std::data(buf), FMT_COMPILE("i{:d}e"), val->val.i); - auto* const evbuf = static_cast(vevbuf); - evbuffer_add(evbuf, std::data(buf), static_cast(out - std::data(buf))); + static_cast(vout)->add(std::data(buf), static_cast(out - std::data(buf))); } -static void saveBoolFunc(tr_variant const* val, void* vevbuf) +static void saveBoolFunc(tr_variant const* val, void* vout) { - auto* const evbuf = static_cast(vevbuf); - if (val->val.b) - { - evbuffer_add(evbuf, "i1e", 3); - } - else - { - evbuffer_add(evbuf, "i0e", 3); - } + static_cast(vout)->add(val->val.b ? "i1e"sv : "i0e"sv); } -static void saveStringImpl(evbuffer* evbuf, std::string_view sv) +static void saveStringImpl(Buffer* tgt, std::string_view sv) { // `${sv.size()}:${sv}` auto prefix = std::array{}; auto out = fmt::format_to(std::data(prefix), FMT_COMPILE("{:d}:"), std::size(sv)); - evbuffer_add(evbuf, std::data(prefix), out - std::data(prefix)); - evbuffer_add(evbuf, std::data(sv), std::size(sv)); + tgt->add(std::data(prefix), out - std::data(prefix)); + tgt->add(sv); } -static void saveStringFunc(tr_variant const* v, void* vevbuf) +static void saveStringFunc(tr_variant const* v, void* vout) { auto sv = std::string_view{}; (void)!tr_variantGetStrView(v, &sv); - auto* const evbuf = static_cast(vevbuf); - saveStringImpl(evbuf, sv); + saveStringImpl(static_cast(vout), sv); } -static void saveRealFunc(tr_variant const* val, void* vevbuf) +static void saveRealFunc(tr_variant const* val, void* vout) { // the benc spec doesn't handle floats; save it as a string. auto buf = std::array{}; auto out = fmt::format_to(std::data(buf), FMT_COMPILE("{:f}"), val->val.d); - auto* const evbuf = static_cast(vevbuf); - saveStringImpl(evbuf, { std::data(buf), static_cast(out - std::data(buf)) }); + saveStringImpl(static_cast(vout), { std::data(buf), static_cast(out - std::data(buf)) }); } -static void saveDictBeginFunc(tr_variant const* /*val*/, void* vevbuf) +static void saveDictBeginFunc(tr_variant const* /*val*/, void* vbuf) { - auto* const evbuf = static_cast(vevbuf); - evbuffer_add(evbuf, "d", 1); + static_cast(vbuf)->push_back('d'); } -static void saveListBeginFunc(tr_variant const* /*val*/, void* vevbuf) +static void saveListBeginFunc(tr_variant const* /*val*/, void* vbuf) { - auto* const evbuf = static_cast(vevbuf); - evbuffer_add(evbuf, "l", 1); + static_cast(vbuf)->push_back('l'); } -static void saveContainerEndFunc(tr_variant const* /*val*/, void* vevbuf) +static void saveContainerEndFunc(tr_variant const* /*val*/, void* vbuf) { - auto* const evbuf = static_cast(vevbuf); - evbuffer_add(evbuf, "e", 1); + static_cast(vbuf)->push_back('e'); } static struct VariantWalkFuncs const walk_funcs = { @@ -350,7 +337,9 @@ static struct VariantWalkFuncs const walk_funcs = { saveContainerEndFunc, // }; -void tr_variantToBufBenc(tr_variant const* top, evbuffer* buf) +std::string tr_variantToStrBenc(tr_variant const* top) { - tr_variantWalk(top, &walk_funcs, buf, true); + auto buf = libtransmission::Buffer{}; + tr_variantWalk(top, &walk_funcs, &buf, true); + return buf.toString(); } diff --git a/libtransmission/variant-common.h b/libtransmission/variant-common.h index ad2a58cfd..e8d3388b4 100644 --- a/libtransmission/variant-common.h +++ b/libtransmission/variant-common.h @@ -32,9 +32,9 @@ struct VariantWalkFuncs void tr_variantWalk(tr_variant const* top, VariantWalkFuncs const* walk_funcs, void* user_data, bool sort_dicts); -void tr_variantToBufJson(tr_variant const* top, struct evbuffer* buf, bool lean); +std::string tr_variantToStrJson(tr_variant const* top, bool lean); -void tr_variantToBufBenc(tr_variant const* top, struct evbuffer* buf); +std::string tr_variantToStrBenc(tr_variant const* top); void tr_variantInit(tr_variant* v, char type); diff --git a/libtransmission/variant-json.cc b/libtransmission/variant-json.cc index 9aee77477..1b339ec35 100644 --- a/libtransmission/variant-json.cc +++ b/libtransmission/variant-json.cc @@ -10,14 +10,13 @@ #include #include #include +#include #include #include #define UTF_CPP_CPLUSPLUS 201703L #include -#include - #include #include @@ -30,12 +29,15 @@ #include "log.h" #include "quark.h" #include "tr-assert.h" +#include "tr-buffer.h" #include "utils.h" #include "variant-common.h" #include "variant.h" using namespace std::literals; +using Buffer = libtransmission::Buffer; + /* arbitrary value... this is much deeper than our code goes */ static auto constexpr MaxDepth = int{ 64 }; @@ -44,8 +46,8 @@ struct json_wrapper_data bool has_content; size_t size; std::string_view key; - evbuffer* keybuf; - evbuffer* strbuf; + std::string keybuf; + std::string strbuf; tr_error* error; std::deque stack; tr_variant* top; @@ -168,11 +170,11 @@ static bool decode_hex_string(char const* in, unsigned int* setme) return true; } -static std::string_view extract_escaped_string(char const* in, size_t in_len, evbuffer* buf) +static std::string_view extract_escaped_string(char const* in, size_t in_len, std::string& buf) { char const* const in_end = in + in_len; - evbuffer_drain(buf, evbuffer_get_length(buf)); + buf.clear(); while (in < in_end) { @@ -183,49 +185,49 @@ static std::string_view extract_escaped_string(char const* in, size_t in_len, ev switch (in[1]) { case 'b': - evbuffer_add(buf, "\b", 1); + buf.push_back('\b'); in += 2; unescaped = true; break; case 'f': - evbuffer_add(buf, "\f", 1); + buf.push_back('\f'); in += 2; unescaped = true; break; case 'n': - evbuffer_add(buf, "\n", 1); + buf.push_back('\n'); in += 2; unescaped = true; break; case 'r': - evbuffer_add(buf, "\r", 1); + buf.push_back('\r'); in += 2; unescaped = true; break; case 't': - evbuffer_add(buf, "\t", 1); + buf.push_back('\t'); in += 2; unescaped = true; break; case '/': - evbuffer_add(buf, "/", 1); + buf.push_back('/'); in += 2; unescaped = true; break; case '"': - evbuffer_add(buf, "\"", 1); + buf.push_back('"'); in += 2; unescaped = true; break; case '\\': - evbuffer_add(buf, "\\", 1); + buf.push_back('\\'); in += 2; unescaped = true; break; @@ -241,11 +243,11 @@ static std::string_view extract_escaped_string(char const* in, size_t in_len, ev { auto buf8 = std::array{}; auto const it = utf8::append(val, std::data(buf8)); - evbuffer_add(buf, std::data(buf8), it - std::data(buf8)); + buf.append(std::data(buf8), it - std::data(buf8)); } - catch (utf8::exception const&) - { // invalid codepoint - evbuffer_add(buf, "?", 1); + catch (utf8::exception const&) // invalid codepoint + { + buf.push_back('?'); } unescaped = true; in += 6; @@ -257,15 +259,15 @@ static std::string_view extract_escaped_string(char const* in, size_t in_len, ev if (!unescaped) { - evbuffer_add(buf, in, 1); + buf.push_back(*in); ++in; } } - return { (char const*)evbuffer_pullup(buf, -1), evbuffer_get_length(buf) }; + return buf; } -static std::pair extract_string(jsonsl_t jsn, struct jsonsl_state_st* state, evbuffer* buf) +static std::pair extract_string(jsonsl_t jsn, struct jsonsl_state_st* state, std::string& buf) { // figure out where the string is char const* in_begin = jsn->base + state->pos_begin; @@ -367,11 +369,9 @@ bool tr_variantParseJson(tr_variant& setme, int parse_opts, std::string_view jso data.size = std::size(json); data.has_content = false; data.key = ""sv; - data.keybuf = evbuffer_new(); data.parse_opts = parse_opts; data.preallocGuess = {}; data.stack = {}; - data.strbuf = evbuffer_new(); data.top = &setme; /* parse it */ @@ -395,8 +395,6 @@ bool tr_variantParseJson(tr_variant& setme, int parse_opts, std::string_view jso { tr_error_propagate(error, &data.error); } - evbuffer_free(data.keybuf); - evbuffer_free(data.strbuf); jsonsl_destroy(jsn); return success; } @@ -412,14 +410,19 @@ struct ParentState int childCount; }; -struct jsonWalk +struct JsonWalk { - bool doIndent = false; + JsonWalk(bool do_indent) + : doIndent{ do_indent } + { + } + std::deque parents; - evbuffer* out = nullptr; + Buffer out; + bool doIndent; }; -static void jsonIndent(struct jsonWalk* data) +static void jsonIndent(struct JsonWalk* data) { static auto buf = std::array{}; @@ -431,11 +434,11 @@ static void jsonIndent(struct jsonWalk* data) if (data->doIndent) { - evbuffer_add(data->out, std::data(buf), std::size(data->parents) * 4 + 1); + data->out.add(std::data(buf), std::size(data->parents) * 4 + 1); } } -static void jsonChildFunc(struct jsonWalk* data) +static void jsonChildFunc(struct JsonWalk* data) { if (!std::empty(data->parents)) { @@ -450,14 +453,14 @@ static void jsonChildFunc(struct jsonWalk* data) if (i % 2 == 0) { - evbuffer_add(data->out, ": ", data->doIndent ? 2 : 1); + data->out.add(data->doIndent ? ": "sv : ":"sv); } else { bool const is_last = pstate.childIndex == pstate.childCount; if (!is_last) { - evbuffer_add(data->out, ",", 1); + data->out.push_back(','); jsonIndent(data); } } @@ -469,7 +472,7 @@ static void jsonChildFunc(struct jsonWalk* data) ++pstate.childIndex; if (bool const is_last = pstate.childIndex == pstate.childCount; !is_last) { - evbuffer_add(data->out, ",", 1); + data->out.push_back(','); jsonIndent(data); } @@ -481,13 +484,13 @@ static void jsonChildFunc(struct jsonWalk* data) } } -static void jsonPushParent(struct jsonWalk* data, tr_variant const* v) +static 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 }); } -static void jsonPopParent(struct jsonWalk* data) +static void jsonPopParent(struct JsonWalk* data) { data->parents.pop_back(); } @@ -496,22 +499,22 @@ static void jsonIntFunc(tr_variant const* val, void* vdata) { auto buf = std::array{}; auto const out = fmt::format_to(std::data(buf), FMT_COMPILE("{:d}"), val->val.i); - auto* const data = static_cast(vdata); - evbuffer_add(data->out, std::data(buf), static_cast(out - std::data(buf))); + auto* const data = static_cast(vdata); + data->out.add(std::data(buf), static_cast(out - std::data(buf))); jsonChildFunc(data); } static void jsonBoolFunc(tr_variant const* val, void* vdata) { - auto* data = static_cast(vdata); + auto* data = static_cast(vdata); if (val->val.b) { - evbuffer_add(data->out, "true", 4); + data->out.add("true"sv); } else { - evbuffer_add(data->out, "false", 5); + data->out.add("false"sv); } jsonChildFunc(data); @@ -519,19 +522,19 @@ static void jsonBoolFunc(tr_variant const* val, void* vdata) static void jsonRealFunc(tr_variant const* val, void* vdata) { - auto* data = static_cast(vdata); + auto* data = static_cast(vdata); if (fabs(val->val.d - (int)val->val.d) < 0.00001) { auto buf = std::array{}; auto const out = fmt::format_to(std::data(buf), FMT_COMPILE("{:.0f}"), val->val.d); - evbuffer_add(data->out, std::data(buf), static_cast(out - std::data(buf))); + data->out.add(std::data(buf), static_cast(out - std::data(buf))); } else { auto buf = std::array{}; auto const out = fmt::format_to(std::data(buf), FMT_COMPILE("{:.4f}"), val->val.d); - evbuffer_add(data->out, std::data(buf), static_cast(out - std::data(buf))); + data->out.add(std::data(buf), static_cast(out - std::data(buf))); } jsonChildFunc(data); @@ -539,96 +542,85 @@ static void jsonRealFunc(tr_variant const* val, void* vdata) static void jsonStringFunc(tr_variant const* val, void* vdata) { - evbuffer_iovec vec[1]; - auto* data = static_cast(vdata); + auto* data = static_cast(vdata); auto sv = std::string_view{}; (void)!tr_variantGetStrView(val, &sv); - evbuffer_reserve_space(data->out, std::size(sv) * 6 + 2, vec, 1); - auto* out = static_cast(vec[0].iov_base); - char const* const outend = out + vec[0].iov_len; - - char* outwalk = out; - *outwalk++ = '"'; + auto& out = data->out; + out.reserve(std::size(data->out) + std::size(sv) * 6 + 2); + out.push_back('"'); for (; !std::empty(sv); sv.remove_prefix(1)) { switch (sv.front()) { case '\b': - *outwalk++ = '\\'; - *outwalk++ = 'b'; + out.add(R"(\b)"sv); break; case '\f': - *outwalk++ = '\\'; - *outwalk++ = 'f'; + out.add(R"(\f)"sv); break; case '\n': - *outwalk++ = '\\'; - *outwalk++ = 'n'; + out.add(R"(\n)"sv); break; case '\r': - *outwalk++ = '\\'; - *outwalk++ = 'r'; + out.add(R"(\r)"sv); break; case '\t': - *outwalk++ = '\\'; - *outwalk++ = 't'; + out.add(R"(\t)"sv); break; case '"': - *outwalk++ = '\\'; - *outwalk++ = '"'; + out.add(R"(\")"sv); break; case '\\': - *outwalk++ = '\\'; - *outwalk++ = '\\'; + out.add(R"(\\)"sv); break; default: if (isprint((unsigned char)sv.front()) != 0) { - *outwalk++ = sv.front(); + out.push_back(sv.front()); } else { try { + auto arr = std::array{}; auto const* const begin8 = std::data(sv); auto const* const end8 = begin8 + std::size(sv); auto const* walk8 = begin8; auto const uch32 = utf8::next(walk8, end8); - outwalk = fmt::format_to_n(outwalk, outend - outwalk - 1, FMT_COMPILE("\\u{:04x}"), uch32).out; + auto const result = fmt::format_to_n(std::data(arr), std::size(arr), FMT_COMPILE("\\u{:04x}"), uch32); + out.add(std::data(arr), result.size); sv.remove_prefix(walk8 - begin8 - 1); } catch (utf8::exception const&) { - *outwalk++ = '?'; + out.push_back('?'); } } break; } } - *outwalk++ = '"'; - vec[0].iov_len = outwalk - out; - evbuffer_commit_space(data->out, vec, 1); + out.push_back('"'); jsonChildFunc(data); } static void jsonDictBeginFunc(tr_variant const* val, void* vdata) { - auto* data = static_cast(vdata); + auto* data = static_cast(vdata); jsonPushParent(data, val); - evbuffer_add(data->out, "{", 1); + data->out.push_back('{'); if (val->val.l.count != 0) { @@ -639,10 +631,10 @@ static void jsonDictBeginFunc(tr_variant const* val, void* vdata) static void jsonListBeginFunc(tr_variant const* val, void* vdata) { size_t const n_children = tr_variantListSize(val); - auto* data = static_cast(vdata); + auto* data = static_cast(vdata); jsonPushParent(data, val); - evbuffer_add(data->out, "[", 1); + data->out.push_back('['); if (n_children != 0) { @@ -652,7 +644,7 @@ static void jsonListBeginFunc(tr_variant const* val, void* vdata) static void jsonContainerEndFunc(tr_variant const* val, void* vdata) { - auto* data = static_cast(vdata); + auto* data = static_cast(vdata); jsonPopParent(data); @@ -660,11 +652,11 @@ static void jsonContainerEndFunc(tr_variant const* val, void* vdata) if (tr_variantIsDict(val)) { - evbuffer_add(data->out, "}", 1); + data->out.push_back('}'); } else /* list */ { - evbuffer_add(data->out, "]", 1); + data->out.push_back(']'); } jsonChildFunc(data); @@ -680,17 +672,16 @@ static struct VariantWalkFuncs const walk_funcs = { jsonContainerEndFunc, // }; -void tr_variantToBufJson(tr_variant const* top, evbuffer* buf, bool lean) +std::string tr_variantToStrJson(tr_variant const* top, bool lean) { - struct jsonWalk data; - - data.doIndent = !lean; - data.out = buf; + auto data = JsonWalk{ !lean }; tr_variantWalk(top, &walk_funcs, &data, true); - if (evbuffer_get_length(buf) != 0) + auto& buf = data.out; + if (!std::empty(buf)) { - evbuffer_add_printf(buf, "\n"); + buf.push_back('\n'); } + return buf.toString(); } diff --git a/libtransmission/variant.cc b/libtransmission/variant.cc index 0d1b711ee..82dff99f3 100644 --- a/libtransmission/variant.cc +++ b/libtransmission/variant.cc @@ -15,8 +15,6 @@ #include #endif -#include - #include #define LIBTRANSMISSION_VARIANT_MODULE @@ -1098,39 +1096,22 @@ void tr_variantMergeDicts(tr_variant* target, tr_variant const* source) **** ***/ -struct evbuffer* tr_variantToBuf(tr_variant const* v, tr_variant_fmt fmt) +std::string tr_variantToStr(tr_variant const* v, tr_variant_fmt fmt) { - struct evbuffer* buf = evbuffer_new(); - - evbuffer_expand(buf, 4096); /* alloc a little memory to start off with */ - switch (fmt) { - case TR_VARIANT_FMT_BENC: - tr_variantToBufBenc(v, buf); - break; - case TR_VARIANT_FMT_JSON: - tr_variantToBufJson(v, buf, false); + return tr_variantToStrJson(v, false); break; case TR_VARIANT_FMT_JSON_LEAN: - tr_variantToBufJson(v, buf, true); + return tr_variantToStrJson(v, true); + break; + + default: // TR_VARIANT_FMT_BENC: + return tr_variantToStrBenc(v); break; } - - return buf; -} - -std::string tr_variantToStr(tr_variant const* v, tr_variant_fmt fmt) -{ - auto* const buf = tr_variantToBuf(v, fmt); - auto const n = evbuffer_get_length(buf); - auto str = std::string{}; - str.resize(n); - evbuffer_copyout(buf, std::data(str), n); - evbuffer_free(buf); - return str; } int tr_variantToFile(tr_variant const* v, tr_variant_fmt fmt, std::string_view filename) diff --git a/libtransmission/variant.h b/libtransmission/variant.h index 9fdcc98a2..a4fa02dbf 100644 --- a/libtransmission/variant.h +++ b/libtransmission/variant.h @@ -12,8 +12,6 @@ #include "quark.h" -struct evbuffer; - struct tr_error; /** @@ -114,9 +112,7 @@ enum tr_variant_fmt int tr_variantToFile(tr_variant const* variant, tr_variant_fmt fmt, std::string_view filename); -std::string tr_variantToStr(tr_variant const* variant, tr_variant_fmt fmt); - -struct evbuffer* tr_variantToBuf(tr_variant const* variant, tr_variant_fmt fmt); +[[nodiscard]] std::string tr_variantToStr(tr_variant const* variant, tr_variant_fmt fmt); enum tr_variant_parse_opts { diff --git a/qt/RpcClient.h b/qt/RpcClient.h index f0bab1537..14bd4088d 100644 --- a/qt/RpcClient.h +++ b/qt/RpcClient.h @@ -31,7 +31,6 @@ Q_DECLARE_METATYPE(TrVariantPtr) extern "C" { - struct evbuffer; struct tr_session; } diff --git a/tests/libtransmission/CMakeLists.txt b/tests/libtransmission/CMakeLists.txt index 53329f646..a955abcae 100644 --- a/tests/libtransmission/CMakeLists.txt +++ b/tests/libtransmission/CMakeLists.txt @@ -5,6 +5,7 @@ add_executable(libtransmission-test bitfield-test.cc block-info-test.cc blocklist-test.cc + buffer-test.cc clients-test.cc completion-test.cc copy-test.cc diff --git a/tests/libtransmission/buffer-test.cc b/tests/libtransmission/buffer-test.cc new file mode 100644 index 000000000..0b13d6143 --- /dev/null +++ b/tests/libtransmission/buffer-test.cc @@ -0,0 +1,63 @@ +// This file Copyright (C) 2013-2022 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. + +#include "transmission.h" + +#include "tr-buffer.h" + +#include "test-fixtures.h" + +using BufferTest = ::testing::Test; +using namespace std::literals; +using Buffer = libtransmission::Buffer; + +TEST_F(BufferTest, startsWithInSingleSegment) +{ + auto constexpr Hello = "Hello, "sv; + auto constexpr World = "World"sv; + auto constexpr Bang = "!"sv; + + auto buf = Buffer{}; + buf.add(Hello); + EXPECT_TRUE(buf.startsWith(Hello)); + + buf.add(World); + EXPECT_TRUE(buf.startsWith(Hello)); + EXPECT_TRUE(buf.startsWith("Hello, Worl"sv)); + EXPECT_TRUE(buf.startsWith("Hello, World"sv)); + EXPECT_FALSE(buf.startsWith("Hello, World!"sv)); + EXPECT_FALSE(buf.startsWith("Hello!"sv)); + + buf.add(Bang); + EXPECT_FALSE(buf.startsWith("Hello!")); + EXPECT_TRUE(buf.startsWith(Hello)); + EXPECT_TRUE(buf.startsWith("Hello, Worl"sv)); + EXPECT_TRUE(buf.startsWith("Hello, World"sv)); + EXPECT_TRUE(buf.startsWith("Hello, World!"sv)); +} +TEST_F(BufferTest, startsWithInMultiSegment) +{ + auto constexpr Hello = "Hello, "sv; + auto constexpr World = "World"sv; + auto constexpr Bang = "!"sv; + + auto buf = std::make_unique(); + buf->add(Buffer{ Hello }); + EXPECT_TRUE(buf->startsWith(Hello)); + + buf->add(Buffer{ World }); + EXPECT_TRUE(buf->startsWith(Hello)); + EXPECT_TRUE(buf->startsWith("Hello, Worl"sv)); + EXPECT_TRUE(buf->startsWith("Hello, World"sv)); + EXPECT_FALSE(buf->startsWith("Hello, World!"sv)); + EXPECT_FALSE(buf->startsWith("Hello!"sv)); + + buf->add(Buffer{ Bang }); + EXPECT_FALSE(buf->startsWith("Hello!")); + EXPECT_TRUE(buf->startsWith(Hello)); + EXPECT_TRUE(buf->startsWith("Hello, Worl"sv)); + EXPECT_TRUE(buf->startsWith("Hello, World"sv)); + EXPECT_TRUE(buf->startsWith("Hello, World!"sv)); +}