/*
 *  Copyright (c) 2019, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file
 *   This file includes definitions for Thread Radio Encapsulation Link (TREL) Packet
 */

#ifndef TREL_PACKET_HPP_
#define TREL_PACKET_HPP_

#include "openthread-core-config.h"

#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE

#include "common/data.hpp"
#include "common/encoding.hpp"
#include "common/locator.hpp"
#include "common/string.hpp"
#include "mac/mac_types.hpp"

namespace ot {
namespace Trel {

/**
 * This class represents a TREL radio link packet encapsulation header.
 *
 */
OT_TOOL_PACKED_BEGIN
class Header
{
public:
    /**
     * This enumeration defines packet types.
     *
     */
    enum Type : uint8_t
    {
        kTypeBroadcast = 0, ///< A TREL broadcast packet.
        kTypeUnicast   = 1, ///< A TREL unicast packet.
        kTypeAck       = 2, ///< A TREL Ack packet.
    };

    /**
     * This enumeration represents Ack Mode field in TREL header.
     *
     */
    enum AckMode : uint8_t
    {
        kNoAck,        ///< No TREL Ack is requested.
        kAckRequested, ///< A TREL Ack is requested.
    };

    static constexpr uint16_t kInfoStringSize = 128; ///< Max chars for the info string (@sa ToInfoString()).

    /**
     * This type defines the fixed-length `String` object returned from `ToString()` method.
     *
     */
    typedef String<kInfoStringSize> InfoString;

    /**
     * This method initializes the header.
     *
     * @param[in] aType     The header type.
     *
     */
    void Init(Type aType) { mControl = aType + kVersion; }

    /**
     * This method checks whether the version field in header is valid or not
     *
     * @returns TRUE if the version field is valid, FALSE otherwise.
     *
     */
    bool IsVersionValid(void) const { return (mControl & kVersionMask) == kVersion; }

    /**
     * This method gets the packet type.
     *
     * @returns The packet type.
     *
     */
    Type GetType(void) const { return static_cast<Type>(mControl & kTypeMask); }

    /**
     * This method gets the header length based on its type.
     *
     * @returns the header length (number of bytes).
     *
     */
    uint16_t GetLength(void) const { return GetSize(GetType()); }

    /**
     * This method gets the Ack Mode field from the header.
     *
     * @returns The Ack Mode field.
     *
     */
    AckMode GetAckMode(void) const { return (mControl & kAckModeFlag) ? kAckRequested : kNoAck; }

    /**
     * This method sets the Ack Mode field in the header.
     *
     * @param[in] aAckMode  The Ack Mode field
     *
     */
    void SetAckMode(AckMode aAckMode);

    /**
     * This method gets the channel field from the header.
     *
     * @returns The channel field.
     *
     */
    uint8_t GetChannel(void) const { return mChannel; }

    /**
     * This method sets the channel field in the header.
     *
     * @param[in] aChannel   A channel.
     *
     */
    void SetChannel(uint8_t aChannel) { mChannel = aChannel; }

    /**
     * This method gets the PAN Identifier field from the header.
     *
     * @returns The PAN Identifier field.
     *
     */
    Mac::PanId GetPanId(void) const { return Encoding::BigEndian::HostSwap16(mPanId); }

    /**
     * This method sets the PAN Identifier field in the header.
     *
     * @param[in] aPanId   A PAN Identifier.
     *
     */
    void SetPanId(Mac::PanId aPanId) { mPanId = Encoding::BigEndian::HostSwap16(aPanId); }

    /**
     * This method gets the packet number field from the header.
     *
     * @returns The packet number field.
     *
     */
    uint32_t GetPacketNumber(void) const { return Encoding::BigEndian::HostSwap32(mPacketNumber); }

    /**
     * This method sets the packet number field in the header.
     *
     * @param[in] aPacketNumber  The packet number.
     *
     */
    void SetPacketNumber(uint32_t aPacketNumber) { mPacketNumber = Encoding::BigEndian::HostSwap32(aPacketNumber); }

    /**
     * This method gets the source MAC address field from the header.
     *
     * @returns The source MAC address field.
     *
     */
    const Mac::ExtAddress &GetSource(void) const { return mSource; }

    /**
     * This method sets the source MAC address filed in the header.
     *
     * @param[in] aSource   A MAC extended address to set as source.
     *
     */
    void SetSource(const Mac::ExtAddress &aSource) { mSource = aSource; }

    /**
     * This method gets the destination MAC address field from the header.
     *
     * This method MUST be used with a unicast of ack type packet, otherwise its behavior is undefined.
     *
     * @returns The destination MAC address field.
     *
     */
    const Mac::ExtAddress &GetDestination(void) const { return mDestination; }

    /**
     * This method sets the destination MAC address field in the header.
     *
     * This method MUST be used with a unicast of ack type packet, otherwise its behavior is undefined.
     *
     * @param[in] aDest   A MAC extended address to set as destination.
     *
     */
    void SetDestination(const Mac::ExtAddress &aDest) { mDestination = aDest; }

    /**
     * This static method gets the size (number of bytes) in header of given packet type.
     *
     * @param[in] aType   The packet type.
     *
     * @returns The fixed header size (number of bytes) for @p aType packet.
     *
     */
    static uint16_t GetSize(Type aType);

    /**
     * This method returns a string representation of header.
     *
     * @returns An `InfoString` representation of header.
     *
     */
    InfoString ToString(void) const;

private:
    static constexpr uint8_t kTypeMask    = (3 << 0); // Bits 0-1 specify packet `Type`
    static constexpr uint8_t kAckModeFlag = (1 << 2); // Bit 2 indicate "ack mode" (TREL ack requested or not).
    static constexpr uint8_t kVersionMask = (7 << 5); // Bits 5-7 specify the version.
    static constexpr uint8_t kVersion     = (0 << 5); // Current TREL header version.

    // All header fields are big-endian.

    uint8_t         mControl;
    uint8_t         mChannel;
    uint16_t        mPanId;
    uint32_t        mPacketNumber;
    Mac::ExtAddress mSource;
    Mac::ExtAddress mDestination; // Present in `kTypeAck` or `kTypeUnicast` packet types.
} OT_TOOL_PACKED_END;

/**
 * This class represents a TREL radio link packet.
 *
 */
class Packet : private MutableData<kWithUint16Length>
{
    using Base = MutableData<kWithUint16Length>;

public:
    /**
     * This method initializes the `Packet` with a given buffer and length.
     *
     * @param[in] aBuffer  A pointer to a buffer containing the entire packet (header and payload).
     * @param[in] aLength  Length (number of bytes) of the packet (including header and payload).
     *
     */
    void Init(uint8_t *aBuffer, uint16_t aLength) { Base::Init(aBuffer, aLength); }

    /**
     * This method initializes the `Packet` with a specified header type and given a payload.
     *
     * The payload buffer @p aPayload should have space reserved before the start of payload for the packet header.
     * This method will initialize the header with the given type @p aType. Rest of header fields can be updated after
     * initializing the packet.
     *
     * @param[in] aType          The packet type.
     * @param[in] aPayload       A pointer to a buffer containing the packet payload. Buffer should have space reserved
     *                           for header before the payload.
     * @param[in] aPayloadLength The length (number of bytes) in the payload only (not including the header).
     *
     */
    void Init(Header::Type aType, uint8_t *aPayload, uint16_t aPayloadLength);

    /**
     * This method gets a pointer to buffer containing the packet.
     *
     * @returns A pointer to buffer containing the packet.
     *
     */
    uint8_t *GetBuffer(void) { return Base::GetBytes(); }

    /**
     * This method gets a pointer to buffer containing the packet.
     *
     * @returns A pointer to buffer containing the packet.
     *
     */
    const uint8_t *GetBuffer(void) const { return Base::GetBytes(); }

    /**
     * This method gets the length of packet.
     *
     * @returns The length (number of bytes) of packet (header and payload).
     *
     */
    uint16_t GetLength(void) const { return Base::GetLength(); }

    /**
     * This method checks whether or not the packet header is valid.
     *
     * @retval TRUE   The packet header is valid and well-formed.
     * @retval FALSE  The packet header is not valid.
     *
     */
    bool IsHeaderValid(void) const;

    /**
     * This method gets the packet header.
     *
     * @returns A reference to the packet header as `Header`.
     *
     */
    Header &GetHeader(void) { return *reinterpret_cast<Header *>(Base::GetBytes()); }

    /**
     * This method gets the packet header.
     *
     * @returns A reference to the packet header as `Header`.
     *
     */
    const Header &GetHeader(void) const { return *reinterpret_cast<const Header *>(Base::GetBytes()); }

    /**
     * This method gets a pointer to start of packet payload.
     *
     * @returns A pointer to start of packet payload (after header).
     *
     */
    uint8_t *GetPayload(void) { return Base::GetBytes() + GetHeader().GetLength(); }

    /**
     * This method gets a pointer to start of packet payload.
     *
     * @returns A pointer to start of packet payload (after header).
     *
     */
    const uint8_t *GetPayload(void) const { return Base::GetBytes() + GetHeader().GetLength(); }

    /**
     * This method gets the payload length.
     *
     * @returns The packet payload length (number of bytes).
     *
     */
    uint16_t GetPayloadLength(void) const { return GetLength() - GetHeader().GetLength(); }
};

} // namespace Trel
} // namespace ot

#endif // #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE

#endif // TREL_PACKET_HPP_
