/**
 * @file mac_data_req.c
 *
 * @brief Implements data request related functions
 *
 * This file implements generation, transmission, reception of data
 * request frames and transmits indirect data frame.
 *
 * $Id: mac_data_req.c 26610 2011-05-11 08:47:45Z sschneid $
 *
 * @author    Atmel Corporation: http://www.atmel.com
 * @author    Support email: avr@atmel.com
 */
/*
 * Copyright (c) 2009, Atmel Corporation All rights reserved.
 *
 * Licensed under Atmel's Limited License Agreement --> EULA.txt
 */

/* === Includes ============================================================ */

#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include "pal.h"
#include "return_val.h"
#include "bmm.h"
#include "qmm.h"
#include "tal.h"
#include "ieee_const.h"
#include "mac_msg_const.h"
#include "mac_api.h"
#include "mac_msg_types.h"
#include "mac_data_structures.h"
#include "stack_config.h"
#include "mac_internal.h"
#include "mac.h"
#include "mac_build_config.h"

#if (MAC_INDIRECT_DATA_BASIC == 1)

/* === Macros =============================================================== */

/*
 * Data request payload length
 */
#define DATA_REQ_PAYLOAD_LEN            (1)

/* === Globals ============================================================= */


/* === Prototypes ========================================================== */

#if (MAC_INDIRECT_DATA_FFD == 1)
static buffer_t *build_null_data_frame(void);
static uint8_t find_long_buffer(void *buf, void *long_addr);
static uint8_t find_short_buffer(void *buf, void *short_addr);
#endif  /*  (MAC_INDIRECT_DATA_FFD == 1)*/

/* === Implementation ====================================================== */

/**
 * @brief Build and transmits data request command frame
 *
 * This function builds and tranmits a data request command frame.
 *
 *
 * @param expl_poll Data request due to explicit MLME poll request
 * @param force_own_long_addr Forces the usage of the Extended Address as
 *                            Source Address. This a allows for implicitly
 *                            poll for pending data at the coordinator if
 *                            the Extended Address was used in the Beacon frame.
 * @param expl_dest_addr_mode Mode of subsequent destination address to be used
 *                            explicitly (0/2/3).
 *                            0: No explicit destination address attached,
 *                               use either macCoordShortAddress or
 *                               macCoordExtendedAddress
 *                            2: Use explicitly attached address in parameter
 *                               expl_dest_addr as destination address as
 *                               short address
 *                            3: Use explicitly attached address in parameter
 *                               expl_dest_addr as destination address as
 *                               extended address
 * @param expl_dest_addr Explicitly attached destination address for data
 *                       request frame. This is to be treated as either not
 *                       present, short or extended address, depending on
 *                       parameter expl_dest_addr_mode.
 * @param expl_dest_pan_id Explicitly attached destination PAN-Id (Coordinator
 *                         PAN-Id) for data request frame.
 *                         This is to be treated only as present, depending on
 *                         parameter expl_dest_addr_mode.
 *
 * @return True if data request command frame was created and sent to
 *         the TAL successfully, false otherwise.
 */
bool mac_build_and_tx_data_req(bool expl_poll,
                               bool force_own_long_addr,
                               uint8_t expl_dest_addr_mode,
                               address_field_t *expl_dest_addr,
                               uint16_t expl_dest_pan_id)
{
    retval_t tal_tx_status;
    bool intrabit = false;
    uint8_t frame_len;
    uint8_t *frame_ptr;
    uint16_t fcf;
    buffer_t *buf_ptr = bmm_buffer_alloc(LARGE_BUFFER_SIZE);

    if (NULL == buf_ptr)
    {
        return false;
    }

    frame_info_t *transmit_frame = (frame_info_t *)BMM_BUFFER_POINTER(buf_ptr);

    /*
     * If this data request cmd frame was initiated by a device due to implicit
     * poll, set msgtype to DATAREQUEST_IMPL_POLL.
     * If this data request cmd frame was initiated by a MLME poll request,
     * set msgtype to DATAREQUEST.
     */
    if (expl_poll)
    {
        transmit_frame->msg_type = DATAREQUEST;
    }
    else
    {
        transmit_frame->msg_type = DATAREQUEST_IMPL_POLL;
    }

    /*
     * The buffer header is stored as a part of frame_info_t structure before the
     * frame is given to the TAL. After the transmission of the frame, reuse
     * the buffer using this pointer.
     */
    transmit_frame->buffer_header = buf_ptr;

    /* Update the payload length. */
    frame_len = DATA_REQ_PAYLOAD_LEN +
                2 + // Add 2 octets for FCS
                2 + // 2 octets for short Source Address
                2 + // 2 octets for short Destination Address
                2 + // 2 octets for Destination PAN-Id
                3;  // 3 octets DSN and FCF

    /* Get the payload pointer. */
    frame_ptr = (uint8_t *)transmit_frame +
                LARGE_BUFFER_SIZE -
                DATA_REQ_PAYLOAD_LEN - 2;   /* Add 2 octets for FCS. */

    /*
     * Build the command frame id.
     * This is actually being written into "transmit_frame->layload[0]".
     */
    *frame_ptr = DATAREQUEST;

    /* Source Address */
    /*
     * Long address needs to be used if a short address is not present
     * or if we are forced to use the long address.
     *
     * This is used for example in cases where the coordinator indicates
     * pending data for us using our extended address.
     *
     * This is also used for transmitting a data request frame
     * during association, since here we always need to use our
     * extended address.
     */
    if ((BROADCAST == tal_pib_ShortAddress) ||
        (CCPU_ENDIAN_TO_LE16(MAC_NO_SHORT_ADDR_VALUE) == tal_pib_ShortAddress) ||
        force_own_long_addr)
    {
        frame_ptr -= 8;
        frame_len += 6; // Add further 6 octets for long Source Address

        /* Build the Source address. */
        convert_64_bit_to_byte_array(tal_pib_IeeeAddress, frame_ptr);
        fcf = FCF_SET_FRAMETYPE(FCF_FRAMETYPE_MAC_CMD) |
              FCF_SET_SOURCE_ADDR_MODE(FCF_LONG_ADDR) |
              FCF_ACK_REQUEST;
    }
    else
    {
        frame_ptr -= 2;

        /* Build the Source address. */
        convert_16_bit_to_byte_array(tal_pib_ShortAddress, frame_ptr);

        fcf = FCF_SET_FRAMETYPE(FCF_FRAMETYPE_MAC_CMD) |
              FCF_SET_SOURCE_ADDR_MODE(FCF_SHORT_ADDR) |
              FCF_ACK_REQUEST;
    }


    /* Source PAN-Id */
    /*
     * In IEEE 802.15.4 the PAN ID Compression bit may always be set.
     * See page 154:
     * If the data request command is being sent in response to the receipt
     * of a beacon frame indicating that data are pending for that device,
     * the Destination Addressing Mode subfield of the Frame Control field
     * may be set to zero ..."
     * In order to keep the implementation simple the address info is also in
     * this case 2 or 3, i.e. the destination address info is present.
     * This in return means that the PAN ID Compression bit is always set for
     * data request frames, except the expl_dest_pan_id parameter is different from
     * our own PAN-Id PIB attribute.
     */
     if ((expl_dest_addr_mode != FCF_NO_ADDR) &&
        (expl_dest_pan_id != tal_pib_PANId)
       )
     {
        frame_ptr -= 2;
        frame_len += 2;  // Add further 6 octets for long Source Pan-Id

        convert_16_bit_to_byte_array(tal_pib_PANId, frame_ptr);
     }
     else
     {
    /*
     * The source PAN Id is not present since the PAN ID
     * Compression bit is set.
     */
        /* Set intra-PAN bit. */
        intrabit = true;
        fcf |= FCF_PAN_ID_COMPRESSION;
    }


    /* Destination Address */
    if (FCF_SHORT_ADDR == expl_dest_addr_mode)
    {
        /* An explicit short destination address is requested. */
        fcf |= FCF_SET_DEST_ADDR_MODE(FCF_SHORT_ADDR);

        frame_ptr -= 2;
        convert_16_bit_to_byte_array(expl_dest_addr->short_address, frame_ptr);
    }
    else if (FCF_LONG_ADDR == expl_dest_addr_mode)
    {
        /* An explicit long destination address is requested. */
        fcf |= FCF_SET_DEST_ADDR_MODE(FCF_LONG_ADDR);

        frame_ptr -= 8;
        frame_len += 6; // Add further 6 octets for long Destination Address
        convert_64_bit_to_byte_array(expl_dest_addr->long_address, frame_ptr);
    }
    else
    {
        /* No explicit destination address is requested. */
        if (CCPU_ENDIAN_TO_LE16(MAC_NO_SHORT_ADDR_VALUE) != mac_pib_macCoordShortAddress)
        {
            /*
             * If current value of short address for coordinator PIB is
             * NOT 0xFFFE, the current value of the short address for
             * coordinator shall be used as desination address.
             */
            fcf |= FCF_SET_DEST_ADDR_MODE(FCF_SHORT_ADDR);

            frame_ptr -= 2;
            convert_16_bit_to_byte_array(mac_pib_macCoordShortAddress, frame_ptr);
        }
        else
        {
            /*
             * If current value of short address for coordinator PIB is 0xFFFE,
             * the current value of the extended address for coordinator
             * shall be used as desination address.
             */
            fcf |= FCF_SET_DEST_ADDR_MODE(FCF_LONG_ADDR);

            frame_ptr -= 8;
            frame_len += 6; // Add further 6 octets for long Destination Address
            convert_64_bit_to_byte_array(mac_pib_macCoordExtendedAddress, frame_ptr);
        }
    }


    /* Destination PAN-Id */
    frame_ptr -= 2;

    if (intrabit)
    {
        /* Add our PAN-Id. */
        convert_16_bit_to_byte_array(tal_pib_PANId, frame_ptr);
    }
    else
    {
    /*
         * There is an expclicit destination address present AND
         * the destination PAN-Id is different from our own PAN-ID,
         * so include the source PAN-id into the frame.
     */
        convert_16_bit_to_byte_array(expl_dest_pan_id, frame_ptr);
    }


    /* Set DSN. */
    frame_ptr--;
    *frame_ptr = mac_pib_macDSN++;


    /* Set the FCF. */
    frame_ptr -= 2;
    convert_spec_16_bit_to_byte_array(fcf, frame_ptr);


    /* First element shall be length of PHY frame. */
    frame_ptr--;
    *frame_ptr = frame_len;

    /* Finished building of frame. */
    transmit_frame->mpdu = frame_ptr;


    /* Transmission should be done with CSMA-CA and frame retries. */
#ifdef BEACON_SUPPORT
    /*
     * Now it gets tricky:
     * In Beacon network the frame is sent with slotted CSMA-CA only if:
     * 1) the node is associated, or
     * 2) the node is idle, but synced before association,
     * 3) the node is a Coordinator (we assume, that coordinators are always
     *    in sync with their parents).
     *
     * In all other cases, the frame has to be sent using unslotted CSMA-CA.
     */
    csma_mode_t cur_csma_mode;

    if (NON_BEACON_NWK != tal_pib_BeaconOrder)
    {
        if (
            ((MAC_IDLE == mac_state) && (MAC_SYNC_BEFORE_ASSOC == mac_sync_state)) ||
#if (MAC_START_REQUEST_CONFIRM == 1)
            (MAC_ASSOCIATED == mac_state) ||
            (MAC_COORDINATOR == mac_state)
#else
            (MAC_ASSOCIATED == mac_state)
#endif /* MAC_START_REQUEST_CONFIRM */
           )
        {
            cur_csma_mode = CSMA_SLOTTED;
        }
        else
        {
            cur_csma_mode = CSMA_UNSLOTTED;
        }
    }
    else
    {
        /* In Nonbeacon network the frame is sent with unslotted CSMA-CA. */
        cur_csma_mode = CSMA_UNSLOTTED;
    }

    tal_tx_status = tal_tx_frame(transmit_frame, cur_csma_mode, true);
#else   /* No BEACON_SUPPORT */
    /* In Nonbeacon build the frame is sent with unslotted CSMA-CA. */
    tal_tx_status = tal_tx_frame(transmit_frame, CSMA_UNSLOTTED, true);
#endif  /* BEACON_SUPPORT */

    if (MAC_SUCCESS == tal_tx_status)
    {
        MAKE_MAC_BUSY();
        return true;
    }
    else
    {
        /* TAL is busy, hence the data request could not be transmitted */
        bmm_buffer_free(buf_ptr);

        return false;
    }
} /* mac_build_and_tx_data_req() */



#if (MAC_INDIRECT_DATA_FFD == 1)
/*
 * @brief Constructs a null data frame
 *
 * @return Pointer to the created null data frame, NULL otherwise.
 */
static buffer_t *build_null_data_frame(void)
{
    bool use_long_addr_dest;
    frame_info_t *transmit_frame;
    uint8_t frame_len;
    uint8_t *frame_ptr;
    uint16_t fcf = 0;
    buffer_t *buf_ptr = bmm_buffer_alloc(LARGE_BUFFER_SIZE);

    if (NULL == buf_ptr)
    {
        return NULL;
    }

    transmit_frame = (frame_info_t *)BMM_BUFFER_POINTER(buf_ptr);

    /* No data payload, this is a null packet.*/
    transmit_frame->msg_type = NULL_FRAME;
    transmit_frame->buffer_header = buf_ptr;

    /* No indirect transmission. */
    transmit_frame->indirect_in_transit = false;

    /* Update the payload length. */
    frame_len = 2 +  // Add 2 octets for FCS
                2 +  // 2 octets for short Destination Address
                2 +  // 2 octets for Destination PAN-Id
                2 +  // 2 octets for short Source Address
                3;   // 3 octets DSN and FCF

    /* Get the payload pointer. */
    frame_ptr = (uint8_t *)transmit_frame + LARGE_BUFFER_SIZE - 2;  /* Add 2 octets for FCS. */

    /*
     * Set Source Address.
     */
    if ((BROADCAST == tal_pib_ShortAddress) ||
        (MAC_NO_SHORT_ADDR_VALUE == tal_pib_ShortAddress)
       )
    {
        /* Use long address as source address. */
        frame_ptr -= 8;
        frame_len += 6;
        convert_64_bit_to_byte_array(tal_pib_IeeeAddress, frame_ptr);
        fcf |= FCF_SET_SOURCE_ADDR_MODE(FCF_LONG_ADDR);
    }
    else
    {
        /* Use short address as source address. */
        frame_ptr -= 2;
        convert_16_bit_to_byte_array(tal_pib_ShortAddress, frame_ptr);
        fcf |= FCF_SET_SOURCE_ADDR_MODE(FCF_SHORT_ADDR);
    }

    /* Shall the Intra-PAN bit set? */
    if (tal_pib_PANId == mac_parse_data.src_panid)
    {
        /*
         * Both PAN-Ids are identical.
         * Set intra-PAN bit.
         */
        fcf |= FCF_PAN_ID_COMPRESSION;
    }
    else
    {
        /* Set Source PAN-Id. */
        frame_ptr -= 2;
        frame_len += 2;
        convert_16_bit_to_byte_array(tal_pib_PANId, frame_ptr);
    }


    /* Set Destination Address. */
    use_long_addr_dest = (FCF_LONG_ADDR == mac_parse_data.src_addr_mode);

    /* Destination address is set from source address of received frame. */
    if (use_long_addr_dest)
    {
        frame_ptr -= 8;
        frame_len += 6;
        convert_64_bit_to_byte_array(mac_parse_data.src_addr.long_address, frame_ptr);

        fcf |= FCF_SET_DEST_ADDR_MODE(FCF_LONG_ADDR) |
              FCF_SET_FRAMETYPE(FCF_FRAMETYPE_DATA);
    }
    else
    {
        frame_ptr -= 2;
        convert_16_bit_to_byte_array(mac_parse_data.src_addr.short_address, frame_ptr);
        fcf |= FCF_SET_DEST_ADDR_MODE(FCF_SHORT_ADDR) |
              FCF_SET_FRAMETYPE(FCF_FRAMETYPE_DATA);
    }


    /* Destination PANId is set from source PANId of received frame. */
    frame_ptr -= 2;
    convert_16_bit_to_byte_array(mac_parse_data.src_panid, frame_ptr);


    /* Set DSN. */
    frame_ptr--;
    *frame_ptr = mac_pib_macDSN++;


    /* Set the FCF. */
    frame_ptr -= 2;
    convert_spec_16_bit_to_byte_array(fcf, frame_ptr);


    /* First element shall be length of PHY frame. */
    frame_ptr--;
    *frame_ptr = frame_len;

    /* Finished building of frame. */
    transmit_frame->mpdu = frame_ptr;

    return buf_ptr;
} /* build_null_data_frame() */



/**
 * @brief Processes a received data request command frame
 *
 * This function processes a received data request command frame
 * at the coordinator, searches for pending indirect data frames
 * for the originator and initiates the frame transmission of the
 * data frame with CSMA-CA.
 *
 * @param msg Frame reception buffer pointer
 */
void mac_process_data_request(buffer_t *msg)
{
    /* Buffer pointer to next indirect data frame to be transmitted. */
    buffer_t *buf_ptr_next_data;
    search_t find_buf;
    frame_info_t *transmit_frame;
    retval_t tal_tx_status;

    /* Free the buffer of the received frame. */
    bmm_buffer_free(msg);

    /* Ignore data request if we are not PAN coordinator or coordinator. */
    if ((MAC_IDLE == mac_state) ||
        (MAC_ASSOCIATED == mac_state)
       )
    {
#if (DEBUG > 0)
        ASSERT("Neither PAN coordinator nor coordinator" == 0);
#endif
        return;
    }

    /* Check the addressing mode */
    if (mac_parse_data.src_addr_mode == FCF_SHORT_ADDR)
    {
        /*
         * Look for pending data in the indirect queue for
         * this short address.
         */

        /*
         * Assign the function pointer for searching the
         * data having address of the requested device.
         */
        find_buf.criteria_func = find_short_buffer;

        /* Update the short address to be searched. */
        find_buf.handle = &mac_parse_data.src_addr.short_address;
    }
    else if (mac_parse_data.src_addr_mode == FCF_LONG_ADDR)
    {
        /*
         * Look for pending data in the indirect queue for
         * this long address.
         */

        /*
         * Assign the function pointer for searching the
         * data having address of the requested device.
         */
        find_buf.criteria_func = find_long_buffer;

        /* Update the long address to be searched. */
        find_buf.handle = &mac_parse_data.src_addr.long_address;
    }
    else
    {
#if (DEBUG > 0)
            ASSERT("Unexpected addressing mode" == 0);
#endif
            return;
    }

    /*
     * Read from the indirect queue. The removal of items from this queue
     * will be done after successful transmission of the frame.
     */
    buf_ptr_next_data = qmm_queue_read(&indirect_data_q, &find_buf);
    /* Note: The find_buf structure is reused below, so do not change this. */

    if (NULL == buf_ptr_next_data)
    {
        mac_handle_tx_null_data_frame();
        return;
    }
    else
    {
        /* Indirect data found and to be sent. */
        transmit_frame = (frame_info_t *)BMM_BUFFER_POINTER(buf_ptr_next_data);

        /*
         * We need to check whether the source PAN-Id of the previously
         * received data request frame is identical to the destination PAN-Id
         * of the pending frame. If not the frame shall not be transmitted,
         * but a Null Data frame instead.
         */
        if (mac_parse_data.src_panid !=
            convert_byte_array_to_16_bit(&transmit_frame->mpdu[PL_POS_DST_PAN_ID_START])
           )
        {
            mac_handle_tx_null_data_frame();
            return;
        }
        else
        {
            /*
             * The frame to be transmitted next is marked.
             * This is necessary since the queue needs to be traversed again
             * to find other pending indirect data frames for this particular
             * recipient.
             */
            transmit_frame->indirect_in_transit = true;
            transmit_frame->buffer_header = buf_ptr_next_data;

            /*
             * Go through the indirect data queue to find out the frame pending for
             * the device which has requested for the data.
             */
            /*
             * Since the buffer header has already been stored in
             * transmit_frame->buffer_header, it can be reused here for
             * other purpose.
             */
            /*
             * It is assumed that the find_buf struct does still have
             * the original values from above.
             */
            buf_ptr_next_data = qmm_queue_read(&indirect_data_q, &find_buf);
            /*
             * Check whether there is another indirect data available
             * for the same recipient.
             */
            if (NULL != buf_ptr_next_data)
            {
                transmit_frame->mpdu[PL_POS_FCF_1] |= FCF_FRAME_PENDING;
            }
        }

        /*
         * Transmission should be done with CSMA-CA or
         * quickly after the ACK of the data request command.
         * Here it's done quickly after the ACK w/o CSMA.
         */
        tal_tx_status = tal_tx_frame(transmit_frame, NO_CSMA_WITH_IFS, false);

        if (MAC_SUCCESS == tal_tx_status)
        {
            MAKE_MAC_BUSY();
        }
        else
        {
            /*
             * TAL rejects frame transmission, since it's too close to the next
             * beacon transmission. The frame is kept in the indirect queue.
             */
            transmit_frame->indirect_in_transit = false;
        }
    }
} /* mac_process_data_request() */



/*
 * @brief Creates and transmits a Null Data frame
 *
 * This function creates and transmits a Null Data frame in case the
 * coordinator does not have pending data to be transmitted.
 */
void mac_handle_tx_null_data_frame(void)
{
    frame_info_t *tx_frame;
    retval_t tal_tx_status;
    buffer_t *buf_ptr;

    /*
     * No matching pending item in the queue,
     * so a Null Data frame is created.
     */
    buf_ptr = build_null_data_frame();

    if (NULL != buf_ptr)
    {
        tx_frame = (frame_info_t *)BMM_BUFFER_POINTER(buf_ptr);

        /*
         * Transmission should be done with CSMA-CA or
         * quickly after the ACK of the data request command.
         * Here it's done quickly after the ACK w/o CSMA.
         */
        tal_tx_status = tal_tx_frame(tx_frame, NO_CSMA_WITH_IFS, false);

        if (MAC_SUCCESS == tal_tx_status)
        {
            MAKE_MAC_BUSY();
        }
        else
        {
            /*
             * Transmission to TAL failed, free up the buffer used to create
             * Null Data frame.
             */
            bmm_buffer_free(buf_ptr);
        }
    }
    }



/*
 * @brief Checks for matching short address
 *
 * This callback function checks whether the passed short address
 * matches with the frame in the queue.
 *
 * @param buf Pointer to indirect data buffer
 * @param short_addr Short address to be searched
 *
 * @return 1 if short address passed matches with the destination
 * address of the indirect frame , 0 otherwise
 */
static uint8_t find_short_buffer(void *buf, void *short_addr)
{
    frame_info_t *data = (frame_info_t *)buf;

    if (!data->indirect_in_transit)
    {
        /*
         * Read octet 2 of Frame Control Field containing the type
         * of destination address.
         */
        uint8_t dst_addr_mode = (data->mpdu[PL_POS_FCF_2] >> FCF_2_DEST_ADDR_OFFSET) & FCF_ADDR_MASK;

        /*
         * Compare indirect data frame's dest_address(short)
         * with short address passed.
         */
        if ((dst_addr_mode == FCF_SHORT_ADDR) &&
            (*(uint16_t *)short_addr == convert_byte_array_to_16_bit(&data->mpdu[PL_POS_DST_ADDR_START]))
           )
        {
            return 1;
        }
    }

    return 0;
}



/*
 * @brief Checks for matching extended address
 *
 * This callback function checks whether the passed short address
 * matches with the frame in the queue.
 *
 * @param buf Pointer to indirect data buffer
 * @param long_addr Extended address to be searched
 *
 * @return 1 if extended address passed matches with the destination
 * address of the indirect frame , 0 otherwise
 */
static uint8_t find_long_buffer(void *buf, void *long_addr)
{
    frame_info_t *data = (frame_info_t *)buf;

    if (!data->indirect_in_transit)
    {
        /*
         * Read octet 2 of Frame Control Field containing the type
         * of destination address.
         */
        uint8_t dst_addr_mode = (data->mpdu[PL_POS_FCF_2] >> FCF_2_DEST_ADDR_OFFSET) & FCF_ADDR_MASK;

        /*
         * Compare indirect data frame's dest_address(extended)
         * with the exended address passed.
         */
    if ((dst_addr_mode == FCF_LONG_ADDR) &&
        (*(uint64_t *)long_addr == convert_byte_array_to_64_bit(&data->mpdu[PL_POS_DST_ADDR_START]))
       )
        {
            return 1;
        }
    }

    return 0;
}
#endif  /* (MAC_INDIRECT_DATA_FFD == 1) */
#endif  /* (MAC_INDIRECT_DATA_BASIC == 1) */

/* EOF */
