RyanHub – file viewer
filename: src/utils/io/ByteBuffer.cpp
branch: feature/world
back to repo
/*
 *
 *               _____  _                 _
 *              /  ___|| |               | |
 *              \ `--. | |_  _ __   __ _ | |_   ___   ___
 *               `--. \| __|| '__| / _` || __| / _ \ / __|
 *              /\__/ /| |_ | |   | (_| || |_ | (_) |\__ \
 *              \____/  \__||_|    \__,_| \__| \___/ |___/
 *
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Copyright (C) 2025 Armen Deroian
 *
 */

#include "ByteBuffer.h"

#include <cstring>

int constexpr SEGMENT_BITS = 0x7F;
int constexpr CONTINUE_BIT = 0x80;

int countCodeUnits(const std::string& utf8) {
    int    count = 0;
    size_t i     = 0;
    while (i < utf8.size()) {
        if (const unsigned char c = utf8[i]; c < 0x80) {
            ++i;
            ++count;
        } else if (c >> 5 == 0x6) {
            i += 2;
            ++count;
        } else if (c >> 4 == 0xE) {
            i += 3;
            ++count;
        } else if (c >> 3 == 0x1E) {
            i += 4;
            count += 2;
        } else {
            throw std::runtime_error("Invalid UTF-8 sequence");
        }
    }
    return count;
}

bool stratos::ByteBuffer::readBoolean() {
    checkRead(sizeof(bool));
    return this->buffer[this->offset++] != 0;
}

int8_t stratos::ByteBuffer::readByte() {
    checkRead(sizeof(int8_t));
    return static_cast<int8_t>(this->buffer[offset++]);
}

uint8_t stratos::ByteBuffer::readUnsignedByte() {
    checkRead(sizeof(uint8_t));
    return static_cast<uint8_t>(this->buffer[offset++]);
}

short stratos::ByteBuffer::readShort() {
    checkRead(sizeof(short));
    const auto high = static_cast<int16_t>(buffer[offset++]);
    const auto low  = static_cast<int16_t>(buffer[offset++]);
    return static_cast<short>(high << 8 | low & 0xFF);
}

uint16_t stratos::ByteBuffer::readUnsignedShort() {
    checkRead(sizeof(uint16_t));
    const auto high = static_cast<uint16_t>(buffer[offset++]);
    const auto low  = static_cast<uint16_t>(buffer[offset++]);
    return static_cast<uint16_t>(high << 8 | low & 0xFF);
}

int stratos::ByteBuffer::readInt() {
    checkRead(sizeof(int));
    const auto b1 = static_cast<int32_t>(buffer[offset++]);
    const auto b2 = static_cast<int32_t>(buffer[offset++]);
    const auto b3 = static_cast<int32_t>(buffer[offset++]);
    const auto b4 = static_cast<int32_t>(buffer[offset++]);
    return b1 << 24 | b2 << 16 | b3 << 8 | b4 & 0xFF;
}
int stratos::ByteBuffer::readInt24() {
    checkRead(3);
    const auto b1 = static_cast<int32_t>(buffer[offset++]);
    const auto b2 = static_cast<int32_t>(buffer[offset++]);
    const auto b3 = static_cast<int32_t>(buffer[offset++]);
    return b1 << 16 | b2 << 8 | b3 & 0xFF;
}

int64_t stratos::ByteBuffer::readLong() {
    checkRead(sizeof(int64_t));
    int64_t result = 0;
    for (int i = 0; i < 8; ++i) {
        result |= static_cast<int64_t>(buffer[offset++]) << ((7 - i) * 8);
    }
    return result;
}

float stratos::ByteBuffer::readFloat() {
    checkRead(sizeof(float));
    const uint32_t bits =
        static_cast<uint32_t>(buffer[offset]) << 24 |
        static_cast<uint32_t>(buffer[offset + 1]) << 16 |
        static_cast<uint32_t>(buffer[offset + 2]) << 8 |
        static_cast<uint32_t>(buffer[offset + 3]);
    offset += 4;
    float value;
    memcpy(&value, &bits, sizeof(float));
    return value;
}

double stratos::ByteBuffer::readDouble() {
    checkRead(sizeof(double));
    uint64_t bits = 0;
    for (int i = 0; i < 8; ++i) {
        bits |= static_cast<uint64_t>(buffer[offset++]) << ((7 - i) * 8);
    }
    double value;
    memcpy(&value, &bits, sizeof(double));
    return value;
}

std::string stratos::ByteBuffer::readString(int maxChars) {
    const uint32_t length = readVarInt();
    if (length > maxChars * 3) {
        throw BufferOverflowException("String length exceeds maximum allowed characters.");
    }
    if (offset + length > buffer.size()) {
        throw BufferUnderflowException("Not enough data to read the string.");
    }

    std::string str(buffer.begin() + offset, buffer.begin() + offset + length);
    offset += length;
    if (const int utf16Units = countCodeUnits(str); utf16Units > maxChars) {
        throw BufferOverflowException("String length exceeds maximum allowed UTF-16 code units.");
    }

    return str;
}

int stratos::ByteBuffer::readVarInt() {
    int value    = 0;
    int position = 0;
    while (true) {
        const uint8_t currentByte = readByte();
        value |= (currentByte & SEGMENT_BITS) << position;
        if ((currentByte & CONTINUE_BIT) == 0) break;
        position += 7;
        if (position >= 32) {
            throw BufferOverflowException("VarInt is too big to fit in an int.");
        }
    }
    return value;
}

int64_t stratos::ByteBuffer::readVarLong() {
    int64_t value    = 0;
    int     position = 0;
    while (true) {
        const uint8_t currentByte = readByte();
        value |= static_cast<int64_t>(currentByte & SEGMENT_BITS) << position;
        if ((currentByte & CONTINUE_BIT) == 0) break;
        position += 7;
        if (position >= 64) {
            throw BufferOverflowException("VarLong is too big to fit in an int64_t.");
        }
    }
    return value;
}

std::vector<uint64_t> stratos::ByteBuffer::readBitSet() {
    const uint32_t        length = readVarInt();
    std::vector<uint64_t> longs(length);
    for (uint32_t i = 0; i < length; ++i) {
        if (offset + 7 >= buffer.size()) {
            throw BufferUnderflowException("Not enough data to read a long from BitSet.");
        }
        uint64_t value = 0;
        for (int j = 0; j < 8; ++j) value |= static_cast<uint64_t>(buffer[offset++]) << ((7 - j) * 8);
        longs[i] = value;
    }
    return longs;
}

std::vector<bool> stratos::ByteBuffer::readFixedBitSet(size_t length) {
    const size_t byteLength = (length + 7) / 8;
    if (offset + byteLength > buffer.size()) {
        throw BufferUnderflowException("Not enough data to read FixedBitSet.");
    }
    std::vector<bool> bits(length);
    for (size_t i = 0; i < length; ++i) {
        const uint8_t byte = buffer[offset + i / 8];
        bits[i]            = (byte >> (i % 8)) & 1;
    }
    offset += byteLength;
    return bits;
}

ByteVec stratos::ByteBuffer::readByteArray(size_t length) {
    ByteVec result;
    result.reserve(length);
    for (size_t i = 0; i < length; ++i) {
        result.push_back(readByte());
    }
    return result;
}
void stratos::ByteBuffer::readFixedLongArray(std::vector<int64_t>& longs) {
    for (size_t i = 0; i < longs.size(); ++i)
        longs[i] = readLong();
}
void stratos::ByteBuffer::writeBoolean(const bool value) {
    buffer.push_back(static_cast<unsigned char>(value ? 1 : 0));
}
void stratos::ByteBuffer::writeByte(const int8_t value) {
    buffer.push_back(static_cast<unsigned char>(value));
}
void stratos::ByteBuffer::writeUnsignedByte(const uint8_t value) {
    buffer.push_back(value);
}
void stratos::ByteBuffer::writeShort(const short value) {
    buffer.push_back(static_cast<unsigned char>(value >> 8 & 0xFF));
    buffer.push_back(static_cast<unsigned char>(value & 0xFF));
}
void stratos::ByteBuffer::writeUnsignedShort(const uint16_t value) {
    buffer.push_back(static_cast<unsigned char>(value >> 8 & 0xFF));
    buffer.push_back(static_cast<unsigned char>(value & 0xFF));
}
void stratos::ByteBuffer::writeInt(const int value) {
    buffer.push_back(static_cast<unsigned char>(value >> 24 & 0xFF));
    buffer.push_back(static_cast<unsigned char>(value >> 16 & 0xFF));
    buffer.push_back(static_cast<unsigned char>(value >> 8 & 0xFF));
    buffer.push_back(static_cast<unsigned char>(value & 0xFF));
}
void stratos::ByteBuffer::writeLong(const int64_t value) {
    for (int i = 7; i >= 0; --i) buffer.push_back(static_cast<unsigned char>(value >> (i * 8) & 0xFF));
}
void stratos::ByteBuffer::writeFloat(const float value) {
    uint32_t bits;
    std::memcpy(&bits, &value, sizeof(float));
    buffer.push_back(static_cast<unsigned char>(bits >> 24 & 0xFF));
    buffer.push_back(static_cast<unsigned char>(bits >> 16 & 0xFF));
    buffer.push_back(static_cast<unsigned char>(bits >> 8 & 0xFF));
    buffer.push_back(static_cast<unsigned char>(bits & 0xFF));
}
void stratos::ByteBuffer::writeDouble(const double value) {
    uint64_t bits;
    std::memcpy(&bits, &value, sizeof(double));
    for (int i = 7; i >= 0; --i) buffer.push_back(static_cast<unsigned char>(bits >> (i * 8) & 0xFF));
}
void stratos::ByteBuffer::writeString(const std::string& value, const int maxChars) {
    if (const int utf16Units = countCodeUnits(value); utf16Units > maxChars) throw BufferOverflowException("String: exceeds character limit");
    if (value.size() > maxChars * 3) throw BufferOverflowException("String: exceeds UTF-8 byte size limit");
    const auto length = static_cast<int32_t>(value.size());
    writeVarInt(length);
    buffer.insert(buffer.end(), value.begin(), value.end());
}
void stratos::ByteBuffer::writeVarInt(int value) {
    while (true) {
        if ((value & ~SEGMENT_BITS) == 0) {
            writeByte(static_cast<uint8_t>(value));
            return;
        }
        writeByte(static_cast<uint8_t>((value & SEGMENT_BITS) | CONTINUE_BIT));
        value = static_cast<uint32_t>(value) >> 7;
    }
}
void stratos::ByteBuffer::writeVarLong(int64_t value) {
    while (true) {
        if ((value & ~static_cast<int64_t>(SEGMENT_BITS)) == 0) {
            writeByte(static_cast<uint8_t>(value));
            return;
        }
        writeByte(static_cast<uint8_t>((value & SEGMENT_BITS) | CONTINUE_BIT));
        value = static_cast<uint64_t>(value) >> 7;
    }
}
void stratos::ByteBuffer::writeBitSet(const std::vector<uint64_t>& longs) {
    const int length = static_cast<int>(longs.size());
    writeVarInt(length);
    for (const uint64_t value : longs)
        for (int i = 7; i >= 0; --i) buffer.push_back(value >> (i * 8) & 0xFF);
}
void stratos::ByteBuffer::writeFixedBitSet(const std::vector<bool>& bits, const size_t length) {
    const size_t byteLength = (length + 7) / 8;
    for (size_t byteIndex = 0; byteIndex < byteLength; ++byteIndex) {
        uint8_t byte = 0;
        for (int bit = 0; bit < 8; ++bit)
            if (const size_t bitIndex = byteIndex * 8 + bit; bitIndex < bits.size() && bits[bitIndex]) byte |= 1 << bit;
        buffer.push_back(byte);
    }
}
void stratos::ByteBuffer::writeByteArray(const ByteVec& values) {
    for (const auto& value : values) buffer.push_back(value);
}
void stratos::ByteBuffer::writeFixedLongArray(const std::vector<int64_t>& longs) {
    for (const auto& value : longs)
        writeLong(value);
}
void    stratos::ByteBuffer::setOffset(const size_t newOffset) {
    if (newOffset > buffer.size()) {
        throw BufferOverflowException("Offset exceeds buffer size");
    }
    offset = newOffset;
}
uint8_t stratos::ByteBuffer::operator[](const size_t index) const {
    if (index >= buffer.size()) {
        throw BufferUnderflowException("Index out of bounds for buffer access");
    }
    return buffer[index];
}
uint8_t& stratos::ByteBuffer::operator[](const size_t index) {
    if (index >= buffer.size()) {
        throw BufferUnderflowException("Index out of bounds for buffer access");
    }
    return buffer[index];
}
int stratos::getEncodedSizeInBytes(const int value) {
    for (int i = 0; i < 5; ++i) {
        if ((value & -1 << i * 7) != 0) continue;
        return i;
    }
    return 5;
}
bool stratos::getEncodedSizeInBytes(const int64_t value) {
    for (int i = 0; i < 10; ++i) {
        if ((value & -1LL << i * 7) != 0) continue;
        return i;
    }
    return 10;
}