File xcdr1_codec.hpp

File List > astutedds > cdr > xcdr1_codec.hpp

Go to the documentation of this file


#ifndef ASTUTEDDS_XCDR1_CODEC_HPP
#define ASTUTEDDS_XCDR1_CODEC_HPP

#include "cdr_types.hpp"
#include <cstring>
#include <iostream>

namespace astutedds::cdr {

class XCDR1Encoder {
public:
    XCDR1Encoder(std::vector<uint8_t>& buffer, bool big_endian = false)
        : buffer_(buffer), swap_bytes_((!big_endian) != is_little_endian()) {
        // Write encapsulation header
        EncapsulationHeader header;
        header.encoding = big_endian ? EncodingKind::CDR_BE : EncodingKind::CDR_LE;
        header.options = 0;

        write_encapsulation_header(header);
    }

    // Encode primitive types
    void encode_uint8(uint8_t value) { write_aligned(&value, sizeof(value), 1); }
    void encode_int8(int8_t value) { write_aligned(&value, sizeof(value), 1); }
    void encode_uint16(uint16_t value) { 
        if (swap_bytes_) value = swap_endian(value);
        write_aligned(&value, sizeof(value), 2);
    }
    void encode_int16(int16_t value) { 
        if (swap_bytes_) value = swap_endian(value);
        write_aligned(&value, sizeof(value), 2);
    }
    void encode_uint32(uint32_t value) { 
        if (swap_bytes_) value = swap_endian(value);
        write_aligned(&value, sizeof(value), 4);
    }
    void encode_int32(int32_t value) { 
        if (swap_bytes_) value = swap_endian(value);
        write_aligned(&value, sizeof(value), 4);
    }
    void encode_uint64(uint64_t value) { 
        if (swap_bytes_) value = swap_endian(value);
        write_aligned(&value, sizeof(value), 8);
    }
    void encode_int64(int64_t value) { 
        if (swap_bytes_) value = swap_endian(value);
        write_aligned(&value, sizeof(value), 8);
    }
    void encode_float(float value) {
        uint32_t bits;
        std::memcpy(&bits, &value, sizeof(value));
        if (swap_bytes_) bits = swap_endian(bits);
        write_aligned(&bits, sizeof(bits), 4);
    }
    void encode_double(double value) {
        uint64_t bits;
        std::memcpy(&bits, &value, sizeof(value));
        if (swap_bytes_) bits = swap_endian(bits);
        write_aligned(&bits, sizeof(bits), 8);
    }
    void encode_bool(bool value) {
        uint8_t byte = value ? 1 : 0;
        write_aligned(&byte, sizeof(byte), 1);
    }

    // Encode string (null-terminated)
    void encode_string(const std::string& str) {
        uint32_t length = static_cast<uint32_t>(str.size() + 1); // Include null
        encode_uint32(length);
        buffer_.insert(buffer_.end(), str.begin(), str.end());
        buffer_.push_back(0); // Null terminator

        // Pad to 4-byte alignment
        size_t current_pos = buffer_.size();
        size_t aligned_pos = align_offset(current_pos, 4);
        if (aligned_pos > current_pos) {
            buffer_.resize(aligned_pos, 0);
        }
    }

    // Encode sequence length
    void encode_sequence_length(uint32_t length) {
        encode_uint32(length);
    }

    size_t size() const { return buffer_.size(); }

private:
    void write_encapsulation_header(const EncapsulationHeader& header) {
        // Per CDR encapsulation rules, kind/options are always written in network byte order (big-endian),
        // independent of the payload endianness.
        const uint16_t encoding_value = static_cast<uint16_t>(header.encoding);
        const uint16_t options_value = header.options;

        buffer_.resize(4);
        buffer_[0] = static_cast<uint8_t>((encoding_value >> 8) & 0xFF);
        buffer_[1] = static_cast<uint8_t>(encoding_value & 0xFF);
        buffer_[2] = static_cast<uint8_t>((options_value >> 8) & 0xFF);
        buffer_[3] = static_cast<uint8_t>(options_value & 0xFF);
    }

    void write_aligned(const void* data, size_t size, size_t alignment) {
        size_t current_pos = buffer_.size();
        size_t aligned_pos = align_offset(current_pos, alignment);

        // Add padding
        if (aligned_pos > current_pos) {
            buffer_.resize(aligned_pos, 0);
        }

        // Write data
        size_t old_size = buffer_.size();
        buffer_.resize(old_size + size);
        std::memcpy(&buffer_[old_size], data, size);
    }

    std::vector<uint8_t>& buffer_;
    bool swap_bytes_;
};

class XCDR1Decoder {
public:
    XCDR1Decoder(const std::vector<uint8_t>& buffer)
        : buffer_(buffer), position_(0) {
        // Read and validate encapsulation header (minimum 4 bytes required)
        if (buffer_.size() < 4) {
            throw std::runtime_error("Buffer too small for CDR encapsulation header (minimum 4 bytes required)");
        }

        // Encapsulation encoding is always in big-endian (network byte order)
        uint16_t encoding_value = (buffer_[0] << 8) | buffer_[1];

        EncodingKind encoding = static_cast<EncodingKind>(encoding_value);
        bool data_is_big_endian = (encoding == EncodingKind::CDR_BE || 
                                    encoding == EncodingKind::PL_CDR_BE);

        // Swap bytes if data and machine endianness differ
        bool machine_is_little_endian = is_little_endian();
        swap_bytes_ = (data_is_big_endian == machine_is_little_endian);
        position_ = 4; // Skip encapsulation header
    }

    XCDR1Decoder(std::vector<uint8_t>&&) = delete;

    // Decode primitive types
    uint8_t decode_uint8() { return read_aligned<uint8_t>(1); }
    int8_t decode_int8() { return read_aligned<int8_t>(1); }
    uint16_t decode_uint16() { 
        uint16_t value = read_aligned<uint16_t>(2);
        return swap_bytes_ ? swap_endian(value) : value;
    }
    int16_t decode_int16() { 
        int16_t value = read_aligned<int16_t>(2);
        return swap_bytes_ ? swap_endian(value) : value;
    }
    uint32_t decode_uint32() { 
        uint32_t value = read_aligned<uint32_t>(4);
        return swap_bytes_ ? swap_endian(value) : value;
    }
    int32_t decode_int32() { 
        int32_t value = read_aligned<int32_t>(4);
        return swap_bytes_ ? swap_endian(value) : value;
    }
    uint64_t decode_uint64() { 
        uint64_t value = read_aligned<uint64_t>(8);
        return swap_bytes_ ? swap_endian(value) : value;
    }
    int64_t decode_int64() { 
        int64_t value = read_aligned<int64_t>(8);
        return swap_bytes_ ? swap_endian(value) : value;
    }
    float decode_float() {
        uint32_t bits = read_aligned<uint32_t>(4);
        if (swap_bytes_) bits = swap_endian(bits);
        float value;
        std::memcpy(&value, &bits, sizeof(value));
        return value;
    }
    double decode_double() {
        uint64_t bits = read_aligned<uint64_t>(8);
        if (swap_bytes_) bits = swap_endian(bits);
        double value;
        std::memcpy(&value, &bits, sizeof(value));
        return value;
    }
    bool decode_bool() {
        return read_aligned<uint8_t>(1) != 0;
    }

    // Decode string
    std::string decode_string() {
        uint32_t length = decode_uint32();
    #ifdef ASTUTEDDS_XCDR_DIAGNOSTICS
        std::cerr << "[DECODE_STRING] After decode_uint32: length=" << length << ", pos=" << position_ << ", bufsize=" << buffer_.size() << std::endl;
    #endif
        if (length == 0) {
            return "";
        }

        if (position_ + length > buffer_.size()) {
    #ifdef ASTUTEDDS_XCDR_DIAGNOSTICS
            std::cerr << "[DECODE_STRING] Buffer underflow: pos=" << position_ << " + len=" << length << " = " << (position_ + length) << " > bufsize=" << buffer_.size() << std::endl;
    #endif
            throw std::runtime_error("Buffer underflow reading string");
        }

        std::string result(reinterpret_cast<const char*>(&buffer_[position_]), length - 1);
    #ifdef ASTUTEDDS_XCDR_DIAGNOSTICS
        std::cerr << "[DECODE_STRING] Created string: '" << result << "', length=" << (length-1) << std::endl;
    #endif
        position_ += length;
    #ifdef ASTUTEDDS_XCDR_DIAGNOSTICS
        std::cerr << "[DECODE_STRING] After adding length: pos=" << position_ << std::endl;
    #endif
        // Align to 4-byte boundary after string data
        position_ = align_offset(position_, 4);
    #ifdef ASTUTEDDS_XCDR_DIAGNOSTICS
        std::cerr << "[DECODE_STRING] After alignment: pos=" << position_ << std::endl;
    #endif
        return result;
    }

    // Decode sequence length
    uint32_t decode_sequence_length() {
        return decode_uint32();
    }

    size_t position() const { return position_; }
    bool at_end() const { return position_ >= buffer_.size(); }

private:
    template<typename T>
    T read_aligned(size_t alignment) {
        position_ = align_offset(position_, alignment);

        if (position_ + sizeof(T) > buffer_.size()) {
            throw std::runtime_error("Buffer underflow");
        }

        T value;
        std::memcpy(&value, &buffer_[position_], sizeof(T));
        position_ += sizeof(T);
        return value;
    }

    const std::vector<uint8_t>& buffer_;
    size_t position_;
    bool swap_bytes_;
};

} // namespace astutedds::cdr

#endif // ASTUTEDDS_XCDR1_CODEC_HPP