| // Copyright 2020 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_varint/varint.h" |
| |
| #include <algorithm> |
| |
| namespace pw { |
| namespace varint { |
| namespace { |
| |
| inline bool ZeroTerminated(pw_varint_Format format) { |
| return (static_cast<unsigned>(format) & 0b10) == 0; |
| } |
| |
| inline bool LeastSignificant(pw_varint_Format format) { |
| return (static_cast<unsigned>(format) & 0b01) == 0; |
| } |
| |
| } // namespace |
| |
| extern "C" size_t pw_varint_EncodeCustom(uint64_t input, |
| void* output, |
| size_t output_size, |
| pw_varint_Format format) { |
| size_t written = 0; |
| std::byte* buffer = static_cast<std::byte*>(output); |
| |
| int value_shift = LeastSignificant(format) ? 1 : 0; |
| int term_shift = value_shift == 1 ? 0 : 7; |
| |
| std::byte cont, term; |
| if (ZeroTerminated(format)) { |
| cont = std::byte(0x01) << term_shift; |
| term = std::byte(0x00) << term_shift; |
| } else { |
| cont = std::byte(0x00) << term_shift; |
| term = std::byte(0x01) << term_shift; |
| } |
| |
| do { |
| if (written >= output_size) { |
| return 0; |
| } |
| |
| bool last_byte = (input >> 7) == 0u; |
| |
| // Grab 7 bits and set the eighth according to the continuation bit. |
| std::byte value = (static_cast<std::byte>(input) & std::byte(0x7f)) |
| << value_shift; |
| |
| if (last_byte) { |
| value |= term; |
| } else { |
| value |= cont; |
| } |
| |
| buffer[written++] = value; |
| input >>= 7; |
| } while (input != 0u); |
| |
| return written; |
| } |
| |
| extern "C" size_t pw_varint_DecodeCustom(const void* input, |
| size_t input_size, |
| uint64_t* output, |
| pw_varint_Format format) { |
| uint64_t decoded_value = 0; |
| uint_fast8_t count = 0; |
| const std::byte* buffer = static_cast<const std::byte*>(input); |
| |
| // The largest 64-bit ints require 10 B. |
| const size_t max_count = std::min(kMaxVarint64SizeBytes, input_size); |
| |
| std::byte mask; |
| uint32_t shift; |
| if (LeastSignificant(format)) { |
| mask = std::byte(0xfe); |
| shift = 1; |
| } else { |
| mask = std::byte(0x7f); |
| shift = 0; |
| } |
| |
| // Determines whether a byte is the last byte of a varint. |
| auto is_last_byte = [&](std::byte byte) { |
| if (ZeroTerminated(format)) { |
| return (byte & ~mask) == std::byte(0); |
| } |
| return (byte & ~mask) != std::byte(0); |
| }; |
| |
| while (true) { |
| if (count >= max_count) { |
| return 0; |
| } |
| |
| // Add the bottom seven bits of the next byte to the result. |
| decoded_value |= static_cast<uint64_t>((buffer[count] & mask) >> shift) |
| << (7 * count); |
| |
| // Stop decoding if the end is reached. |
| if (is_last_byte(buffer[count++])) { |
| break; |
| } |
| } |
| |
| *output = decoded_value; |
| return count; |
| } |
| |
| // TODO(frolv): Remove this deprecated alias. |
| extern "C" size_t pw_VarintEncode(uint64_t integer, |
| void* output, |
| size_t output_size) { |
| return pw_varint_Encode(integer, output, output_size); |
| } |
| |
| extern "C" size_t pw_varint_ZigZagEncode(int64_t integer, |
| void* output, |
| size_t output_size) { |
| return pw_varint_Encode(ZigZagEncode(integer), output, output_size); |
| } |
| |
| // TODO(frolv): Remove this deprecated alias. |
| extern "C" size_t pw_VarintDecode(const void* input, |
| size_t input_size, |
| uint64_t* output) { |
| return pw_varint_Decode(input, input_size, output); |
| } |
| |
| extern "C" size_t pw_varint_ZigZagDecode(const void* input, |
| size_t input_size, |
| int64_t* output) { |
| uint64_t value = 0; |
| size_t bytes = pw_varint_Decode(input, input_size, &value); |
| *output = ZigZagDecode(value); |
| return bytes; |
| } |
| |
| extern "C" size_t pw_varint_EncodedSize(uint64_t integer) { |
| return EncodedSize(integer); |
| } |
| |
| extern "C" size_t pw_varint_ZigZagEncodedSize(int64_t integer) { |
| return ZigZagEncodedSize(integer); |
| } |
| |
| } // namespace varint |
| } // namespace pw |