/* -*- c++ -*- */
/* Copyright 2016 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * GNU Radio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 *
 * GNU Radio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GNU Radio; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#ifndef INCLUDED_DIGITAL_HEADER_FORMAT_BASE_H
#define INCLUDED_DIGITAL_HEADER_FORMAT_BASE_H

#include <gnuradio/digital/api.h>
#include <gnuradio/digital/header_buffer.h>
#include <gnuradio/logger.h>
#include <pmt/pmt.h>
#include <boost/enable_shared_from_this.hpp>

namespace gr {
namespace digital {

/*!
 * \brief Base header formatter class.
 * \ingroup packet_operators_blk
 *
 * \details
 *
 * Creates a base class that other packet formatters will inherit
 * from. The child classes create and parse protocol-specific
 * headers. To add a new protocol processing class, create a class
 * that inherits from this and overload the necessary
 * functions. The main functions to overload are:
 *
 * \li header_format_base::format: takes in a payload and
 * creates a header from it.
 *
 * \li header_format_base::parse: receive bits and extract
 * the header info. These are expected to be hard bits (0 or 1)
 * that have either been sliced or gone through an FEC decoder.
 *
 * \li header_format_base::header_nbits: the number of bits
 * in the full header (including an access code).
 *
 * \li header_format_base::header_ok: checks to see if the
 * received header is ok. Since the header often specifies the
 * length of the frame to decode next, it is important that this
 * information be correct.
 *
 * \li header_format_base::header_payload: unpacks the header
 * register (from the class header_buffer) as a set of bits into
 * its component parts of the header. For example, this may find
 * and extract the frame length field as a 16-bit value and/or
 * flags related to the type of modulation and FEC codes used in
 * the frame's payload.
 *
 * Protected functions of this class that the child class should
 * overload include:
 *
 * \li enter_search
 * \li enter_have_sync
 * \li enter_have_header
 *
 * These three function represent the different states of the
 * parsing state machine. Expected behavior is that the protocol
 * has some known word that we are first looking for the identify
 * the start of the frame. The parsing FSM starts in a state to
 * search for the beginning of the header, normally by looking for
 * a known word (i.e., the access code). Then it changes state to
 * read in the full header. We expect that the protocol provides
 * the length of the header for processing, so the parsing looks
 * pulls in the full length of the header. Then it changes state
 * to the "have header" state for checking and processing. The
 * base class provides the basic functionality for this state
 * machine. However, most likely, each child class must manage
 * these states for themselves.
 *
 * This class is specifically designed to work with packets/frames
 * in the asynchronous PDU architecture of GNU Radio. See the
 * packet_format_async block for formatting the headers onto
 * payloads and packet_parse_b block for parsing headers in a
 * receiver.
 *
 * The Packet Format block takes in a PDU and uses a formatter
 * class derived from this class to add a header onto the
 * packet. The Packet Format blocks takes in the PDU, unpacks the
 * message, and passes it to a formatter class' format function,
 * which builds a header based on the payload. The header is
 * passed back and emitted from formatter block as a separate
 * output. The async format block, packet_format_async, has two
 * message output ports. The 'header' port passes the header out
 * as a PDU and the 'payload' passes the payload out as a PDU. The
 * flowgraph can then separately modulate and combine these two
 * pieces in the follow-on processing.
 *
 * The packet_sync_b block uses the formatter class by calling the
 * 'parse' function to parse the received packet headers. This
 * parser block is a sink for the data stream and emits a message
 * from an 'info' port that contains an PMT dictionary of the
 * information in the header. The formatter class determines the
 * dictionary keys.
 *
 * This is the base class for dealing with formatting headers for
 * different protocols and purposes. For other header formatting
 * behaviors, create a child class from here and overload the
 * format, parse, and parsing state machine functions as
 * necessary.
 *
 * \sa header_format_default
 * \sa header_format_counter
 */
class DIGITAL_API header_format_base
    : public boost::enable_shared_from_this<gr::digital::header_format_base>
{
public:
    typedef boost::shared_ptr<header_format_base> sptr;

    header_format_base();
    virtual ~header_format_base();

    sptr base() { return shared_from_this(); };
    sptr formatter() { return shared_from_this(); };

    /*!
     * Function to creates a header. The child classes overload this
     * function to format the header in the protocol-specific way.
     *
     * \param nbytes_in The length (in bytes) of the \p input payload
     * \param input An array of unsigned chars of the packet payload
     * \param output A pmt::u8vector with the new header prepended
     *        onto the input data.
     * \param info A pmt::dict containing meta data and info about
     *        the PDU (generally from the metadata portion of the
     *        input PDU). Data can be extracted from this for the
     *        header formatting or inserted.
     *
     * MUST be overloaded.
     */
    virtual bool format(int nbytes_in,
                        const unsigned char* input,
                        pmt::pmt_t& output,
                        pmt::pmt_t& info) = 0;

    /*!
     * Parses a header. This function is overloaded in the child
     * class, which knows how to convert the incoming hard bits (0's
     * and 1's) back into a packet header.
     *
     * \param nbits_in The number of bits in the input array.
     * \param input The input as hard decision bits.
     * \param info A vector of pmt::dicts to hold any meta data or
     *        info about the PDU. When parsing the header, the
     *        formatter can add info from the header into this dict.
     *        Each packet has a single PMT dictionary of info, so
     *        the vector length is the number of packets received
     *        extracted during one call to this parser function.
     * \param nbits_processed Number of input bits actually
     *        processed; If all goes well, this is nbits_in. A
     *        premature return after a bad header could be less than
     *        this.
     *
     * MUST be overloaded.
     */
    virtual bool parse(int nbits_in,
                       const unsigned char* input,
                       std::vector<pmt::pmt_t>& info,
                       int& nbits_processed) = 0;

    /*!
     * Returns the length of the formatted header in bits.
     * MUST be overloaded.
     */
    virtual size_t header_nbits() const = 0;

    /*!
     * Returns the length of the formatted header in bytes.
     * Auto-calculated from the overloaded header_nbits().
     */
    size_t header_nbytes() const;

protected:
    enum state_t { STATE_SYNC_SEARCH, STATE_HAVE_SYNC };

    state_t d_state;         //!< state of the state machine
    header_buffer d_hdr_reg; //!< header_buffer object to hold header bits
    pmt::pmt_t d_info;       //!< info captured from the header

    //! Enter Search state of the state machine to find the access code.
    virtual void enter_search();

    //! Access code found, start getting the header
    virtual void enter_have_sync();

    //! Header found, setup for pulling in the hard decision bits
    virtual void enter_have_header(int payload_len);

    //! Verify that the header is valid
    virtual bool header_ok() = 0;

    /*! Get info from the header; return payload length and package
     *  rest of data in d_info dictionary.
     */
    virtual int header_payload() = 0;

    /*! Used by blocks to access the logger system.
     */
    gr::logger_ptr d_logger;
    gr::logger_ptr d_debug_logger;
};

} // namespace digital
} // namespace gr

#endif /* INCLUDED_DIGITAL_HEADER_FORMAT_BASE_H */
