diff --git a/include/libtorrent/receive_buffer.hpp b/include/libtorrent/receive_buffer.hpp new file mode 100644 index 000000000..4aeac94bd --- /dev/null +++ b/include/libtorrent/receive_buffer.hpp @@ -0,0 +1,280 @@ +/* + +Copyright (c) 2014, Arvid Norberg, Steven Siloti +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_RECEIVE_BUFFER_HPP_INCLUDED +#define TORRENT_RECEIVE_BUFFER_HPP_INCLUDED + +#include +#include +#include +#include + +namespace libtorrent { + +inline int round_up8(int v) +{ + return ((v & 7) == 0) ? v : v + (8 - (v & 7)); +} + +struct receive_buffer +{ + friend struct crypto_receive_buffer; + + receive_buffer(buffer_allocator_interface& allocator) + : m_recv_start(0) + , m_recv_end(0) + , m_recv_pos(0) + , m_packet_size(0) + , m_soft_packet_size(0) + , m_disk_recv_buffer_size(0) + , m_disk_recv_buffer(allocator, 0) + {} + + int packet_size() const { return m_packet_size; } + int packet_bytes_remaining() const + { + TORRENT_ASSERT(m_recv_start == 0); + return m_packet_size - m_recv_pos; + } + + int max_receive(); + + bool packet_finished() const { return m_packet_size <= m_recv_pos; } + int pos() const { return m_recv_pos; } + int capacity() const { return m_recv_buffer.capacity() + m_disk_recv_buffer_size; } + + int regular_buffer_size() const { return m_packet_size - m_disk_recv_buffer_size; } + + // regular buffer only + boost::asio::mutable_buffer reserve(int size); + // with possible disk buffer usage + int reserve(boost::array& vec, int size); + + void received(int bytes_transferred) + { + TORRENT_ASSERT(m_packet_size > 0); + m_recv_end += bytes_transferred; + TORRENT_ASSERT(m_recv_pos <= int(m_recv_buffer.size() + + m_disk_recv_buffer_size)); + } + + int advance_pos(int bytes); + bool pos_at_end() { return m_recv_pos == m_recv_end; } + + void clamp_size(); + + void set_soft_packet_size(int size) { m_soft_packet_size = size; } + + // size = the packet size to remove from the receive buffer + // packet_size = the next packet size to receive in the buffer + // offset = the offset into the receive buffer where to remove `size` bytes + void cut(int size, int packet_size, int offset = 0); + + buffer::const_interval get() const; + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) + // returns the entire regular buffer + // should only be used during the handshake + buffer::interval mutable_buffer(); + // returns the last 'bytes' from the receive buffer + void mutable_buffers(std::vector& vec, int bytes); +#endif + + void free_disk_buffer() + { + m_disk_recv_buffer.reset(); + m_disk_recv_buffer_size = 0; + } + + bool has_disk_buffer() const { return m_disk_recv_buffer; } + void assert_no_disk_buffer() const + { + TORRENT_ASSERT(!m_disk_recv_buffer); + TORRENT_ASSERT(m_disk_recv_buffer_size == 0); + } + + void assign_disk_buffer(char* buffer, int size); + char* release_disk_buffer(); + + // the purpose of this function is to free up and cut off all messages + // in the receive buffer that have been parsed and processed. + void normalize(); + bool normalized() const { return m_recv_start == 0; } + + void reset(int packet_size); + + bool can_recv_contiguous(int /*size*/) const { return true; } + +#if TORRENT_USE_INVARIANT_CHECKS + void check_invariant() const + { + TORRENT_ASSERT(m_recv_end >= m_recv_start); + TORRENT_ASSERT(bool(m_disk_recv_buffer) == (m_disk_recv_buffer_size > 0)); + } +#endif + +private: + // recv_buf.begin (start of actual receive buffer) + // | + // | m_recv_start (logical start of current + // | | receive buffer, as perceived by upper layers) + // | | + // | | m_recv_pos (number of bytes consumed + // | | | by upper layer, from logical receive buffer) + // | | | + // | x---------x + // | | | recv_buf.end (end of actual receive buffer) + // | | | | + // v v v v + // *------==========--------- + // ^ + // | + // | + // ------------------->x m_recv_end (end of received data, + // beyond this point is garbage) + // m_recv_buffer + + // when not using contiguous receive buffers, there + // may be a disk_recv_buffer in the mix as well. Whenever + // m_disk_recv_buffer_size > 0 (and presumably also + // m_disk_recv_buffer != NULL) the disk buffer is imagined + // to be appended to the receive buffer right after m_recv_end. + + // the start of the logical receive buffer + int m_recv_start; + + // the number of valid, received bytes in m_recv_buffer + int m_recv_end; + + // the byte offset in m_recv_buffer that we have + // are passing on to the upper layer. This is + // always <= m_recv_end + int m_recv_pos; + + // the size (in bytes) of the bittorrent message + // we're currently receiving + int m_packet_size; + + // the number of bytes that the other + // end has to send us in order to respond + // to all outstanding piece requests we + // have sent to it + int m_soft_packet_size; + + int m_disk_recv_buffer_size; + + buffer m_recv_buffer; + + // if this peer is receiving a piece, this + // points to a disk buffer that the data is + // read into. This eliminates a memcopy from + // the receive buffer into the disk buffer + disk_buffer_holder m_disk_recv_buffer; +}; + +#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS) +// Wraps a receive_buffer to provide the ability to inject +// possibly authenticated crypto beneath the bittorrent protocol. +// When authenticated crypto is in use the wrapped receive_buffer +// holds the receive state of the crpyto layer while this class +// tracks the state of the bittorrent protocol. +struct crypto_receive_buffer +{ + crypto_receive_buffer(receive_buffer& next) + : m_recv_pos(INT_MAX) + , m_packet_size(0) + , m_soft_packet_size(0) + , m_connection_buffer(next) + {} + + buffer::interval mutable_buffer() { return m_connection_buffer.mutable_buffer(); } + char* release_disk_buffer() { return m_connection_buffer.release_disk_buffer(); } + bool has_disk_buffer() const { return m_connection_buffer.has_disk_buffer(); } + void assert_no_disk_buffer() const { m_connection_buffer.assert_no_disk_buffer(); } + + bool packet_finished() const; + + bool crypto_packet_finished() const + { + return m_recv_pos == INT_MAX || m_connection_buffer.packet_finished(); + } + + int packet_size() const; + + int crypto_packet_size() const + { + TORRENT_ASSERT(m_recv_pos != INT_MAX); + return m_connection_buffer.packet_size() - m_recv_pos; + } + + int pos() const; + + void cut(int size, int packet_size, int offset = 0); + + void crypto_cut(int size, int packet_size) + { + TORRENT_ASSERT(m_recv_pos != INT_MAX); + m_connection_buffer.cut(size, m_recv_pos + packet_size, m_recv_pos); + } + + void reset(int packet_size); + void crypto_reset(int packet_size); + + void set_soft_packet_size(int size); + + int advance_pos(int bytes); + + buffer::const_interval get() const; + + bool can_recv_contiguous(int /*size*/) const + { + // TODO: Detect when the start of the next crpyto packet is aligned + // with the start of piece data and the crpyto packet is at least + // as large as the piece data. With a little extra work + // we could receive directly into a disk buffer in that case. + return m_recv_pos == INT_MAX; + } + + void mutable_buffers(std::vector& vec + , std::size_t bytes_transfered); + +private: + int m_recv_pos; + int m_packet_size; + int m_soft_packet_size; + receive_buffer& m_connection_buffer; +}; +#endif // TORRENT_DISABLE_ENCRYPTION + +} // namespace libtorrent + +#endif // #ifndef TORRENT_RECEIVE_BUFFER_HPP_INCLUDED