RyanHub – file viewer
filename: src/utils/io/ByteBuffer.h
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
 *
 */

#ifndef STRATOS_BUFFER_H
#define STRATOS_BUFFER_H

#include <utility>
#include <vector>
#include <cstdint>
#include <stdexcept>

typedef unsigned char byte;
typedef std::vector<unsigned char> ByteVec;

namespace stratos {

class BufferUnderflowException : public std::logic_error {
  public:
    explicit BufferUnderflowException(const std::string& message)
        : std::logic_error(message) {}
    explicit BufferUnderflowException(const char* message)
        : std::logic_error(message) {}
};

class BufferOverflowException : public std::logic_error {
  public:
    explicit BufferOverflowException(const std::string& message)
        : std::logic_error(message) {}
    explicit BufferOverflowException(const char* message)
        : std::logic_error(message) {}
};

class ByteBuffer {
  public:
    ByteBuffer() : offset(0) {}
    explicit ByteBuffer(const ByteVec& data) : offset(0), buffer(data) {}
    explicit ByteBuffer(ByteVec&& data) : offset(0), buffer(std::move(data)) {}
    ByteBuffer(const ByteVec& data, const size_t offset) : offset(offset), buffer(data) {
        if (offset > data.size()) throw BufferOverflowException("Offset exceeds buffer size");
    }
    ByteBuffer(ByteVec&& data, const size_t offset) : offset(offset), buffer(std::move(data)) {
        if (offset > data.size()) throw BufferOverflowException("Offset exceeds buffer size");
    }
    ByteBuffer(const ByteBuffer&)            = delete;
    ByteBuffer& operator=(const ByteBuffer&) = delete;
    ByteBuffer(ByteBuffer&&)                 = default;
    ByteBuffer& operator=(ByteBuffer&&)      = default;
    virtual ~ByteBuffer()                    = default;

    bool                  readBoolean();
    int8_t                readByte();
    uint8_t               readUnsignedByte();
    short                 readShort();
    uint16_t              readUnsignedShort();
    int                   readInt();
    int                   readInt24();
    int64_t               readLong();
    float                 readFloat();
    double                readDouble();
    std::string           readString(int maxChars);
    int                   readVarInt();
    int64_t               readVarLong();
    std::vector<uint64_t> readBitSet();
    std::vector<bool>     readFixedBitSet(size_t length);
    ByteVec               readByteArray(size_t length);
    void                  readFixedLongArray(std::vector<int64_t>& longs);

    void writeBoolean(bool value);
    void writeByte(int8_t value);
    void writeUnsignedByte(uint8_t value);
    void writeShort(short value);
    void writeUnsignedShort(uint16_t value);
    void writeInt(int value);
    void writeLong(int64_t value);
    void writeFloat(float value);
    void writeDouble(double value);
    void writeString(const std::string& value, int maxChars);
    void writeVarInt(int value);
    void writeVarLong(int64_t value);
    void writeBitSet(const std::vector<uint64_t>& longs);
    void writeFixedBitSet(const std::vector<bool>& bits, size_t length);
    void writeByteArray(const ByteVec& values);
    void writeFixedLongArray(const std::vector<int64_t>& longs);

    /**
     * @brief Clears the buffer & resets the offset.
     */
    void clear() {
        offset = 0;
        buffer.clear();
    }

    void                         resize(const size_t size) { buffer.resize(size); }
    void                         reserve(const size_t size) { buffer.reserve(size); }
    void                         append(const ByteVec& data) { buffer.insert(buffer.end(), data.begin(), data.end()); }
    void                         append(const ByteVec&& data) { buffer.insert(buffer.end(), std::make_move_iterator(data.begin()), std::make_move_iterator(data.end())); }
    auto                         begin() { return buffer.begin(); }
    auto                         end() { return buffer.end(); }
    ByteVec&                     data() { return buffer; }
    [[nodiscard]] const ByteVec& data() const { return buffer; }
    [[nodiscard]] auto           cbegin() const { return buffer.cbegin(); }
    [[nodiscard]] auto           cend() const { return buffer.cend(); }
    [[nodiscard]] size_t         size() const { return buffer.size(); }
    [[nodiscard]] bool           empty() const { return buffer.empty(); }
    [[nodiscard]] size_t         getOffset() const { return offset; }
    void                         setOffset(size_t newOffset);

    uint8_t  operator[](size_t index) const;
    uint8_t& operator[](size_t index);

  protected:
    size_t            offset;
    std::vector<byte> buffer;

    /**
     * @brief Checks if the buffer is readable.
     */
    void checkRead(const uint64_t size) const {
        if (offset + size > buffer.size()) {
            throw BufferUnderflowException("Buffer underflow: not enough data to read");
        }
    }
};

int  getEncodedSizeInBytes(int value);
bool getEncodedSizeInBytes(int64_t value);

} // namespace stratos

#endif // STRATOS_BUFFER_H