1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #include "pw_varint/varint.h"
16
17 #include <algorithm>
18 #include <cstddef>
19
20 namespace pw {
21 namespace varint {
22 namespace {
23
ZeroTerminated(pw_varint_Format format)24 inline bool ZeroTerminated(pw_varint_Format format) {
25 return (static_cast<unsigned>(format) & 0b10) == 0;
26 }
27
LeastSignificant(pw_varint_Format format)28 inline bool LeastSignificant(pw_varint_Format format) {
29 return (static_cast<unsigned>(format) & 0b01) == 0;
30 }
31
32 } // namespace
33
pw_varint_EncodeCustom(uint64_t integer,void * output,size_t output_size,pw_varint_Format format)34 extern "C" size_t pw_varint_EncodeCustom(uint64_t integer,
35 void* output,
36 size_t output_size,
37 pw_varint_Format format) {
38 size_t written = 0;
39 std::byte* buffer = static_cast<std::byte*>(output);
40
41 int value_shift = LeastSignificant(format) ? 1 : 0;
42 int term_shift = value_shift == 1 ? 0 : 7;
43
44 std::byte cont, term;
45 if (ZeroTerminated(format)) {
46 cont = std::byte(0x01) << term_shift;
47 term = std::byte(0x00) << term_shift;
48 } else {
49 cont = std::byte(0x00) << term_shift;
50 term = std::byte(0x01) << term_shift;
51 }
52
53 do {
54 if (written >= output_size) {
55 return 0;
56 }
57
58 bool last_byte = (integer >> 7) == 0u;
59
60 // Grab 7 bits and set the eighth according to the continuation bit.
61 std::byte value = (static_cast<std::byte>(integer) & std::byte(0x7f))
62 << value_shift;
63
64 if (last_byte) {
65 value |= term;
66 } else {
67 value |= cont;
68 }
69
70 buffer[written++] = value;
71 integer >>= 7;
72 } while (integer != 0u);
73
74 return written;
75 }
76
pw_varint_DecodeCustom(const void * input,size_t input_size,uint64_t * output,pw_varint_Format format)77 extern "C" size_t pw_varint_DecodeCustom(const void* input,
78 size_t input_size,
79 uint64_t* output,
80 pw_varint_Format format) {
81 uint64_t decoded_value = 0;
82 uint_fast8_t count = 0;
83 const std::byte* buffer = static_cast<const std::byte*>(input);
84
85 // The largest 64-bit ints require 10 B.
86 const size_t max_count = std::min(kMaxVarint64SizeBytes, input_size);
87
88 std::byte mask;
89 uint32_t shift;
90 if (LeastSignificant(format)) {
91 mask = std::byte(0xfe);
92 shift = 1;
93 } else {
94 mask = std::byte(0x7f);
95 shift = 0;
96 }
97
98 // Determines whether a byte is the last byte of a varint.
99 auto is_last_byte = [&](std::byte byte) {
100 if (ZeroTerminated(format)) {
101 return (byte & ~mask) == std::byte(0);
102 }
103 return (byte & ~mask) != std::byte(0);
104 };
105
106 while (true) {
107 if (count >= max_count) {
108 return 0;
109 }
110
111 // Add the bottom seven bits of the next byte to the result.
112 decoded_value |= static_cast<uint64_t>((buffer[count] & mask) >> shift)
113 << (7 * count);
114
115 // Stop decoding if the end is reached.
116 if (is_last_byte(buffer[count++])) {
117 break;
118 }
119 }
120
121 *output = decoded_value;
122 return count;
123 }
124
pw_varint_EncodedSizeBytes(uint64_t integer)125 extern "C" size_t pw_varint_EncodedSizeBytes(uint64_t integer) {
126 return EncodedSize(integer);
127 }
128
129 } // namespace varint
130 } // namespace pw
131