Skip to content

Commit 3a086bb

Browse files
Update UVarInt implementation (#40)
Signed-off-by: SergKaprovich <[email protected]>
1 parent 4a05dba commit 3a086bb

File tree

4 files changed

+77
-105
lines changed

4 files changed

+77
-105
lines changed

include/libp2p/multi/uvarint.hpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,8 @@
1616
namespace libp2p::multi {
1717

1818
/**
19-
* A C++ wrapper for varint encoding implementation, which can be found in
20-
* c-utils library. Encodes unsigned integers into variable-length byte
21-
* arrays, efficient, having both an ability to store large numbers and not
22-
* wasting space on small ones. Mind that the maximum length of a varint is 8
23-
* bytes and it can store only unsigned integers
24-
* @see https://github.com/multiformats/unsigned-varint
19+
* @class Encodes and decodes unsigned integers into and from
20+
* variable-length byte arrays using LEB128 algorithm.
2521
*/
2622
class UVarint {
2723
public:
@@ -86,7 +82,7 @@ namespace libp2p::multi {
8682

8783
private:
8884
/// private ctor for unsafe creation
89-
UVarint(gsl::span<const uint8_t> varint_bytes, int64_t varint_size);
85+
UVarint(gsl::span<const uint8_t> varint_bytes, size_t varint_size);
9086

9187
std::vector<uint8_t> bytes_{};
9288
};

src/multi/uvarint.cpp

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,53 +11,39 @@ namespace libp2p::multi {
1111
using common::hex_upper;
1212

1313
UVarint::UVarint(uint64_t number) {
14-
bytes_.resize(8);
15-
size_t i = 0;
16-
size_t size = 0;
17-
for (; i < 8; i++) {
18-
bytes_[i] = static_cast<uint8_t>((number & 0xFFul) | 0x80ul);
19-
number >>= 7ul;
20-
if (number == 0) {
21-
bytes_[i] &= 0x7Ful;
22-
size = i + 1;
23-
break;
24-
}
25-
}
26-
bytes_.resize(size);
14+
do {
15+
uint8_t byte = static_cast<uint8_t>(number) & 0x7f;
16+
number >>= 7;
17+
if (number != 0)
18+
byte |= 0x80;
19+
bytes_.push_back(byte);
20+
} while (number != 0);
2721
}
2822

2923
UVarint::UVarint(gsl::span<const uint8_t> varint_bytes)
3024
: bytes_(varint_bytes.begin(),
3125
varint_bytes.begin() + calculateSize(varint_bytes)) {}
3226

33-
UVarint::UVarint(gsl::span<const uint8_t> varint_bytes, int64_t varint_size)
27+
UVarint::UVarint(gsl::span<const uint8_t> varint_bytes, size_t varint_size)
3428
: bytes_(varint_bytes.begin(), varint_bytes.begin() + varint_size) {}
3529

3630
boost::optional<UVarint> UVarint::create(
3731
gsl::span<const uint8_t> varint_bytes) {
38-
if (varint_bytes.empty()) {
39-
return {};
32+
size_t size = calculateSize(varint_bytes);
33+
if (size > 0) {
34+
return UVarint{varint_bytes, size};
4035
}
41-
// no use of calculateSize(..), as it is unsafe in this case
42-
int64_t s = 0;
43-
while ((varint_bytes[s] & 0x80u) != 0) {
44-
++s;
45-
if (s >= varint_bytes.size()) {
46-
return {};
47-
}
48-
}
49-
return UVarint{varint_bytes, s + 1};
36+
return {};
5037
}
5138

5239
uint64_t UVarint::toUInt64() const {
5340
uint64_t res = 0;
54-
for (size_t i = 0; i < 8 && i < bytes_.size(); i++) {
55-
res |= ((bytes_[i] & 0x7ful) << (7 * i));
56-
if ((bytes_[i] & 0x80ul) == 0) {
57-
return res;
58-
}
41+
size_t index = 0;
42+
for (const auto &byte : bytes_) {
43+
res += static_cast<uint64_t>((byte & 0x7f)) << index;
44+
index += 7;
5945
}
60-
return -1;
46+
return res;
6147
}
6248

6349
gsl::span<const uint8_t> UVarint::toBytes() const {
@@ -91,12 +77,24 @@ namespace libp2p::multi {
9177
}
9278

9379
size_t UVarint::calculateSize(gsl::span<const uint8_t> varint_bytes) {
94-
size_t s = 0;
95-
96-
while ((varint_bytes[s] & 0x80u) != 0) {
97-
s++;
80+
size_t size = 0;
81+
size_t shift = 0;
82+
constexpr size_t capacity = sizeof(uint64_t) * 8;
83+
bool last_byte_found = false;
84+
for (const auto &byte : varint_bytes) {
85+
++size;
86+
uint64_t slice = byte & 0x7f;
87+
if (shift >= capacity || slice << shift >> shift != slice) {
88+
size = 0;
89+
break;
90+
}
91+
if ((byte & 0x80) == 0) {
92+
last_byte_found = true;
93+
break;
94+
}
95+
shift += 7;
9896
}
99-
return s + 1;
97+
return last_byte_found ? size : 0;
10098
}
10199

102100
} // namespace libp2p::multi

test/libp2p/multi/utils/uvarint_test.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,43 @@ TEST(UVarint, CalculateSize) {
6161
uint8_t another_bytes[] = {0x71, 0xA3, 0x75, 0x43, 0xAA};
6262
ASSERT_EQ(UVarint::calculateSize(gsl::span(another_bytes, 5)), 1);
6363
}
64+
65+
/**
66+
* @given Sample integers from (N/2, N)
67+
* @when Encoding and decoding back sample integer
68+
* @then Encoding/decoding must be successful without losses of data
69+
*/
70+
TEST(UVarint, ReversibilitySuccess) {
71+
uint64_t max = std::numeric_limits<uint64_t>::max() / 2;
72+
for (uint64_t data = 2; data < max; data *= 2) {
73+
UVarint var{data};
74+
uint64_t decoded = var.toUInt64();
75+
ASSERT_EQ(data, decoded);
76+
}
77+
}
78+
79+
/**
80+
* @given Minimum and maximum possible values
81+
* @when Encoding and decoding back sample integer
82+
* @then Encoding/decoding must be successful without losses of data
83+
*/
84+
TEST(UVarint, EncodeLimitsAreCorrect) {
85+
uint64_t min = std::numeric_limits<uint64_t>::min();
86+
uint64_t max = std::numeric_limits<uint64_t>::max();
87+
UVarint var_min{min};
88+
UVarint var_max{max};
89+
ASSERT_EQ(min, var_min.toUInt64());
90+
ASSERT_EQ(max, var_max.toUInt64());
91+
}
92+
93+
/**
94+
* @given Encoded 2^64 value bytes (max size of uint64_t is 2^64-1)
95+
* @when Creating new UVarint from raw bytes
96+
* @then Decoding raw bytes must be failed
97+
*/
98+
TEST(UVaring, DecodeOverflowFailure) {
99+
std::vector<uint8_t> overflow_encoded_data{
100+
{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02}};
101+
auto var = UVarint::create(overflow_encoded_data);
102+
ASSERT_FALSE(var);
103+
}

test/libp2p/multi/uvarint_test.cpp

Lines changed: 0 additions & 62 deletions
This file was deleted.

0 commit comments

Comments
 (0)