BCD (Binary Coded Decimals): C++11/STL parsing library

BCD (Binary Coded Decimals) is a common way to encode integer numbers in a way that makes human-readable output somewhat simpler. In this encoding scheme, every decimal digit is encoded as either a single byte (8 bits), or a nibble (half of a byte, 4 bits). This obviously wastes a lot of bits, but it makes translation into human-readable string much easier than traditional binary-to-decimal conversion process, which includes lots of divisions by 10.

For example, encoding integer 31337 in 8-digit, 8 bits per digit, big endian order of digits BCD format yields

00 00 00 03 01 03 03 07

Encoding the same integer as 8-digit, 4 bits per digit, little endian order BCD format would yield:

73 31 30 00

Using this type of encoding in Kaitai Struct is pretty straightforward: one calls for this type, specifying desired encoding parameters, and gets result using either as_int or as_str attributes.

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.8

References

This page hosts a formal specification of BCD (Binary Coded Decimals) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for C++11/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream). One can open local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.bin", std::ifstream::binary);
    
    #include <sstream>
    
    std::istringstream is(str);
    
    #include <sstream>
    
    const char buf[] = { ... };
    std::string str(buf, sizeof buf);
    std::istringstream is(str);
    
  2. We need to wrap our input stream into Kaitai stream:
    #include "kaitai/kaitaistream.h"
    
    kaitai::kstream ks(&is);
    
  3. And finally, we can invoke the parsing:
    bcd_t data(&ks);
    

After that, one can get various attributes from the structure by invoking getter methods like:

data.as_int() // => Value of this BCD number as integer. Endianness would be selected based on `is_le` parameter given.

C++11/STL source code to parse BCD (Binary Coded Decimals)

bcd.h

#pragma once

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "kaitai/kaitaistruct.h"
#include <stdint.h>
#include <memory>
#include <vector>

#if KAITAI_STRUCT_VERSION < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif

/**
 * BCD (Binary Coded Decimals) is a common way to encode integer
 * numbers in a way that makes human-readable output somewhat
 * simpler. In this encoding scheme, every decimal digit is encoded as
 * either a single byte (8 bits), or a nibble (half of a byte, 4
 * bits). This obviously wastes a lot of bits, but it makes translation
 * into human-readable string much easier than traditional
 * binary-to-decimal conversion process, which includes lots of
 * divisions by 10.
 * 
 * For example, encoding integer 31337 in 8-digit, 8 bits per digit,
 * big endian order of digits BCD format yields
 * 
 * ```
 * 00 00 00 03 01 03 03 07
 * ```
 * 
 * Encoding the same integer as 8-digit, 4 bits per digit, little
 * endian order BCD format would yield:
 * 
 * ```
 * 73 31 30 00
 * ```
 * 
 * Using this type of encoding in Kaitai Struct is pretty
 * straightforward: one calls for this type, specifying desired
 * encoding parameters, and gets result using either `as_int` or
 * `as_str` attributes.
 */

class bcd_t : public kaitai::kstruct {

public:

    bcd_t(uint8_t p_num_digits, uint8_t p_bits_per_digit, bool p_is_le, kaitai::kstream* p__io, kaitai::kstruct* p__parent = nullptr, bcd_t* p__root = nullptr);

private:
    void _read();
    void _clean_up();

public:
    ~bcd_t();

private:
    bool f_as_int;
    int32_t m_as_int;

public:

    /**
     * Value of this BCD number as integer. Endianness would be selected based on `is_le` parameter given.
     */
    int32_t as_int();

private:
    bool f_as_int_le;
    int32_t m_as_int_le;

public:

    /**
     * Value of this BCD number as integer (treating digit order as little-endian).
     */
    int32_t as_int_le();

private:
    bool f_last_idx;
    int32_t m_last_idx;

public:

    /**
     * Index of last digit (0-based).
     */
    int32_t last_idx();

private:
    bool f_as_int_be;
    int32_t m_as_int_be;

public:

    /**
     * Value of this BCD number as integer (treating digit order as big-endian).
     */
    int32_t as_int_be();

private:
    std::unique_ptr<std::vector<int32_t>> m_digits;
    uint8_t m_num_digits;
    uint8_t m_bits_per_digit;
    bool m_is_le;
    bcd_t* m__root;
    kaitai::kstruct* m__parent;

public:
    std::vector<int32_t>* digits() const { return m_digits.get(); }

    /**
     * Number of digits in this BCD representation. Only values from 1 to 8 inclusive are supported.
     */
    uint8_t num_digits() const { return m_num_digits; }

    /**
     * Number of bits per digit. Only values of 4 and 8 are supported.
     */
    uint8_t bits_per_digit() const { return m_bits_per_digit; }

    /**
     * Endianness used by this BCD representation. True means little-endian, false is for big-endian.
     */
    bool is_le() const { return m_is_le; }
    bcd_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

bcd.cpp

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "bcd.h"

bcd_t::bcd_t(uint8_t p_num_digits, uint8_t p_bits_per_digit, bool p_is_le, kaitai::kstream* p__io, kaitai::kstruct* p__parent, bcd_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_num_digits = p_num_digits;
    m_bits_per_digit = p_bits_per_digit;
    m_is_le = p_is_le;
    m_digits = nullptr;
    f_as_int = false;
    f_as_int_le = false;
    f_last_idx = false;
    f_as_int_be = false;
    _read();
}

void bcd_t::_read() {
    m_digits = std::unique_ptr<std::vector<int32_t>>(new std::vector<int32_t>());
    const int l_digits = num_digits();
    for (int i = 0; i < l_digits; i++) {
        switch (bits_per_digit()) {
        case 4: {
            m_digits->push_back(std::move(m__io->read_bits_int_be(4)));
            break;
        }
        case 8: {
            m_digits->push_back(std::move(m__io->read_u1()));
            break;
        }
        }
    }
}

bcd_t::~bcd_t() {
    _clean_up();
}

void bcd_t::_clean_up() {
}

int32_t bcd_t::as_int() {
    if (f_as_int)
        return m_as_int;
    m_as_int = ((is_le()) ? (as_int_le()) : (as_int_be()));
    f_as_int = true;
    return m_as_int;
}

int32_t bcd_t::as_int_le() {
    if (f_as_int_le)
        return m_as_int_le;
    m_as_int_le = (digits()->at(0) + ((num_digits() < 2) ? (0) : (((digits()->at(1) * 10) + ((num_digits() < 3) ? (0) : (((digits()->at(2) * 100) + ((num_digits() < 4) ? (0) : (((digits()->at(3) * 1000) + ((num_digits() < 5) ? (0) : (((digits()->at(4) * 10000) + ((num_digits() < 6) ? (0) : (((digits()->at(5) * 100000) + ((num_digits() < 7) ? (0) : (((digits()->at(6) * 1000000) + ((num_digits() < 8) ? (0) : ((digits()->at(7) * 10000000))))))))))))))))))))));
    f_as_int_le = true;
    return m_as_int_le;
}

int32_t bcd_t::last_idx() {
    if (f_last_idx)
        return m_last_idx;
    m_last_idx = (num_digits() - 1);
    f_last_idx = true;
    return m_last_idx;
}

int32_t bcd_t::as_int_be() {
    if (f_as_int_be)
        return m_as_int_be;
    m_as_int_be = (digits()->at(last_idx()) + ((num_digits() < 2) ? (0) : (((digits()->at((last_idx() - 1)) * 10) + ((num_digits() < 3) ? (0) : (((digits()->at((last_idx() - 2)) * 100) + ((num_digits() < 4) ? (0) : (((digits()->at((last_idx() - 3)) * 1000) + ((num_digits() < 5) ? (0) : (((digits()->at((last_idx() - 4)) * 10000) + ((num_digits() < 6) ? (0) : (((digits()->at((last_idx() - 5)) * 100000) + ((num_digits() < 7) ? (0) : (((digits()->at((last_idx() - 6)) * 1000000) + ((num_digits() < 8) ? (0) : ((digits()->at((last_idx() - 7)) * 10000000))))))))))))))))))))));
    f_as_int_be = true;
    return m_as_int_be;
}