/* -*- c++ -*- */
/* Copyright 2015-2016 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

#ifndef INCLUDED_DIGITAL_HEADER_BUFFER_H
#define INCLUDED_DIGITAL_HEADER_BUFFER_H

#include <gnuradio/digital/api.h>
#include <cstdint>
#include <cstdlib>
#include <vector>

namespace gr {
namespace digital {

/*!
 * \brief Helper class for handling payload headers.
 * \ingroup packet_operators_blk
 *
 * \details
 *
 * This class is used by the header format blocks (e.g.,
 * digital::header_format_default) to make it easier to deal with
 * payload headers. This class functions in two different ways
 * depending on if it is used in a transmitter or receiver. When
 * used in a transmitter, this class helps us build headers out of
 * the fields of the protocol. When used in a receiver, this class
 * helps us parse the received bits into the protocol's fields.
 *
 * This page describes how to work with the different modes,
 * transmit or receive. The class is instructed as to which mode
 * it is in by how the constructor is called. If the constructor
 * is passed a valid array (non NULL), then it is in transmit mode
 * and will pack this buffer with the header fields. If that
 * buffer is NULL, the object is in receive mode.
 *
 * \section header_buffer_tx Transmit Mode
 *
 * When passed a valid buffer in the constructor, this object is in
 * transmit mode. We can then use the add_field[N] functions to
 * add new fields to this header. The buffer MUST be large enough
 * to hold the full header. As this class is meant to work mostly
 * with the digital::header_format_default and child
 * classes, the header length can be read from
 * digital::header_format_default::header_nbytes().
 *
 * Each field is a specific length of 8, 16, 32, or 64 bits that
 * are to be transmitted in network byte order. We can adjust the
 * direction of the bytes by setting the byte-swap flag, \p bs, to
 * true or false.
 *
 * The length argument (\p len) for all add_field[N] calls is the
 * number of bytes actually accounted for in the data
 * structure. Often, we would use the full size of the field,
 * which is sizeof(dtype), and the add_field[N] call defaults to
 * len=N. Occasionally, we may need to use fewer bytes than
 * actually represented by the data type. An example would be the
 * access code used in the header_format_default, which is a
 * uint64_t type but may have fewer bytes used in the actual
 * access code.
 *
 * The function that calls this class is expected to handle the
 * memory handling of the buffer -- both allocating and
 * deallocating.
 *
 * As simple example of using this class in transmit mode:
 *
 * \verbatim
 volk::vector<uint8_t> buffer(header_nbytes());

 header_buffer hdr(buffer);
 hdr.add_field64(sync_word, sync_word_len);
 hdr.add_field16(payload_length);
 hdr.add_field8(header_flags);
 hdr.add_field8(header_options);

 // Do something with the header
 \endverbatim
 *
 * In this example, the header contains four fields:
 *
 * \verbatim
   |0                           15|16          23|24          31|
   |                          sync word                         |
   |                                                            |
   |           length             |     flags    |   options    |
   \endverbatim
 *
 * The sync word can be up to 64-bits, but the add_field64 is also
 * passed the number of actual bytes in the sync word and so could
 * be fewer.
 *
 * \section header_buffer_rx Receive Mode
 *
 * In receive mode, we build up the header as bits are received by
 * inserting them with insert_bit. We can find out how long the
 * current header is, in bits, using the call to length(). If the
 * header is of the appropriate length, we can then start
 * extracting the fields from it. When we are done with the
 * current header, call clear() to reset the internal buffer to
 * empty, which will mean that length() returns 0.
 *
 * The header fields are extracted using the extract_field[N]
 * functions. Like the add_field[N] functions, we specify the size
 * (in bits) of the field we are extracting. We pass this function
 * the bit-position of the expected field in the received header
 * buffer. The extract_field[N] assumes that the number of bits
 * for the field is N, but we can tell the function to use fewer
 * bits if we want. Setting the length parameter of these
 * functions greater than N is illegal, and it will throw an
 * error.
 *
 * For example, given a header of | length | seq. num. | where the
 * length is 16 bits and the sequence number is 32 bits, we would
 * use:
 *
 * \verbatim
      uint16_t len = d_hdr_reg.extract_field16(0);
      uint32_t seq = d_hdr_reg.extract_field32(16);
   \endverbatim
 *
 * The extract_field functions are specific to data types of the
 * field and the number of bits for each field is inferred by the
 * data type. So extract_field16 assumes we want all 16 bits in
 * the field represented.
 *
 * Some headers have fields that are not standard sizes of
 * integers, like a 1 bit, 4 bit, or even 12 bit fields. We can
 * ask for fewer bits for each field. say:
 *
 * \verbatim
   |0          15|16     19|20          31|
   |     len     |  flags  |   options    |
   \endverbatim
 *
 * We would use the following extraction functions:
 *
 * \verbatim
      uint16_t len   = d_hdr_reg.extract_field16(0);
      uint8_t  flags = d_hdr_reg.extract_field8(16, 4);
      uint16_t opts  = d_hdr_reg.extract_field16(20, 12);
   \endverbatim
 *
 * \sa header_format_default
 * \sa header_format_counter
 * \sa header_format_crc
 */
class DIGITAL_API header_buffer
{
private:
    size_t d_offset;
    uint8_t* d_buffer;

    std::vector<bool> d_input;

public:
    /*!
     * Create a header buffer object with a pre-allocated buffer, \p
     * buffer, to hold the formatted header data.
     *
     * If \p buffer is set to NULL, then this object is in receive
     * mode meant to receive bits from an incoming data stream and
     * provide the ability to extract fields. In this mode, calls to
     * add_field are invalid and will be nops.
     */
    header_buffer(uint8_t* buffer = NULL);

    /*!
     * Class destructor.
     */
    ~header_buffer();

    /*!
     * Clears the header.
     *
     * In transmit mode, this resets the current offset so new
     * add_field functions start adding data to the start of the
     * buffer.
     *
     * In receive mode, this clears the buffer that we have inserted
     * bits in to.
     */
    void clear();


    /*!
     * In transmit mode, this returns the length of the data in
     * the buffer (not the allocated buffer length).
     *
     * In receiving mode, this returns the current length in bits of
     * the received header.
     */
    size_t length() const;

    /*!
     * Returns a constant pointer to the buffer.
     */
    const uint8_t* header() const;

    /*!
     * Add an 8-bit field to the header.
     *
     * \param data The 8-bit data item.
     * \param len Length (in bits) of \p data.
     * \param bs Set to 'true' to byte swap the data.
     */
    void add_field8(uint8_t data, int len = 8, bool bs = false);

    /*!
     * Add an 16-bit field to the header.
     *
     * \param data The 16-bit data item.
     * \param len Length (in bits) of \p data.
     * \param bs Set to 'true' to byte swap the data.
     */
    void add_field16(uint16_t data, int len = 16, bool bs = false);

    /*!
     * Add an 32-bit field to the header.
     *
     * \param data The 32-bit data item.
     * \param len Length (in bits) of \p data.
     * \param bs Set to 'true' to byte swap the data.
     */
    void add_field32(uint32_t data, int len = 32, bool bs = false);

    /*!
     * Add an 64-bit field to the header.
     *
     * \param data The 64-bit data item.
     * \param len Length (in bits) of \p data.
     * \param bs Set to 'true' to byte swap the data.
     */
    void add_field64(uint64_t data, int len = 64, bool bs = false);


    /*****************************************************
     *   Receive mode to build a header from bits        *
     *****************************************************/

    /*!
     * Insert a new bit on the back of the input buffer. This
     * function is used in receive mode to add new bits as they are
     * received for later use of the extract_field functions.
     *
     * \param bit New bit to add.
     */
    void insert_bit(int bit);


    /*!
     * Returns a field in the packet header.
     *
     * \param pos Bit position of the start of the field.
     * \param len The number of bits in the field.
     * \param bs Set to 'true' to byte swap the data.
     * \param lsb_first Set to 'true' to read field encoded as least significant bit first
     */
    template <class T>
    T extract_field(int pos,
                    int len = 8 * sizeof(T),
                    bool bs = false,
                    bool lsb_first = false);

    /*!
     * Returns up to an 8-bit field in the packet header.
     *
     * \param pos Bit position of the start of the field.
     * \param len The number of bits in the field.
     * \param bs Set to 'true' to byte swap the data.
     * \param lsb_first Set to 'true' to read field encoded as least significant bit first
     */
    uint8_t extract_field8(int pos, int len = 8, bool bs = false, bool lsb_first = false)
    {
        return extract_field<uint8_t>(pos, len, bs, lsb_first);
    };
    // constexpr auto& extract_field8 = extract_field<uint8_t>;
    /*!
     * Returns up to a 16-bit field in the packet header.
     *
     * \param pos Bit position of the start of the field.
     * \param len The number of bits in the field.
     * \param bs Set to 'true' to byte swap the data.
     * \param lsb_first Set to 'true' to read field encoded as least significant bit first
     */
    uint16_t
    extract_field16(int pos, int len = 16, bool bs = false, bool lsb_first = false)
    {
        return extract_field<uint16_t>(pos, len, bs, lsb_first);
    };

    /*!
     * Returns up to a 32-bit field in the packet header.
     *
     * \param pos Bit position of the start of the field.
     * \param len The number of bits in the field.
     * \param bs Set to 'true' to byte swap the data.
     * \param lsb_first Set to 'true' to read field encoded as least significant bit first
     */
    uint32_t
    extract_field32(int pos, int len = 32, bool bs = false, bool lsb_first = false)
    {
        return extract_field<uint32_t>(pos, len, bs, lsb_first);
    };

    /*!
     * Returns up to a 64-bit field in the packet header.
     *
     * \param pos Bit position of the start of the field.
     * \param len The number of bits in the field.
     * \param bs Set to 'true' to byte swap the data.
     * \param lsb_first Set to 'true' to read field encoded as least significant bit first
     */
    uint64_t
    extract_field64(int pos, int len = 64, bool bs = false, bool lsb_first = false)
    {
        return extract_field<uint64_t>(pos, len, bs, lsb_first);
    };
};

} // namespace digital
} // namespace gr

#endif /* INCLUDED_DIGITAL_HEADER_BUFFER_H */
