--- /dev/null
+#pragma once
+/******************************************************************************
+*
+* Copyright (c) 2019 AT&T Intellectual Property.
+* Copyright (c) 2018-2019 Nokia.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+*******************************************************************************/
+
+// Standard Includes: ANSI C/C++, MSA, and Third-Party Libraries
+#include <cstring>
+
+// Local Includes: Application specific classes, functions, and libraries
+#include "asn/error_context.hpp"
+#include "value_traits.hpp"
+
+//#define CODEC_BIT_TRACE_ENABLE
+#ifdef CODEC_BIT_TRACE_ENABLE
+ #define CODEC_BIT_TRACE(FMT, args...) printf("%s[%u]:" FMT "\n", std::strrchr(__FILE__, '/') + 1, __LINE__, ##args)
+#else
+ #define CODEC_BIT_TRACE(...)
+#endif
+
+namespace asn {
+
+template <class IE, class VALUE, bool LSB>
+struct bit_accessor;
+
+template <bool LSB>
+struct bit_accessor_cross_byte;
+
+template <typename PTR>
+class buffer
+{
+public:
+ typedef PTR pointer;
+
+ explicit buffer(error_context& err)
+ : m_errCtx(err)
+ {}
+
+ void reset(pointer data, u32 size)
+ {
+ m_start = data;
+ m_end = m_start + size;
+ m_current = m_start;
+ m_shift = 0;
+ }
+
+ u32 getOffset() const { return begin() - m_start; }
+
+ u32 getBytesLeft() const { return end() - begin(); }
+ u32 getBytesUsed() const { return getOffset() + (get_shift() ? 1 : 0); }
+
+ u8 get_shift() const { return m_shift; }
+ void reset_shift() { m_shift = 0; }
+ void byte_align() { if (get_shift()) { m_shift = 0; ++m_current; } }
+
+ pointer advance(u32 delta) { pointer p = begin(); m_current += delta; return p; }
+
+ pointer begin() const { return m_current; }
+
+ void set_begin(pointer ptr, u8 bit_shift)
+ {
+ if (m_start <= ptr && ptr <= end())
+ {
+ m_current = ptr;
+ m_shift = bit_shift;
+ }
+ }
+
+ pointer end() const { return m_end; }
+ void set_end(pointer v) { m_end = v; }
+
+ bool checkAlignment(char const* name, u8 const mask = 0xFF)
+ {
+ if (0 == (get_shift() & mask)) return true;
+ m_errCtx.alignmentError(name, get_shift());
+ return false;
+ }
+
+ bool checkBytesLeft(char const* name, u32 const num_bytes)
+ {
+ if (getBytesLeft() >= num_bytes) return true;
+ m_errCtx.lengthErrorBytes(name, getBytesLeft(), num_bytes);
+ return false;
+ }
+
+ bool checkBitsLeft(char const* name, u32 const num_bits)
+ {
+ if (getBitsLeft() >= num_bits) return true;
+ m_errCtx.lengthErrorBits(name, getBitsLeft(), num_bits);
+ return false;
+ }
+
+ bool checkBytesAndAlignment(char const* name, u32 const num_bytes, u8 const mask = 0xFF)
+ {
+ if (getBytesLeft() >= num_bytes)
+ {
+ if (0 == (get_shift() & mask)) return true;
+ m_errCtx.alignmentError(name, get_shift());
+ }
+ else
+ {
+ m_errCtx.lengthErrorBytes(name, getBytesLeft(), num_bytes);
+ }
+ return false;
+ }
+
+ pointer getBytes(char const* name, u32 num_bytes)
+ {
+ if (checkBytesAndAlignment(name, num_bytes))
+ {
+ return advance(num_bytes);
+ }
+ return nullptr;
+ }
+
+ //NOTE! num_bytes should be set to minimal number of bytes expected
+ pointer getBytes(char const* name, u32 max_requested, u32& num_bytes)
+ {
+ if (checkAlignment(name))
+ {
+ u32 const left = getBytesLeft();
+ if (left >= num_bytes)
+ {
+ num_bytes = (left > max_requested) ? max_requested : left;
+ return advance(num_bytes);
+ }
+
+ m_errCtx.lengthErrorBytes(name, getBytesLeft(), num_bytes);
+ }
+ return nullptr;
+ }
+
+ bool putByte(char const* name, u8 byte)
+ {
+ if (checkBytesAndAlignment(name, 1))
+ {
+ begin()[0] = byte;
+ ++m_current;
+ return true;
+ }
+ return false;
+ }
+
+ bool putBytes(char const* name, void const* in, u32 num_bytes)
+ {
+ if (checkBytesAndAlignment(name, num_bytes))
+ {
+ std::memcpy(begin(), in, num_bytes);
+ m_current += num_bytes;
+ return true;
+ }
+ return false;
+ }
+
+ bool checkAlignment(u8 const mask = 0xFF)
+ {
+ if (0 == (get_shift() & mask)) return true;
+ m_errCtx.alignmentError(get_shift());
+ return false;
+ }
+
+ bool checkBytesLeft(u32 const num_bytes)
+ {
+ if (getBytesLeft() >= num_bytes) return true;
+ m_errCtx.lengthErrorBytes(getBytesLeft(), num_bytes);
+ return false;
+ }
+
+ bool checkBitsLeft(u32 const num_bits)
+ {
+ if (getBitsLeft() >= num_bits) return true;
+ m_errCtx.lengthErrorBits(getBitsLeft(), num_bits);
+ return false;
+ }
+
+ bool checkBytesAndAlignment(u32 const num_bytes, u8 const mask = 0xFF)
+ {
+ if (getBytesLeft() >= num_bytes)
+ {
+ if (0 == (get_shift() & mask)) return true;
+ m_errCtx.alignmentError(get_shift());
+ }
+ else
+ {
+ m_errCtx.lengthErrorBytes(getBytesLeft(), num_bytes);
+ }
+ return false;
+ }
+
+ pointer getBytes(u32 num_bytes)
+ {
+ if (checkBytesAndAlignment(num_bytes))
+ {
+ return advance(num_bytes);
+ }
+ return nullptr;
+ }
+
+ //NOTE! num_bytes should be set to minimal number of bytes expected
+ pointer getBytes(u32 max_requested, u32& num_bytes)
+ {
+ if (checkAlignment())
+ {
+ u32 const left = getBytesLeft();
+ if (left >= num_bytes)
+ {
+ num_bytes = (left > max_requested) ? max_requested : left;
+ return advance(num_bytes);
+ }
+
+ m_errCtx.lengthErrorBytes(getBytesLeft(), num_bytes);
+ }
+ return nullptr;
+ }
+
+ bool putByte(u8 byte)
+ {
+ if (checkBytesAndAlignment(1))
+ {
+ begin()[0] = byte;
+ ++m_current;
+ return true;
+ }
+ return false;
+ }
+
+ bool putBytes(void const* in, u32 num_bytes)
+ {
+ if (num_bytes)
+ {
+ if(checkBytesAndAlignment(num_bytes))
+ {
+ std::memcpy(begin(), in, num_bytes);
+ m_current += num_bytes;
+ }
+ else
+ return false;
+ }
+ return true;
+ }
+
+ char const* toString() const
+ {
+ static char sz[64];
+ u8 const* p = begin();
+ snprintf(sz, sizeof(sz), "%02X %02X %02X %02X [%02X]=%p@%u..%p -%u bits: +%u bytes", p[-4], p[-3], p[-2], p[-1], p[0], p
+ , getOffset(), end(), get_shift(), getBytesLeft());
+ return sz;
+ }
+
+private:
+ template <class IE, class VALUE, bool LSB>
+ friend class bit_accessor;
+
+ template <bool LSB>
+ friend struct bit_accessor_cross_byte;
+
+ template <typename U>
+ void push_u8(U value) { *m_current++ = static_cast<u8>(value); }
+ template <typename U>
+ void put_u8(U value) { m_current[0] = static_cast<u8>(value); }
+
+//khxm68: TODO: won't compile without it. WTF?!
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
+ u8 get_u8() const { return m_current[0]; }
+#pragma GCC diagnostic pop
+ u8 extract_u8(u8 value, u8 mask, u8 shift) const
+ { return (get_u8() & ~(mask << shift)) | ((value & mask) << shift); }
+
+ u16 get_u16() const { return (static_cast<u16>(m_current[0]) << 8) | m_current[1]; }
+ u16 extract_u16(u16 value, u16 mask, u16 shift) const
+ { return (get_u16() & ~(mask << shift)) | ((value & mask) << shift); }
+
+ u32 get_u24() const { return (static_cast<u32>(m_current[0]) << 16) | (static_cast<u32>(m_current[1]) << 8) | m_current[2]; }
+ u32 extract_u24(u32 value, u32 mask, u32 shift) const
+ { return (get_u24() & ~(mask << shift)) | ((value & mask) << shift); }
+
+ u32 get_u32() const { return (static_cast<u32>(m_current[0]) << 24) | (static_cast<u32>(m_current[1]) << 16) | (static_cast<u32>(m_current[2]) << 8) | m_current[3]; }
+ u32 extract_u32(u32 value, u32 mask, u32 shift) const
+ { return (get_u32() & ~(mask << shift)) | ((value & mask) << shift); }
+
+ u32 getBitsLeft() const { return begin() == end() ? 0 : (getBytesLeft()*8 - get_shift()); }
+
+ void bit_advance(u8 num_bits)
+ {
+ m_shift += num_bits;
+ if (get_shift() > 7)
+ {
+ reset_shift();
+ m_current++;
+ }
+ }
+
+ pointer m_current;
+ pointer m_end;
+ pointer m_start;
+ u8 m_shift;
+ error_context& m_errCtx;
+};
+
+template <class IE> //LSB 1..7 bits
+struct bit_accessor<IE, stdex::value::_8, true>
+{
+ static_assert(IE::length::value > 0 && IE::length::value < 8, "something wrong with traits!");
+
+ static void put(u8 value, buffer<u8*>& buf)
+ {
+ u8 const mask = static_cast<u8>((1u << IE::length::value) - 1);
+ u8 const shift = buf.get_shift();
+ u8 const data = buf.extract_u8(value, mask, shift);
+ buf.put_u8(data);
+
+ CODEC_BIT_TRACE("put shift=%u(%u) data[%u]=%02X(%02X) mask=%04X: %s", shift, buf.get_shift()
+ , IE::length::value, data, value, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value);
+ }
+
+ static u8 get(buffer<u8 const*>& buf)
+ {
+ u8 const mask = static_cast<u8>((1u << IE::length::value) - 1);
+ u8 const shift = buf.get_shift();
+ u8 const data = (buf.get_u8() >> shift) & mask;
+
+ CODEC_BIT_TRACE("get shift=%u(%u) data[%u]=%02X mask=%02X: %s", shift, buf.get_shift()
+ , IE::length::value, data, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value);
+ return data;
+ }
+};
+
+template <class IE> //LSB 9..15 bits
+struct bit_accessor<IE, stdex::value::_16, true>
+{
+ static_assert(IE::length::value > 8 && IE::length::value < 16, "something wrong with traits!");
+
+ static void put(u16 value, buffer<u8*>& buf)
+ {
+ u16 const mask = static_cast<u16>((1u << IE::length::value) - 1);
+ u16 const shift = buf.get_shift();
+ u16 const data = buf.extract_u16(value, mask, shift);
+ buf.push_u8(data >> 8);
+ buf.put_u8(data);
+
+ CODEC_BIT_TRACE("put shift=%u(%u) data[%u]=%04X(%04X) mask=%04X: %s", shift, buf.get_shift()
+ , IE::length::value, data, value, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value - 8);
+ }
+
+ static u16 get(buffer<u8 const*>& buf)
+ {
+ u16 const mask = static_cast<u16>((1u << IE::length::value) - 1);
+ u16 const shift = buf.get_shift();
+ u16 const data = (buf.get_u16() >> shift) & mask;
+ buf.m_current += 1;
+
+ CODEC_BIT_TRACE("get shift=%u(%u) data[%u]=%04X mask=%04X: %s", shift, buf.get_shift()
+ , IE::length::value, data, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value - 8);
+ return data;
+ }
+};
+
+template <class IE> //LSB 17..24 bits
+struct bit_accessor<IE, stdex::value::_24, true>
+{
+ static_assert(IE::length::value > 16 && IE::length::value <= 24, "something wrong with traits!");
+
+ static void put(u32 value, buffer<u8*>& buf)
+ {
+ u32 const mask = static_cast<u32>((1u << IE::length::value) - 1);
+ u32 const shift = buf.get_shift();
+ u32 const data = buf.extract_u24(value, mask, shift);
+
+ buf.push_u8(data >> 16);
+ buf.push_u8(data >> 8);
+ buf.put_u8(data);
+
+ CODEC_BIT_TRACE("put shift=%u(%u) data[%u]=%06X(%06X) mask=%06X: %s", shift, buf.get_shift()
+ , IE::length::value, data, value, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value - 16);
+ }
+
+ static u32 get(buffer<u8 const*>& buf)
+ {
+ u32 const mask = static_cast<u32>((1u << IE::length::value) - 1);
+ u32 const shift = buf.get_shift();
+ u32 const data = (buf.get_u24() >> shift) & mask;
+ buf.m_current += 2;
+
+ CODEC_BIT_TRACE("get shift=%u(%u) data[%u]=%06X mask=%06X: %s", shift, buf.get_shift()
+ , IE::length::value, data, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value - 16);
+ return data;
+ }
+};
+
+template <class IE> //LSB 25..31 bits
+struct bit_accessor<IE, stdex::value::_32, true>
+{
+ static_assert(IE::length::value > 24 && IE::length::value < 32, "something wrong with traits!");
+
+ static void put(u32 value, buffer<u8*>& buf)
+ {
+ u32 const mask = static_cast<u32>((1u << IE::length::value) - 1);
+ u32 const shift = buf.get_shift();
+ u32 const data = buf.extract_u32(value, mask, shift);
+
+ buf.push_u8(data >> 24);
+ buf.push_u8(data >> 16);
+ buf.push_u8(data >> 8);
+ buf.put_u8(data);
+
+ CODEC_BIT_TRACE("put shift=%u(%u) data[%u]=%08X(%08X) mask=%08X: %s", shift, buf.get_shift()
+ , IE::length::value, data, value, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value - 24);
+ }
+
+ static u32 get(buffer<u8 const*>& buf)
+ {
+ u32 const mask = static_cast<u32>((1u << IE::length::value) - 1);
+ u32 const shift = buf.get_shift();
+ u32 const data = (buf.get_u32() >> shift) & mask;
+ buf.m_current += 3;
+
+ CODEC_BIT_TRACE("get shift=%u(%u) data[%u]=%08X mask=%08X: %s", shift, buf.get_shift()
+ , IE::length::value, data, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value - 24);
+ return data;
+ }
+};
+
+
+template <class IE> //MSB 1..7 bits
+struct bit_accessor<IE, stdex::value::_8, false>
+{
+ static_assert(IE::length::value > 0 && IE::length::value < 8, "something wrong with traits!");
+
+ static void put(u8 value, buffer<u8*>& buf)
+ {
+ u8 const mask = static_cast<u8>((1u << IE::length::value) - 1);
+ u8 const shift = 8 - buf.get_shift() - IE::length::value;
+ u8 const data = buf.extract_u8(value, mask, shift);
+ buf.put_u8(data);
+
+ CODEC_BIT_TRACE("put shift=%u(%u) data[%u]=%02X(%02X) mask=%04X: %s", shift, buf.get_shift()
+ , IE::length::value, data, value, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value);
+ }
+
+ static u8 get(buffer<u8 const*>& buf)
+ {
+ u8 const mask = static_cast<u8>((1u << IE::length::value) - 1);
+ u8 const shift = 8 - buf.get_shift() - IE::length::value;
+ u8 const data = (buf.get_u8() >> shift) & mask;
+
+ CODEC_BIT_TRACE("get shift=%u(%u) data[%u]=%02X mask=%02X: %s", shift, buf.get_shift()
+ , IE::length::value, data, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value);
+ return data;
+ }
+};
+
+template <class IE> //MSB 9..15 bits
+struct bit_accessor<IE, stdex::value::_16, false>
+{
+ static_assert(IE::length::value > 8 && IE::length::value < 16, "something wrong with traits!");
+
+ static void put(u16 value, buffer<u8*>& buf)
+ {
+ u16 const mask = static_cast<u16>((1u << IE::length::value) - 1);
+ u16 const shift = 16 - buf.get_shift() - IE::length::value;
+ u16 const data = buf.extract_u16(value, mask, shift);
+
+ buf.push_u8(data >> 8);
+ buf.put_u8(data);
+
+ CODEC_BIT_TRACE("put shift=%u(%u) data[%u]=%04X(%04X) mask=%04X: %s", shift, buf.get_shift()
+ , IE::length::value, data, value, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value - 8);
+ }
+
+ static u16 get(buffer<u8 const*>& buf)
+ {
+ u16 const mask = static_cast<u16>((1u << IE::length::value) - 1);
+ u16 const shift = 16 - buf.get_shift() - IE::length::value;
+ u16 const data = (buf.get_u16() >> shift) & mask;
+ buf.m_current += 1;
+
+ CODEC_BIT_TRACE("get shift=%u(%u) data[%u]=%04X mask=%04X: %s", shift, buf.get_shift()
+ , IE::length::value, data, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value - 8);
+ return data;
+ }
+};
+
+
+template <class IE> //MSB 17..24 bits
+struct bit_accessor<IE, stdex::value::_24, false>
+{
+ static_assert(IE::length::value > 16 && IE::length::value <= 24, "something wrong with traits!");
+
+ static void put(u32 value, buffer<u8*>& buf)
+ {
+ u32 const mask = static_cast<u32>((1u << IE::length::value) - 1);
+ u32 const shift = 24 - buf.get_shift() - IE::length::value;
+ u32 const data = buf.extract_u24(value, mask, shift);
+
+ buf.push_u8(data >> 16);
+ buf.push_u8(data >> 8);
+ buf.put_u8(data);
+
+ CODEC_BIT_TRACE("put shift=%u(%u) data[%u]=%06X(%06X) mask=%06X: %s", shift, buf.get_shift()
+ , IE::length::value, data, value, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value - 16);
+ }
+
+ static u32 get(buffer<u8 const*>& buf)
+ {
+ u32 const mask = static_cast<u32>((1u << IE::length::value) - 1);
+ u32 const shift = 24 - buf.get_shift() - IE::length::value;
+ u32 const data = (buf.get_u24() >> shift) & mask;
+ buf.m_current += 2;
+
+ CODEC_BIT_TRACE("get shift=%u(%u) data[%u]=%06X mask=%06X: %s", shift, buf.get_shift()
+ , IE::length::value, data, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value - 16);
+ return data;
+ }
+};
+
+template <class IE> //MSB 25..31 bits
+struct bit_accessor<IE, stdex::value::_32, false>
+{
+ static_assert(IE::length::value > 24 && IE::length::value < 32, "something wrong with traits!");
+
+ static void put(u32 value, buffer<u8*>& buf)
+ {
+ u32 const mask = static_cast<u32>((1u << IE::length::value) - 1);
+ u32 const shift = 32 - buf.get_shift() - IE::length::value;
+ u32 const data = buf.extract_u32(value, mask, shift);
+
+ buf.push_u8(data >> 24);
+ buf.push_u8(data >> 16);
+ buf.push_u8(data >> 8);
+ buf.put_u8(data);
+
+ CODEC_BIT_TRACE("put shift=%u(%u) data[%u]=%08X(%08X) mask=%08X: %s", shift, buf.get_shift()
+ , IE::length::value, data, value, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value - 24);
+ }
+
+ static u32 get(buffer<u8 const*>& buf)
+ {
+ u32 const mask = static_cast<u32>((1u << IE::length::value) - 1);
+ u32 const shift = 32 - buf.get_shift() - IE::length::value;
+ u32 const data = (buf.get_u32() >> shift) & mask;
+ buf.m_current += 3;
+
+ CODEC_BIT_TRACE("get shift=%u(%u) data[%u]=%08X mask=%08X: %s", shift, buf.get_shift()
+ , IE::length::value, data, mask, buf.toString());
+
+ buf.bit_advance(IE::length::value - 24);
+ return data;
+ }
+};
+
+/********************************************************************************
+bit_accessor_cross_byte
+*********************************************************************************/
+template<>
+struct bit_accessor_cross_byte<false> //MSB
+{
+ static bool put(u8 value, u8 length, buffer<u8*>& buf)
+ {
+ u8 spare = 8 - buf.get_shift();
+
+ if (spare < length)
+ {
+ length -= spare;
+ u8 chunk = value >> length;
+ u8 const mask = static_cast<u8>((1u << spare) - 1);
+ u8 data = buf.extract_u8(chunk, mask, 0);
+ buf.put_u8(data);
+
+ buf.bit_advance(spare);
+ }
+ if (buf.checkBytesLeft(1))
+ {
+ u8 const mask = static_cast<u8>((1u << length) - 1);
+ u8 const shift = 8 - buf.get_shift() - length;
+ u8 data = buf.extract_u8(value, mask, shift);
+ buf.put_u8(data);
+
+ buf.bit_advance(length);
+
+ return true;
+ }
+ return false;
+ }
+
+ static u8 get(u8 length, buffer<u8 const*>& buf)
+ {
+ u8 rval {0};
+ u8 const mask = static_cast<u8>((1u << length) - 1);
+ u8 const spare = 8 - buf.get_shift();
+ if (spare < length)
+ {
+ length -= spare;
+ rval = buf.get_u8() << length;
+ buf.bit_advance(spare);
+ }
+ if (length && buf.checkBytesLeft(1))
+ {
+ u8 val = buf.get_u8();
+ val = val >> (8 - length - buf.get_shift());
+ rval |= val;
+ rval &= mask;
+ buf.bit_advance(length);
+ }
+ return rval;
+ }
+
+ static void padByte(buffer<u8*>& buf)
+ {
+ if (buf.get_shift())
+ {
+ u8 mask = static_cast<u8>((1u << buf.get_shift()) - 1);
+ mask = mask << (8 - buf.get_shift());
+ buf.begin()[0] &= mask;
+ buf.bit_advance(8 - buf.get_shift());
+ }
+ }
+
+ static void padByte(buffer<u8 const*>& buf)
+ {
+ if(buf.get_shift())
+ buf.bit_advance(8 - buf.get_shift());
+ }
+
+ static bool put(const u8* in, size_t len, buffer<u8*>& buf, u8 trail_bitsqty)
+ {
+ if (len)
+ {
+ if (trail_bitsqty)
+ {
+ buf.putBytes(in, len - 1);
+ u8 last_byte = in[len - 1];
+ last_byte = last_byte >> (8 - trail_bitsqty);
+ return put(last_byte, trail_bitsqty, buf);
+ }
+ buf.putBytes(in, len);
+ }
+ return true;
+ }
+};
+
+} //end: namespace asn
+
+