Program Listing for File message_buffer.hpp

Return to documentation for file (sockets/include/sockets/detail/message_buffer.hpp)

#pragma once

#include "byte_order.hpp"
#include "utils.hpp"
#include <algorithm>
#include <array>
#include <concepts>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <optional>
#include <span>
#include <stdexcept>
#include <vector>

namespace c2k {
    class MessageBuffer final {
    private:
        std::vector<std::byte> m_data{};

    public:
        MessageBuffer() = default;

        explicit MessageBuffer(std::vector<std::byte> data) : m_data{ std::move(data) } { }

        [[nodiscard]] std::size_t size() const {
            return m_data.size();
        }

        [[nodiscard]] std::vector<std::byte> const& data() const& {
            return m_data;
        }

        [[nodiscard]] std::vector<std::byte> data() && {
            return std::move(m_data);
        }

        template<std::integral T>
        friend MessageBuffer& operator<<(MessageBuffer& message_buffer, T const value) {
            auto buffer = std::array<std::byte, sizeof(value)>{};
            auto const network_value = to_network_byte_order(value);
            std::copy_n(reinterpret_cast<std::byte const*>(&network_value), sizeof(network_value), buffer.data());
            message_buffer.m_data.insert(message_buffer.m_data.end(), buffer.cbegin(), buffer.cend());
            return message_buffer;
        }

        template<std::integral T>
        friend MessageBuffer&& operator<<(MessageBuffer&& message_buffer, T const value) {
            message_buffer << value;
            return std::move(message_buffer);
        }

        template<std::integral... Ts>
        [[nodiscard]] auto try_extract() {
            static_assert(sizeof...(Ts) > 0, "at least one type argument must be specified");
            if constexpr (sizeof...(Ts) == 1) {
                if (size() < detail::summed_sizeof<Ts...>()) {
                    return std::optional<Ts...>{ std::nullopt };
                }
                auto result = (Ts{}, ...);
                *this >> result;
                return std::optional<Ts...>{ result };
            } else {
                if (size() < detail::summed_sizeof<Ts...>()) {
                    return std::optional<std::tuple<Ts...>>{};
                }
                return std::optional{ std::tuple<Ts...>{ [&] {
                    auto value = Ts{};
                    *this >> value;
                    return value;
                }()... } };
            }
        }

        template<std::integral Integral>
        friend MessageBuffer& operator>>(MessageBuffer& message_buffer, Integral& target) {
            if (message_buffer.m_data.size() < sizeof(target)) {
                throw std::runtime_error{ "not enough data to extract value" };
            }
            auto buffer = Integral{};
            std::copy_n(message_buffer.m_data.cbegin(), sizeof(target), reinterpret_cast<std::byte*>(&buffer));
            target = from_network_byte_order(buffer);
            message_buffer.m_data.erase(
                    message_buffer.m_data.cbegin(),
                    message_buffer.m_data.cbegin() + sizeof(target)
            );
            return message_buffer;
        }

        friend MessageBuffer& operator<<(MessageBuffer& message_buffer, std::span<std::byte const> const data) {
            message_buffer.m_data.insert(message_buffer.m_data.end(), data.begin(), data.end());
            return message_buffer;
        }
    };
} // namespace c2k