Program Listing for File internal.hpp

Return to documentation for file (include/3rdparty/mcap/internal.hpp)

#pragma once

#include <cstring>

#include "types.hpp"

// Do not compile on systems with non-8-bit bytes
static_assert(std::numeric_limits<unsigned char>::digits == 8);

namespace mcap {

namespace internal {

constexpr uint64_t MinHeaderLength = /* magic bytes */ sizeof(Magic) +
                                     /* opcode */ 1 +
                                     /* record length */ 8 +
                                     /* profile length */ 4 +
                                     /* library length */ 4;
constexpr uint64_t FooterLength = /* opcode */ 1 +
                                  /* record length */ 8 +
                                  /* summary start */ 8 +
                                  /* summary offset start */ 8 +
                                  /* summary crc */ 4 +
                                  /* magic bytes */ sizeof(Magic);

inline std::string ToHex(uint8_t byte) {
    std::string result{2, '\0'};
    result[0] = "0123456789ABCDEF"[(uint8_t(byte) >> 4) & 0x0F];
    result[1] = "0123456789ABCDEF"[uint8_t(byte) & 0x0F];
    return result;
}
inline std::string ToHex(std::byte byte) {
    return ToHex(uint8_t(byte));
}

inline std::string to_string(const std::string& arg) {
    return arg;
}
inline std::string to_string(std::string_view arg) {
    return std::string(arg);
}
inline std::string to_string(const char* arg) {
    return std::string(arg);
}
template <typename... T>
[[nodiscard]] inline std::string StrCat(T&&... args) {
    using mcap::internal::to_string;
    using std::to_string;
    return ("" + ... + to_string(std::forward<T>(args)));
}

inline uint32_t KeyValueMapSize(const KeyValueMap& map) {
    size_t size = 0;
    for(const auto& [key, value] : map) {
        size += 4 + key.size() + 4 + value.size();
    }
    return (uint32_t)(size);
}

inline const std::string CompressionString(Compression compression) {
    switch(compression) {
        case Compression::None:
        default:
            return std::string{};
        case Compression::Lz4:
            return "lz4";
        case Compression::Zstd:
            return "zstd";
    }
}

inline uint16_t ParseUint16(const std::byte* data) {
    return uint16_t(data[0]) | (uint16_t(data[1]) << 8);
}

inline uint32_t ParseUint32(const std::byte* data) {
    return uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16) | (uint32_t(data[3]) << 24);
}

inline Status ParseUint32(const std::byte* data, uint64_t maxSize, uint32_t* output) {
    if(maxSize < 4) {
        const auto msg = StrCat("cannot read uint32 from ", maxSize, " bytes");
        return Status{StatusCode::InvalidRecord, msg};
    }
    *output = ParseUint32(data);
    return StatusCode::Success;
}

inline uint64_t ParseUint64(const std::byte* data) {
    return uint64_t(data[0]) | (uint64_t(data[1]) << 8) | (uint64_t(data[2]) << 16) | (uint64_t(data[3]) << 24) | (uint64_t(data[4]) << 32)
           | (uint64_t(data[5]) << 40) | (uint64_t(data[6]) << 48) | (uint64_t(data[7]) << 56);
}

inline Status ParseUint64(const std::byte* data, uint64_t maxSize, uint64_t* output) {
    if(maxSize < 8) {
        const auto msg = StrCat("cannot read uint64 from ", maxSize, " bytes");
        return Status{StatusCode::InvalidRecord, msg};
    }
    *output = ParseUint64(data);
    return StatusCode::Success;
}

inline Status ParseStringView(const std::byte* data, uint64_t maxSize, std::string_view* output) {
    uint32_t size = 0;
    if(auto status = ParseUint32(data, maxSize, &size); !status.ok()) {
        const auto msg = StrCat("cannot read string size: ", status.message);
        return Status{StatusCode::InvalidRecord, msg};
    }
    if(uint64_t(size) > (maxSize - 4)) {
        const auto msg = StrCat("string size ", size, " exceeds remaining bytes ", (maxSize - 4));
        return Status(StatusCode::InvalidRecord, msg);
    }
    *output = std::string_view(reinterpret_cast<const char*>(data + 4), size);
    return StatusCode::Success;
}

inline Status ParseString(const std::byte* data, uint64_t maxSize, std::string* output) {
    uint32_t size = 0;
    if(auto status = ParseUint32(data, maxSize, &size); !status.ok()) {
        return status;
    }
    if(uint64_t(size) > (maxSize - 4)) {
        const auto msg = StrCat("string size ", size, " exceeds remaining bytes ", (maxSize - 4));
        return Status(StatusCode::InvalidRecord, msg);
    }
    *output = std::string(reinterpret_cast<const char*>(data + 4), size);
    return StatusCode::Success;
}

inline Status ParseByteArray(const std::byte* data, uint64_t maxSize, ByteArray* output) {
    uint32_t size = 0;
    if(auto status = ParseUint32(data, maxSize, &size); !status.ok()) {
        return status;
    }
    if(uint64_t(size) > (maxSize - 4)) {
        const auto msg = StrCat("byte array size ", size, " exceeds remaining bytes ", (maxSize - 4));
        return Status(StatusCode::InvalidRecord, msg);
    }
    output->resize(size);
    std::memcpy(output->data(), data + 4, size);
    return StatusCode::Success;
}

inline Status ParseKeyValueMap(const std::byte* data, uint64_t maxSize, KeyValueMap* output) {
    uint32_t sizeInBytes = 0;
    if(auto status = ParseUint32(data, maxSize, &sizeInBytes); !status.ok()) {
        return status;
    }
    if(sizeInBytes > (maxSize - 4)) {
        const auto msg = StrCat("key-value map size ", sizeInBytes, " exceeds remaining bytes ", (maxSize - 4));
        return Status(StatusCode::InvalidRecord, msg);
    }

    // Account for the byte size prefix in sizeInBytes to make the bounds checking
    // below simpler
    sizeInBytes += 4;

    output->clear();
    uint64_t pos = 4;
    while(pos < sizeInBytes) {
        std::string_view key;
        if(auto status = ParseStringView(data + pos, sizeInBytes - pos, &key); !status.ok()) {
            const auto msg = StrCat("cannot read key-value map key at pos ", pos, ": ", status.message);
            return Status{StatusCode::InvalidRecord, msg};
        }
        pos += 4 + key.size();
        std::string_view value;
        if(auto status = ParseStringView(data + pos, sizeInBytes - pos, &value); !status.ok()) {
            const auto msg = StrCat("cannot read key-value map value for key \"", key, "\" at pos ", pos, ": ", status.message);
            return Status{StatusCode::InvalidRecord, msg};
        }
        pos += 4 + value.size();
        output->emplace(key, value);
    }
    return StatusCode::Success;
}

inline std::string MagicToHex(const std::byte* data) {
    return internal::ToHex(data[0]) + internal::ToHex(data[1]) + internal::ToHex(data[2]) + internal::ToHex(data[3]) + internal::ToHex(data[4])
           + internal::ToHex(data[5]) + internal::ToHex(data[6]) + internal::ToHex(data[7]);
}

}  // namespace internal

}  // namespace mcap