Skip to content

src/scale/scale_encoder_stream.cpp

Namespaces

Name
sgns
sgns::scale

Source code

#include "scale/scale_encoder_stream.hpp"

#include "base/outcome_throw.hpp"
#include "scale/scale_error.hpp"
#include "scale/types.hpp"

namespace sgns::scale {
  namespace {
    // must not use these functions outside encodeInteger
    inline void encodeFirstCategory(uint8_t value, ScaleEncoderStream &out) {
      // only values from [0, kMinUint16) can be put here
      out << static_cast<uint8_t>(value << 2u);
    }

    inline void encodeSecondCategory(uint16_t value, ScaleEncoderStream &out) {
      // only values from [kMinUint16, kMinUint32) can be put here
      auto v = value;
      v <<= 2u;  // v *= 4
      v += 1u;   // set 0b01 flag
      auto minor_byte = static_cast<uint8_t>(v & 0xFFu);
      v >>= 8u;
      auto major_byte = static_cast<uint8_t>(v & 0xFFu);

      out << minor_byte << major_byte;
    }

    inline void encodeThirdCategory(uint32_t value, ScaleEncoderStream &out) {
      // only values from [kMinUint32, kMinBigInteger) can be put here
      uint32_t v = (value << 2u) + 2;
      scale::detail::encodeInteger<uint32_t>(v, out);
    }

    // calculate number of bytes required
    size_t countBytes(CompactInteger v) {
      if (0 == v) {
        return 1;
      }

      size_t counter = 0;
      while (v > 0) {
        ++counter;
        v >>= 8;
      }

      return counter;
    }
    void encodeCompactInteger(const CompactInteger &value,
                              ScaleEncoderStream &out) {
      // cannot encode negative numbers
      // there is no description how to encode compact negative numbers
      if (value < 0) {
        base::raise(EncodeError::NEGATIVE_COMPACT_INTEGER);
      }

      if (value < compact::EncodingCategoryLimits::kMinUint16) {
        encodeFirstCategory(value.convert_to<uint8_t>(), out);
        return;
      }

      if (value < compact::EncodingCategoryLimits::kMinUint32) {
        encodeSecondCategory(value.convert_to<uint16_t>(), out);
        return;
      }

      if (value < compact::EncodingCategoryLimits::kMinBigInteger) {
        encodeThirdCategory(value.convert_to<uint32_t>(), out);
        return;
      }

      // number of bytes required to represent value
      size_t bigIntLength = countBytes(value);

      // number of bytes to scale-encode value
      // 1 byte is reserved for header
      size_t requiredLength = 1 + bigIntLength;

      if (bigIntLength > 67) {
        base::raise(EncodeError::COMPACT_INTEGER_TOO_BIG);
      }

      ByteArray result;
      result.reserve(requiredLength);

      /* The value stored in 6 major bits of header is used
       * to encode number of bytes for storing big integer.
       * Value formed by 6 bits varies from 0 to 63 == 2^6 - 1,
       * However big integer byte count starts from 4,
       * so to store this number we should decrease this value by 4.
       * And the range of bytes number for storing big integer
       * becomes 4 .. 67. To form resulting header we need to move
       * those bits representing bytes count to the left by 2 positions
       * by means of multiplying by 4.
       * Minor 2 bits store encoding option, in our case it is 0b11 == 3
       * We just add 3 to the result of operations above
       */
      uint8_t header = (bigIntLength - 4) * 4 + 3;

      result.push_back(header);

      CompactInteger v{value};
      for (size_t i = 0; i < bigIntLength; ++i) {
        result.push_back(static_cast<uint8_t>(
            v & 0xFF));  // push back least significant byte
        v >>= 8;
      }

      for (const uint8_t c : result) {
        out << c;
      }
    }
  }  // namespace

  ByteArray ScaleEncoderStream::data() const {
    ByteArray buffer(stream_.size(), 0u);
    for (auto &&[it, dest] = std::pair(stream_.begin(), buffer.begin());
         it != stream_.end();
         ++it, ++dest) {
      *dest = *it;
    }
    return buffer;
  }

  ScaleEncoderStream &ScaleEncoderStream::putByte(uint8_t v) {
    stream_.push_back(v);
    return *this;
  }

  ScaleEncoderStream &ScaleEncoderStream::operator<<(const CompactInteger &v) {
    encodeCompactInteger(v, *this);
    return *this;
  }

  ScaleEncoderStream &ScaleEncoderStream::encodeOptionalBool(
      const boost::optional<bool> &v) {
    auto result = OptionalBool::TRUE_;

    if (!v.has_value()) {
      result = OptionalBool::NONE_;
    } else if (!*v) {
      result = OptionalBool::FALSE_;
    }

    return putByte(static_cast<uint8_t>(result));
  }

}  // namespace sgns::scale

Updated on 2026-03-04 at 13:10:45 -0800