Modernize bitfield.cc: Storage changes and refactor (#1927)

* Modernize bitfield.cc: Storage changed to vector of bytes, return vector from getRaw, new Span readonly memory view
Modernize bitfield.cc: Code style/review notes
Modernize bitfield.cc: Code format

* Modernize bitfield.cc: Swap end and begin in bit counting code assertion

* Modernize bitfield.cc: Rewrite states and simplify code

* Modernize bitfield.cc: Fixing the code and tests

* Modernize bitfield.cc: Fixing tests

* Modernize bitfield.cc: Formatting; +std::size, +const
This commit is contained in:
Dmytro Lytovchenko 2021-10-16 16:04:19 +02:00 committed by GitHub
parent 43ad1346eb
commit 953f07375a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 448 additions and 355 deletions

View File

@ -7,18 +7,19 @@
*/
#include <algorithm>
#include <cstring> /* memset */
#include <iterator> // std::back_inserter
#include <numeric> // std::accumulate
#include "transmission.h"
#include "bitfield.h"
#include "tr-assert.h"
#include "utils.h" /* tr_new0() */
#include "span.h"
/****
*****
****/
static constexpr int8_t const trueBitCount[256] = {
std::array<int8_t const, 256> Bitfield::true_bits_lookup_ = {
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, //
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, //
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, //
@ -37,17 +38,13 @@ static constexpr int8_t const trueBitCount[256] = {
4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, //
};
constexpr size_t Bitfield::countArray() const
size_t Bitfield::countArray() const
{
size_t ret = 0;
size_t i = this->alloc_count_;
while (i > 0)
{
ret += trueBitCount[this->bits_[--i]];
}
return ret;
return std::accumulate(
std::begin(bits_),
std::end(bits_),
0,
[](auto acc, auto item) { return acc + true_bits_lookup_[item]; });
}
size_t Bitfield::countRangeImpl(size_t begin, size_t end) const
@ -56,22 +53,21 @@ size_t Bitfield::countRangeImpl(size_t begin, size_t end) const
size_t const first_byte = begin >> 3U;
size_t const last_byte = (end - 1) >> 3U;
if (this->bit_count_ == 0)
if (bit_count_ == 0)
{
return 0;
}
if (first_byte >= this->alloc_count_)
if (first_byte >= std::size(bits_))
{
return 0;
}
TR_ASSERT(begin < end);
TR_ASSERT(this->bits_ != nullptr);
if (first_byte == last_byte)
{
uint8_t val = this->bits_[first_byte];
uint8_t val = bits_[first_byte];
int i = begin - (first_byte * 8);
val <<= i;
@ -80,58 +76,63 @@ size_t Bitfield::countRangeImpl(size_t begin, size_t end) const
val >>= i;
val <<= i;
ret += trueBitCount[val];
ret += true_bits_lookup_[val];
}
else
{
size_t const walk_end = std::min(this->alloc_count_, last_byte);
size_t const walk_end = std::min(std::size(bits_), last_byte);
/* first byte */
size_t const first_shift = begin - (first_byte * 8);
uint8_t val = this->bits_[first_byte];
uint8_t val = bits_[first_byte];
val <<= first_shift;
val >>= first_shift;
ret += trueBitCount[val];
ret += true_bits_lookup_[val];
/* middle bytes */
for (size_t i = first_byte + 1; i < walk_end; ++i)
{
ret += trueBitCount[this->bits_[i]];
ret += true_bits_lookup_[bits_[i]];
}
/* last byte */
if (last_byte < this->alloc_count_)
if (last_byte < std::size(bits_))
{
size_t const last_shift = (last_byte + 1) * 8 - end;
val = this->bits_[last_byte];
val = bits_[last_byte];
val >>= last_shift;
val <<= last_shift;
ret += trueBitCount[val];
ret += true_bits_lookup_[val];
}
}
TR_ASSERT(ret <= (begin - end));
TR_ASSERT(ret <= (end - begin));
return ret;
}
bool Bitfield::readBit(size_t n) const
{
if (this->hasAll())
switch (mode_)
{
case OperationMode::Normal:
{
auto const byte_offset = n >> 3;
if (byte_offset >= std::size(bits_))
{
return false;
}
return ((bits_[byte_offset] << (n & 7)) & 0x80) != 0;
}
case OperationMode::All:
return true;
}
if (this->hasNone())
{
case OperationMode::None:
return false;
case OperationMode::Start:
TR_UNREACHABLE();
}
if (n >> 3U >= this->alloc_count_)
{
return false;
}
return (this->bits_[n >> 3U] << (n & 7U) & 0x80) != 0;
return false;
}
/***
@ -142,8 +143,13 @@ bool Bitfield::readBit(size_t n) const
bool Bitfield::isValid() const
{
TR_ASSERT((this->alloc_count_ == 0) == (this->bits_ == nullptr));
TR_ASSERT(this->bits_ == nullptr || this->true_count_ == this->countArray());
TR_ASSERT_MSG(
std::size(bits_) == 0 || true_count_ == countArray(),
"Invalid Bitfield state: bits.size()=%zu bit_count=%zu true_count=%zu countArray()=%zu",
std::size(bits_),
bit_count_,
true_count_,
countArray());
return true;
}
@ -152,124 +158,54 @@ bool Bitfield::isValid() const
size_t Bitfield::countBits() const
{
TR_ASSERT(this->isValid());
TR_ASSERT(isValid());
return this->true_count_;
return true_count_;
}
void Bitfield::setBitsInArray(uint8_t* array, size_t bit_count)
void Bitfield::setBitsInArray(std::vector<uint8_t>& array, size_t bit_count)
{
uint8_t const val = 0xFF;
size_t const n = getStorageSize(bit_count);
TR_ASSERT(getStorageSize(bit_count) == std::size(array));
if (n > 0)
if (!std::empty(array) && bit_count > 0)
{
memset(array, val, n - 1);
auto const last_byte_index = getStorageSize(bit_count) - 1;
array[n - 1] = val << (n * 8 - bit_count);
std::fill_n(std::begin(array), last_byte_index, 0xFF);
array[last_byte_index] = 0xFF << (last_byte_index * 8 - bit_count);
}
}
void* Bitfield::getRaw(size_t* byte_count) const
std::vector<uint8_t> Bitfield::getRaw() const
{
TR_ASSERT(this->bit_count_ > 0);
TR_ASSERT(bit_count_ > 0);
size_t const n = getStorageSize(this->bit_count_);
uint8_t* newBits = tr_new0(uint8_t, n);
size_t const n = getStorageSize(bit_count_);
auto new_bits = std::vector<uint8_t>(n);
if (this->alloc_count_ != 0)
if (!std::empty(bits_))
{
TR_ASSERT(this->alloc_count_ <= n);
std::memcpy(newBits, this->bits_, this->alloc_count_);
TR_ASSERT(std::size(bits_) <= n);
std::copy(std::cbegin(bits_), std::cend(bits_), std::back_inserter(new_bits));
}
else if (this->hasAll())
else if (hasAll())
{
setBitsInArray(newBits, this->bit_count_);
setBitsInArray(new_bits, bit_count_);
}
*byte_count = n;
return newBits;
return new_bits;
}
void Bitfield::ensureBitsAlloced(size_t n)
void Bitfield::ensureNthBitFits(size_t n)
{
size_t bytes_needed;
bool const has_all = this->hasAll();
TR_ASSERT_MSG(mode_ == OperationMode::Normal, "Can only reallocate storage in Normal mode");
if (has_all)
size_t bytes_needed = getStorageSize(std::max(n, true_count_));
if (std::size(bits_) < bytes_needed)
{
bytes_needed = getStorageSize(std::max(n, this->true_count_));
bits_.resize(bytes_needed);
}
else
{
bytes_needed = getStorageSize(n);
}
if (this->alloc_count_ < bytes_needed)
{
this->bits_ = tr_renew(uint8_t, this->bits_, bytes_needed);
std::memset(this->bits_ + this->alloc_count_, 0, bytes_needed - this->alloc_count_);
this->alloc_count_ = bytes_needed;
if (has_all)
{
setBitsInArray(this->bits_, this->true_count_);
}
}
}
bool Bitfield::ensureNthBitAlloced(size_t nth)
{
// count is zero-based, so we need to allocate nth+1 bits before setting the nth
if (nth == SIZE_MAX)
{
return false;
}
this->ensureBitsAlloced(nth + 1);
return true;
}
void Bitfield::freeArray()
{
tr_free(this->bits_);
this->bits_ = nullptr;
this->alloc_count_ = 0;
}
void Bitfield::setTrueCount(size_t n)
{
TR_ASSERT(this->bit_count_ == 0 || n <= this->bit_count_);
this->true_count_ = n;
if (this->hasAll() || this->hasNone())
{
this->freeArray();
}
TR_ASSERT(this->isValid());
}
void Bitfield::rebuildTrueCount()
{
this->setTrueCount(this->countArray());
}
void Bitfield::incTrueCount(size_t i)
{
TR_ASSERT(this->bit_count_ == 0 || i <= this->bit_count_);
TR_ASSERT(this->bit_count_ == 0 || this->true_count_ <= this->bit_count_ - i);
this->setTrueCount(this->true_count_ + i);
}
void Bitfield::decTrueCount(size_t i)
{
TR_ASSERT(this->bit_count_ == 0 || i <= this->bit_count_);
TR_ASSERT(this->bit_count_ == 0 || this->true_count_ >= i);
this->setTrueCount(this->true_count_ - i);
}
/****
@ -278,215 +214,212 @@ void Bitfield::decTrueCount(size_t i)
Bitfield::Bitfield(size_t bit_count)
{
this->bit_count_ = bit_count;
this->true_count_ = 0;
this->bits_ = nullptr;
this->alloc_count_ = 0;
this->hint_ = NORMAL;
bit_count_ = bit_count;
true_count_ = 0;
setMode(OperationMode::Normal);
TR_ASSERT(this->isValid());
TR_ASSERT(isValid());
}
void Bitfield::setHasNone()
void Bitfield::setMode(Bitfield::OperationMode new_mode)
{
this->freeArray();
this->true_count_ = 0;
this->hint_ = HAS_NONE;
TR_ASSERT(this->isValid());
}
void Bitfield::setHasAll()
{
this->freeArray();
this->true_count_ = this->bit_count_;
this->hint_ = HAS_ALL;
TR_ASSERT(this->isValid());
}
void Bitfield::setFromBitfield(Bitfield const& src)
{
if (src.hasAll())
switch (new_mode)
{
this->setHasAll();
}
else if (src.hasNone())
{
this->setHasNone();
}
else
{
this->setRaw(src.bits_, src.alloc_count_, true);
case OperationMode::Normal:
switch (mode_)
{
case OperationMode::All:
{
// Switching from ALL mode to NORMAL, should set the bits
mode_ = OperationMode::Normal;
ensureNthBitFits(bit_count_);
setBitRangeImpl(0, bit_count_ - 1);
true_count_ = bit_count_; // switching from mode ALL, all bits are set
break;
}
case OperationMode::None:
{
// Switching from ALL mode to NORMAL, should set the bits
mode_ = OperationMode::Normal;
ensureNthBitFits(bit_count_);
clearBitRangeImpl(0, bit_count_ - 1);
true_count_ = 0; // switching from mode NONE, all bits are not set
break;
}
case OperationMode::Start:
mode_ = OperationMode::Normal;
// fall through
case OperationMode::Normal:
break;
}
break;
case OperationMode::All:
clearStorage();
true_count_ = bit_count_;
mode_ = OperationMode::All;
break;
case OperationMode::None:
clearStorage();
true_count_ = 0;
mode_ = OperationMode::None;
break;
case OperationMode::Start:
TR_UNREACHABLE();
break;
}
TR_ASSERT(isValid());
}
void Bitfield::setRaw(void const* newBits, size_t byte_count, bool bounded)
Bitfield::Bitfield(Span<uint8_t> new_bits, size_t bit_count, bool bounded)
: bit_count_(bit_count)
{
this->freeArray();
this->true_count_ = 0;
true_count_ = 0;
setMode(OperationMode::Normal);
// Having bounded=true, limits the amount of moved data to available storage size
size_t byte_count = bounded ? std::min(std::size(new_bits), getStorageSize(bit_count_)) : std::size(new_bits);
bits_.resize(byte_count);
std::copy_n(std::begin(new_bits), byte_count, std::begin(bits_));
if (bounded)
{
byte_count = std::min(byte_count, getStorageSize(this->bit_count_));
}
this->bits_ = static_cast<uint8_t*>(tr_memdup(newBits, byte_count));
this->alloc_count_ = byte_count;
if (bounded)
{
/* ensure the excess newBits are set to '0' */
int const excess_bit_count = byte_count * 8 - this->bit_count_;
TR_ASSERT(excess_bit_count >= 0);
TR_ASSERT(excess_bit_count <= 7);
/* ensure the excess new_bits are set to '0' */
int const excess_bit_count = bit_count_ & 7;
if (excess_bit_count != 0)
{
this->bits_[this->alloc_count_ - 1] &= 0xff << excess_bit_count;
bits_[byte_count - 1] &= 0xFF << excess_bit_count;
}
}
this->rebuildTrueCount();
setTrueCount(countArray());
}
void Bitfield::setFromFlags(bool const* flags, size_t n)
Bitfield::Bitfield(bool const* flags, size_t n)
: mode_(OperationMode::Normal)
{
size_t trueCount = 0;
this->freeArray();
this->ensureBitsAlloced(n);
clearStorage();
ensureNthBitFits(n);
for (size_t i = 0; i < n; ++i)
TR_ASSERT(std::size(bits_) >= getStorageSize(n));
for (size_t index = 0; index < n; ++index)
{
if (flags[i] && this->bits_ != nullptr)
if (flags[index])
{
++trueCount;
this->bits_[i >> 3U] |= (0x80 >> (i & 7U));
bits_[index >> 3] |= (0x80 >> (index & 7));
}
}
this->setTrueCount(trueCount);
setTrueCount(trueCount);
}
void Bitfield::setBit(size_t bit)
void Bitfield::setBit(size_t bit_index)
{
if (!this->readBit(bit) && this->ensureNthBitAlloced(bit))
switch (mode_)
{
size_t const offset = bit >> 3U;
if ((this->bits_ != nullptr) && (offset < this->alloc_count_))
case OperationMode::Normal:
{
this->bits_[offset] |= 0x80 >> (bit & 7U);
this->incTrueCount(1);
setBitImpl(bit_index);
break;
}
case OperationMode::All:
TR_ASSERT(bit_index <= bit_count_);
break;
case OperationMode::None:
setMode(OperationMode::Normal);
setBitImpl(bit_index);
break;
case OperationMode::Start:
TR_UNREACHABLE();
break;
}
}
void Bitfield::setBitRange(size_t begin, size_t end)
{
size_t sb;
size_t eb;
unsigned char sm;
unsigned char em;
size_t const diff = (end - begin) - this->countRange(begin, end);
if (mode_ == OperationMode::All)
{
return;
}
else if (mode_ == OperationMode::None)
{
setMode(OperationMode::Normal);
}
if (diff == 0)
size_t const true_bits_difference = (end - begin) - countRange(begin, end);
if (true_bits_difference == 0)
{
return;
}
end--;
if (end >= this->bit_count_ || begin > end)
if (end >= bit_count_ || begin > end)
{
return;
}
sb = begin >> 3;
sm = ~(0xff << (8 - (begin & 7)));
eb = end >> 3;
em = 0xff << (7 - (end & 7));
setBitRangeImpl(begin, end);
if (!this->ensureNthBitAlloced(end))
{
return;
}
if (sb == eb)
{
this->bits_[sb] |= sm & em;
}
else
{
this->bits_[sb] |= sm;
this->bits_[eb] |= em;
if (++sb < eb)
{
std::memset(this->bits_ + sb, 0xff, eb - sb);
}
}
this->incTrueCount(diff);
TR_ASSERT(true_count_ + true_bits_difference <= bit_count_);
setTrueCount(true_count_ + true_bits_difference);
}
void Bitfield::clearBit(size_t bit)
{
TR_ASSERT(this->isValid());
TR_ASSERT(isValid());
if (this->readBit(bit) && this->ensureNthBitAlloced(bit))
switch (mode_)
{
this->bits_[bit >> 3U] &= 0xff7f >> (bit & 7U);
this->decTrueCount(1);
case OperationMode::Normal:
clearBitImpl(bit);
break;
case OperationMode::All:
setMode(OperationMode::Normal);
clearBitImpl(bit);
break;
case OperationMode::None:
break;
case OperationMode::Start:
TR_UNREACHABLE();
break;
}
}
void Bitfield::clearBitRange(size_t begin, size_t end)
{
size_t sb;
size_t eb;
unsigned char sm;
unsigned char em;
size_t const diff = this->countRange(begin, end);
if (mode_ == OperationMode::None)
{
return;
}
else if (mode_ == OperationMode::All)
{
setMode(OperationMode::Normal);
}
if (diff == 0)
size_t const true_bits_difference = countRange(begin, end); // all true bits in range will be gone
if (true_bits_difference == 0)
{
return;
}
end--;
if (end >= this->bit_count_ || begin > end)
if (end >= bit_count_ || begin > end)
{
return;
}
sb = begin >> 3;
sm = 0xff << (8 - (begin & 7));
eb = end >> 3;
em = ~(0xff << (7 - (end & 7)));
clearBitRangeImpl(begin, end);
if (!this->ensureNthBitAlloced(end))
{
return;
}
if (sb == eb)
{
this->bits_[sb] &= sm | em;
}
else
{
this->bits_[sb] &= sm;
this->bits_[eb] &= em;
if (++sb < eb)
{
std::memset(this->bits_ + sb, 0, eb - sb);
}
}
this->decTrueCount(diff);
TR_ASSERT(true_count_ >= true_bits_difference);
setTrueCount(true_count_ - true_bits_difference);
}

View File

@ -12,14 +12,33 @@
#error only libtransmission should #include this header.
#endif
#include <array>
#include <vector>
#include "transmission.h"
#include "tr-macros.h"
#include "tr-assert.h"
#include "span.h"
/// @brief Implementation of the BitTorrent spec's Bitfield array of bits
struct Bitfield
{
public:
/// @brief State diagram for modes of operation: None -> Normal <==> All
/// ALL and NONE: Special cases for when full or empty but we don't know the bitCount.
/// This occurs when a magnet link's peers send have all / have none
enum struct OperationMode
{
/// @brief State at the creation
Start,
/// @brief Normal operation: storage of bytes contains bits to set or clear
Normal,
/// @brief If bit_count_==0, storage is inactive, consider all bits to be 1
All,
/// @brief If bit_count_==0, storage is inactive, consider all bits to be 0
None,
};
/***
**** life cycle
***/
@ -30,21 +49,29 @@ public:
{
}
/// @brief Builds bits from array of boolean flags
Bitfield(bool const* bytes, size_t n);
~Bitfield()
{
this->setHasNone();
setMode(OperationMode::None);
}
/***
****
***/
void setHasAll();
/// @brief Creates new Bitfield with same count of bits as *this and replaces data from new_bits
void setFrom(Span<uint8_t> new_bits, bool bounded)
{
*this = Bitfield(new_bits, this->bit_count_, bounded);
}
void setHasNone();
/// @brief Change the state (mode of operation)
void setMode(OperationMode new_mode);
/// @brief Sets one bit
void setBit(size_t bit);
void setBit(size_t bit_index);
/// @brief Sets bit range [begin, end) to 1
void setBitRange(size_t begin, size_t end);
@ -61,29 +88,31 @@ public:
[[nodiscard]] size_t countRange(size_t begin, size_t end) const
{
if (this->hasAll())
if (hasAll())
{
return end - begin;
}
if (this->hasNone())
if (hasNone())
{
return 0;
}
return this->countRangeImpl(begin, end);
return countRangeImpl(begin, end);
}
[[nodiscard]] size_t countBits() const;
/// @brief Returns whether all bits are set, or mode is ALL. Always false for zero sized bitfield.
[[nodiscard]] constexpr bool hasAll() const
{
return this->bit_count_ != 0 ? (this->true_count_ == this->bit_count_) : this->hint_ == HAS_ALL;
return ((bit_count_ != 0) && (true_count_ == bit_count_)) || (mode_ == OperationMode::All);
}
/// @brief Returns whether all bits are clear, or mode is NONE. Always false for zero sized bitfield.
[[nodiscard]] constexpr bool hasNone() const
{
return this->bit_count_ != 0 ? (this->true_count_ == 0) : this->hint_ == HAS_NONE;
return ((bit_count_ != 0) && (true_count_ == 0)) || (mode_ == OperationMode::None);
}
[[nodiscard]] bool readBit(size_t n) const;
@ -92,13 +121,7 @@ public:
****
***/
void setFromFlags(bool const* bytes, size_t n);
void setFromBitfield(Bitfield const& src);
void setRaw(void const* newBits, size_t byte_count, bool bounded);
void* getRaw(size_t* byte_count) const;
[[nodiscard]] std::vector<uint8_t> getRaw() const;
[[nodiscard]] size_t getBitCount() const
{
@ -106,41 +129,140 @@ public:
}
private:
[[nodiscard]] constexpr size_t countArray() const;
[[nodiscard]] size_t countRangeImpl(size_t begin, size_t end) const;
static void setBitsInArray(uint8_t* array, size_t bit_count);
/// @brief Copies bits from the readonly view new_bits. Use Bitfield::setFrom to access this constructor
/// @param bounded Whether incoming data is constrained by our memory and bit size
Bitfield(Span<uint8_t> new_bits, size_t bit_count, bool bounded);
/// @brief Contains lookup table for how many set bits are there in 0..255
static std::array<int8_t const, 256> true_bits_lookup_;
static constexpr size_t getStorageSize(size_t bit_count)
{
return (bit_count >> 3) + ((bit_count & 7) != 0 ? 1 : 0);
return 1 + ((bit_count + 7) >> 3);
}
[[nodiscard]] size_t countArray() const;
[[nodiscard]] size_t countRangeImpl(size_t begin, size_t end) const;
/// @brief Given bit count, sets that many bits in the array, assumes array size is big enough.
static void setBitsInArray(std::vector<uint8_t>& array, size_t bit_count);
void ensureNthBitFits(size_t n);
inline void setTrueCount(size_t n)
{
TR_ASSERT(mode_ == OperationMode::Normal);
TR_ASSERT(n <= bit_count_);
true_count_ = n;
TR_ASSERT(isValid());
}
void ensureBitsAlloced(size_t n);
bool ensureNthBitAlloced(size_t nth);
void freeArray();
void setTrueCount(size_t n);
void rebuildTrueCount();
void incTrueCount(size_t i);
void decTrueCount(size_t i);
#ifdef TR_ENABLE_ASSERTS
[[nodiscard]] bool isValid() const;
#endif
uint8_t* bits_ = nullptr;
size_t alloc_count_ = 0;
/// @brief Set the bit
inline void setBitImpl(size_t bit)
{
TR_ASSERT_MSG(mode_ == OperationMode::Normal, "Can only set bits in Normal operation mode");
TR_ASSERT(isValid());
if (!readBit(bit))
{
ensureNthBitFits(bit);
auto const byte_offset = bit >> 3;
size_t bit_value = size_t{ 0x80U } >> (bit & 7);
bits_[byte_offset] |= bit_value;
setTrueCount(true_count_ + 1);
}
TR_ASSERT(isValid());
}
/// @brief Clear the bit
inline void clearBitImpl(size_t bit)
{
TR_ASSERT_MSG(mode_ == OperationMode::Normal, "Can only set bits in Normal operation mode");
TR_ASSERT(isValid());
if (readBit(bit))
{
ensureNthBitFits(bit);
size_t const byte_mask = size_t{ 0xFF7FU } >> (bit & 7U);
bits_[bit >> 3] &= byte_mask;
TR_ASSERT(true_count_ > 0);
setTrueCount(true_count_ - 1);
}
TR_ASSERT(isValid());
}
/// @brief Ensure that the memory is properly deallocated and size becomes zero
inline void clearStorage()
{
bits_ = std::vector<uint8_t>();
}
inline void setBitRangeImpl(size_t begin, size_t end)
{
size_t start_byte = begin >> 3;
size_t start_mask = ~(size_t{ 0xFFU } << (8 - (begin & 7)));
size_t end_byte = end >> 3;
size_t end_mask = size_t{ 0xFFU } << (7 - (end & 7));
ensureNthBitFits(end);
if (start_byte == end_byte)
{
bits_[start_byte] |= start_mask & end_mask;
}
else
{
bits_[start_byte] |= start_mask;
bits_[end_byte] |= end_mask;
if (++start_byte < end_byte)
{
std::fill_n(std::begin(bits_) + start_byte, end_byte - start_byte, 0xFF);
}
}
}
inline void clearBitRangeImpl(size_t begin, size_t end)
{
size_t start_byte = begin >> 3;
size_t start_mask = size_t{ 0xFFU } << (8 - (begin & 7));
size_t end_byte = end >> 3;
size_t end_mask = ~(size_t{ 0xFFU } << (7 - (end & 7)));
ensureNthBitFits(end);
if (start_byte == end_byte)
{
bits_[start_byte] &= start_mask | end_mask;
}
else
{
bits_[start_byte] &= start_mask;
bits_[end_byte] &= end_mask;
if (++start_byte < end_byte)
{
std::fill_n(std::begin(bits_) + start_byte, end_byte - start_byte, 0);
}
}
}
std::vector<uint8_t> bits_;
size_t bit_count_ = 0;
size_t true_count_ = 0;
enum OperationMode
{
/// @brief Normal operation: storage of bytes contains bits to set or clear
NORMAL,
/// @brief If bit_count_==0, storage is inactive, consider all bits to be 1
HAS_ALL,
/// @brief If bit_count_==0, storage is inactive, consider all bits to be 0
HAS_NONE,
};
// Special cases for when full or empty but we don't know the bitCount.
// This occurs when a magnet link's peers send have all / have none
OperationMode hint_ = NORMAL;
OperationMode mode_ = OperationMode::Start;
};

View File

@ -21,7 +21,7 @@ static void tr_cpReset(tr_completion* cp)
cp->sizeNow = 0;
cp->sizeWhenDoneIsDirty = true;
cp->haveValidIsDirty = true;
cp->blockBitfield->setHasNone();
cp->blockBitfield->setMode(Bitfield::OperationMode::None);
}
void tr_cpConstruct(tr_completion* cp, tr_torrent* tor)
@ -36,7 +36,7 @@ void tr_cpBlockInit(tr_completion* cp, Bitfield const& b)
tr_cpReset(cp);
// set blockBitfield
cp->blockBitfield->setFromBitfield(b);
*(cp->blockBitfield) = b;
// set sizeNow
cp->sizeNow = cp->blockBitfield->countBits();
@ -294,11 +294,10 @@ bool tr_cpFileIsComplete(tr_completion const* cp, tr_file_index_t i)
return cp->blockBitfield->countRange(f, l + 1) == (l + 1 - f);
}
void* tr_cpCreatePieceBitfield(tr_completion const* cp, size_t* byte_count)
std::vector<uint8_t> tr_cpCreatePieceBitfield(tr_completion const* cp)
{
TR_ASSERT(tr_torrentHasMetadata(cp->tor));
void* ret;
tr_piece_index_t n;
n = cp->tor->info.pieceCount;
@ -307,7 +306,7 @@ void* tr_cpCreatePieceBitfield(tr_completion const* cp, size_t* byte_count)
if (tr_cpHasAll(cp))
{
pieces.setHasAll();
pieces.setMode(Bitfield::OperationMode::All);
}
else if (!tr_cpHasNone(cp))
{
@ -318,12 +317,11 @@ void* tr_cpCreatePieceBitfield(tr_completion const* cp, size_t* byte_count)
flags[i] = tr_cpPieceIsComplete(cp, i);
}
pieces.setFromFlags(flags, n);
pieces = Bitfield(flags, n);
tr_free(flags);
}
ret = pieces.getRaw(byte_count);
return ret;
return pieces.getRaw();
}
double tr_cpPercentComplete(tr_completion const* cp)

View File

@ -124,7 +124,7 @@ static inline bool tr_cpBlockIsComplete(tr_completion const* cp, tr_block_index_
bool tr_cpFileIsComplete(tr_completion const* cp, tr_file_index_t);
void* tr_cpCreatePieceBitfield(tr_completion const* cp, size_t* byte_count);
std::vector<uint8_t> tr_cpCreatePieceBitfield(tr_completion const* cp);
constexpr void tr_cpInvalidateDND(tr_completion* cp)
{

View File

@ -1681,7 +1681,7 @@ static ReadState readBtMessage(tr_peerMsgsImpl* msgs, struct evbuffer* inbuf, si
uint8_t* tmp = tr_new(uint8_t, msglen);
dbgmsg(msgs, "got a bitfield");
tr_peerIoReadBytes(msgs->io, inbuf, tmp, msglen);
msgs->have.setRaw(tmp, msglen, tr_torrentHasMetadata(msgs->torrent));
msgs->have.setFrom(Span{ tmp, msglen }, tr_torrentHasMetadata(msgs->torrent));
msgs->publishClientGotBitfield(&msgs->have);
updatePeerProgress(msgs);
tr_free(tmp);
@ -1775,7 +1775,7 @@ static ReadState readBtMessage(tr_peerMsgsImpl* msgs, struct evbuffer* inbuf, si
if (fext)
{
msgs->have.setHasAll();
msgs->have.setMode(Bitfield::OperationMode::All);
TR_ASSERT(msgs->have.hasAll());
msgs->publishClientGotHaveAll();
updatePeerProgress(msgs);
@ -1793,7 +1793,7 @@ static ReadState readBtMessage(tr_peerMsgsImpl* msgs, struct evbuffer* inbuf, si
if (fext)
{
msgs->have.setHasNone();
msgs->have.setMode(Bitfield::OperationMode::None);
msgs->publishClientGotHaveNone();
updatePeerProgress(msgs);
}
@ -2299,18 +2299,14 @@ static void sendBitfield(tr_peerMsgsImpl* msgs)
{
TR_ASSERT(tr_torrentHasMetadata(msgs->torrent));
void* bytes;
size_t byte_count = 0;
struct evbuffer* out = msgs->outMessages;
bytes = tr_torrentCreatePieceBitfield(msgs->torrent, &byte_count);
evbuffer_add_uint32(out, sizeof(uint8_t) + byte_count);
auto bytes = tr_torrentCreatePieceBitfield(msgs->torrent);
evbuffer_add_uint32(out, sizeof(uint8_t) + bytes.size());
evbuffer_add_uint8(out, BT_BITFIELD);
evbuffer_add(out, bytes, byte_count);
evbuffer_add(out, bytes.data(), std::size(bytes));
dbgmsg(msgs, "sending bitfield... outMessage size is now %zu", evbuffer_get_length(out));
pokeBatchPeriod(msgs, IMMEDIATE_PRIORITY_INTERVAL_SECS);
tr_free(bytes);
}
static void tellPeerWhatWeHave(tr_peerMsgsImpl* msgs)

View File

@ -484,10 +484,8 @@ static void bitfieldToBenc(Bitfield const* b, tr_variant* benc)
}
else
{
size_t byte_count = 0;
auto* raw = static_cast<uint8_t*>(b->getRaw(&byte_count));
tr_variantInitRaw(benc, raw, byte_count);
tr_free(raw);
auto const raw = b->getRaw();
tr_variantInitRaw(benc, raw.data(), std::size(raw));
}
}
@ -676,22 +674,22 @@ static uint64_t loadProgress(tr_variant* dict, tr_torrent* tor)
}
else if (buflen == 3 && memcmp(buf, "all", 3) == 0)
{
blocks.setHasAll();
blocks.setMode(Bitfield::OperationMode::All);
}
else if (buflen == 4 && memcmp(buf, "none", 4) == 0)
{
blocks.setHasNone();
blocks.setMode(Bitfield::OperationMode::None);
}
else
{
blocks.setRaw(buf, buflen, true);
blocks.setFrom(Span{ buf, buflen }, true);
}
}
else if (tr_variantDictFindStr(prog, TR_KEY_have, &str, nullptr))
{
if (strcmp(str, "all") == 0)
{
blocks.setHasAll();
blocks.setMode(Bitfield::OperationMode::All);
}
else
{
@ -700,7 +698,7 @@ static uint64_t loadProgress(tr_variant* dict, tr_torrent* tor)
}
else if (tr_variantDictFindRaw(prog, TR_KEY_bitfield, &raw, &rawlen))
{
blocks.setRaw(raw, rawlen, true);
blocks.setFrom(Span{ raw, rawlen }, true);
}
else
{

View File

@ -759,12 +759,10 @@ static void initField(
case TR_KEY_pieces:
if (tr_torrentHasMetadata(tor))
{
size_t byte_count = 0;
void* bytes = tr_torrentCreatePieceBitfield(tor, &byte_count);
auto* enc = static_cast<char*>(tr_base64_encode(bytes, byte_count, nullptr));
auto const bytes = tr_torrentCreatePieceBitfield(tor);
auto* enc = static_cast<char*>(tr_base64_encode(bytes.data(), std::size(bytes), nullptr));
tr_variantInitStr(initme, enc != nullptr ? enc : "", TR_BAD_SIZE);
tr_free(enc);
tr_free(bytes);
}
else
{

View File

@ -1514,7 +1514,7 @@ enum
static void turtleUpdateTable(struct tr_turtle_info* t)
{
t->minutes->setHasNone();
t->minutes->setMode(Bitfield::OperationMode::None);
for (int day = 0; day < 7; ++day)
{
@ -2981,7 +2981,7 @@ std::vector<tr_torrent*> tr_sessionGetNextQueuedTorrents(tr_session* session, tr
}
// find the best n candidates
num_wanted = std::min(num_wanted, candidates.size());
num_wanted = std::min(num_wanted, std::size(candidates));
if (num_wanted < candidates.size())
{
std::partial_sort(

45
libtransmission/span.h Normal file
View File

@ -0,0 +1,45 @@
/*
* This file Copyright (C) Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#pragma once
#ifndef __TRANSMISSION__
#error only libtransmission should #include this header.
#endif
/// @brief Readonly non-owning view into a provided memory block with start pointer and size.
/// In C++20 this appears in standard library as std::span and remotely similar usage.
template<typename T>
class Span
{
public:
Span(T const* ptr, size_t size)
: ptr_{ ptr }
, size_{ size }
{
}
T const* begin() const
{
return this->ptr_;
}
T const* end() const
{
return this->ptr_ + this->size_;
}
[[nodiscard]] size_t size() const
{
return size_;
}
private:
T const* ptr_;
size_t size_;
};

View File

@ -14,6 +14,7 @@
#include <string>
#include <unordered_set>
#include <vector>
#include "bandwidth.h" /* tr_bandwidth */
#include "completion.h" /* tr_completion */
@ -460,9 +461,9 @@ static inline size_t tr_torrentMissingBytesInPiece(tr_torrent const* tor, tr_pie
return tr_cpMissingBytesInPiece(&tor->completion, i);
}
static inline void* tr_torrentCreatePieceBitfield(tr_torrent const* tor, size_t* byte_count)
static inline std::vector<uint8_t> tr_torrentCreatePieceBitfield(tr_torrent const* tor)
{
return tr_cpCreatePieceBitfield(&tor->completion, byte_count);
return tr_cpCreatePieceBitfield(&tor->completion);
}
constexpr uint64_t tr_torrentHaveTotal(tr_torrent const* tor)

View File

@ -18,6 +18,7 @@
#define TR_ASSERT(x) ((void)(TR_LIKELY(x) || tr_assert_report(__FILE__, __LINE__, "%s", #x)))
#define TR_ASSERT_MSG(x, ...) ((void)(TR_LIKELY(x) || tr_assert_report(__FILE__, __LINE__, __VA_ARGS__)))
#define TR_UNREACHABLE() tr_assert_report(__FILE__, __LINE__, "Unreachable code")
#define TR_ENABLE_ASSERTS
@ -25,6 +26,7 @@
#define TR_ASSERT(x) ((void)0)
#define TR_ASSERT_MSG(x, ...) ((void)0)
#define TR_UNREACHABLE() ((void)0)
#undef TR_ENABLE_ASSERTS

View File

@ -1179,7 +1179,7 @@ uint16_t tr_torrentGetPeerLimit(tr_torrent const* tor);
enum
{
TR_PRI_LOW = -1,
TR_PRI_NORMAL = 0, /* since NORMAL is 0, memset initializes nicely */
TR_PRI_NORMAL = 0, /* since Normal is 0, memset initializes nicely */
TR_PRI_HIGH = 1
};

View File

@ -223,7 +223,7 @@ char const* tr_strip_positional_args(char const* str)
char const* in = str;
size_t pos = 0;
for (; str && *str && pos + 1 < buf.size(); ++str)
for (; str && *str && pos + 1 < std::size(buf); ++str)
{
buf[pos++] = *str;

View File

@ -67,7 +67,7 @@ public:
, bandwidth(tor->bandwidth)
{
// init parent bits
have.setHasAll();
have.setMode(Bitfield::OperationMode::All);
tr_peerUpdateProgress(tor, this);
file_urls.resize(tr_torrentInfo(tor)->fileCount);

View File

@ -182,11 +182,11 @@ TEST(Bitfields, hasAllNone)
EXPECT_TRUE(field.hasAll());
EXPECT_TRUE(!field.hasNone());
field.setHasNone();
field.setMode(Bitfield::OperationMode::None);
EXPECT_TRUE(!field.hasAll());
EXPECT_TRUE(field.hasNone());
field.setHasAll();
field.setMode(Bitfield::OperationMode::All);
EXPECT_TRUE(field.hasAll());
EXPECT_TRUE(!field.hasNone());
}
@ -197,11 +197,11 @@ TEST(Bitfields, hasAllNone)
EXPECT_TRUE(!field.hasAll());
EXPECT_TRUE(!field.hasNone());
field.setHasNone();
field.setMode(Bitfield::OperationMode::None);
EXPECT_TRUE(!field.hasAll());
EXPECT_TRUE(field.hasNone());
field.setHasAll();
field.setMode(Bitfield::OperationMode::All);
EXPECT_TRUE(field.hasAll());
EXPECT_TRUE(!field.hasNone());
}