#!/usr/bin/env python3
#
# Copyright 2010-2013,2015 Ettus Research LLC
# Copyright 2018 Ettus Research, a National Instruments Company
#
# SPDX-License-Identifier: GPL-3.0-or-later
#

"""
The vrt packer/unpacker code generator:

This script will generate the pack and unpack routines that convert
metatdata into vrt headers and vrt headers into metadata.

The generated code infers jump tables to speed-up the parsing time.
"""

TMPL_TEXT = """<% import time %>
/***********************************************************************
 * This file was generated by ${file} on ${time.strftime("%c")}
 **********************************************************************/

#include <uhd/exception.hpp>
#include <uhd/transport/vrt_if_packet.hpp>
#include <uhd/utils/byteswap.hpp>
#include <uhd/types/endianness.hpp>
#include <vector>

//define the endian macros to convert integers
#ifdef UHD_BIG_ENDIAN
    #define BE_MACRO(x) (x)
    #define LE_MACRO(x) uhd::byteswap(x)
#else
    #define BE_MACRO(x) uhd::byteswap(x)
    #define LE_MACRO(x) (x)
#endif

using namespace uhd;
using namespace uhd::transport;
using namespace uhd::transport::vrt;

typedef size_t pred_type;
typedef std::vector<pred_type> pred_table_type;
#define pred_table_index(hdr) ((hdr >> 20) & 0x1ff)

static pred_table_type get_pred_unpack_table(void)
{
    pred_table_type table(1 << 9, 0); //only 9 bits useful here (20-28)
    for (size_t i = 0; i < table.size(); i++){
        uint32_t vrt_hdr_word = i << 20;
        if(vrt_hdr_word & ${hex(0x1 << 28)}) table[i] |= ${hex(sid_p)};
        if(vrt_hdr_word & ${hex(0x1 << 27)}) table[i] |= ${hex(cid_p)};
        if(vrt_hdr_word & ${hex(0x3 << 22)}) table[i] |= ${hex(tsi_p)};
        if(vrt_hdr_word & ${hex(0x3 << 20)}) table[i] |= ${hex(tsf_p)};
        if(vrt_hdr_word & ${hex(0x1 << 26)}) table[i] |= ${hex(tlr_p)};
        if(vrt_hdr_word & ${hex(0x1 << 24)}) table[i] |= ${hex(eob_p)};
        if(vrt_hdr_word & ${hex(0x1 << 25)}) table[i] |= ${hex(sob_p)};
    }
    return table;
}

static const pred_table_type pred_unpack_table(get_pred_unpack_table());

//maps trailer bits to num empty bytes
//maps num empty bytes to trailer bits
static const uint32_t occ_table[] = {0, 2, 1, 3};

const uint32_t VRLP = ('V' << 24) | ('R' << 16) | ('L' << 8) | ('P' << 0);
const uint32_t VEND = ('V' << 24) | ('E' << 16) | ('N' << 8) | ('D' << 0);

UHD_INLINE static uint32_t chdr_to_vrt(const uint32_t chdr, if_packet_info_t &info)
{
    const uint32_t bytes = chdr & 0xffff;
    uint32_t vrt = (bytes + 3)/4;
    info.packet_count = (chdr >> 16) & 0xfff;
    vrt |= ((chdr >> 31) & 0x1) << 30; //context packet
    vrt |= ((chdr >> 29) & 0x1) << 20; //has tsf
    vrt |= ((chdr >> 28) & 0x1) << 24; //has eob
    vrt |= (0x1) << 28; //has sid (always)
    return vrt;
}

UHD_INLINE static uint32_t vrt_to_chdr(const uint32_t vrt, const if_packet_info_t &info)
{
    const uint32_t words32 = vrt & 0xffff;
    int bytes_rem = info.num_payload_bytes % 4;
    if (bytes_rem != 0) bytes_rem -= 4; //adjust for round up
    uint32_t chdr = (words32 * 4) + bytes_rem;
    chdr |= (info.packet_count & 0xfff) << 16;
    chdr |= ((vrt >> 30) & 0x1) << 31; //context packet
    chdr |= ((vrt >> 20) & 0x1) << 29; //has tsf
    chdr |= ((vrt >> 24) & 0x1) << 28; //has eob
    return chdr;
}

########################################################################
<%def name="gen_code(XE_MACRO, suffix)">
########################################################################
/***********************************************************************
 * internal impl of packing VRT IF header only
 **********************************************************************/
UHD_INLINE void __if_hdr_pack_${suffix}(
    uint32_t *packet_buff,
    if_packet_info_t &if_packet_info,
    uint32_t &vrt_hdr_word32
){
    uint32_t vrt_hdr_flags = 0;

    pred_type pred = 0;
    if (if_packet_info.has_sid) pred |= ${hex(sid_p)};
    if (if_packet_info.has_cid) pred |= ${hex(cid_p)};
    if (if_packet_info.has_tsi) pred |= ${hex(tsi_p)};
    if (if_packet_info.has_tsf) pred |= ${hex(tsf_p)};
    if (if_packet_info.has_tlr) pred |= ${hex(tlr_p)};
    if (if_packet_info.eob)     pred |= ${hex(eob_p)};
    if (if_packet_info.sob)     pred |= ${hex(sob_p)};

    switch(pred){
    % for pred in range(2**7):
    case ${pred}:<% num_header_words = 1 %><% flags = 0 %>
        ########## Stream ID ##########
        % if pred & sid_p:
            packet_buff[${num_header_words}] = ${XE_MACRO}(if_packet_info.sid);<% num_header_words += 1 %><% flags |= (0x1 << 28) %>
        % endif
        ########## Class ID ##########
        % if pred & cid_p:
            packet_buff[${num_header_words}] = 0; //not implemented<% num_header_words += 1 %>
            packet_buff[${num_header_words}] = 0; //not implemented<% num_header_words += 1 %><% flags |= (0x1 << 27) %>
        % endif
        ########## Integer Time ##########
        % if pred & tsi_p:
            packet_buff[${num_header_words}] = ${XE_MACRO}(if_packet_info.tsi);<% num_header_words += 1 %><% flags |= (0x3 << 22) %>
        % endif
        ########## Fractional Time ##########
        % if pred & tsf_p:
            packet_buff[${num_header_words}] = ${XE_MACRO}(uint32_t(if_packet_info.tsf >> 32));<% num_header_words += 1 %>
            packet_buff[${num_header_words}] = ${XE_MACRO}(uint32_t(if_packet_info.tsf >> 0));<% num_header_words += 1 %><% flags |= (0x1 << 20) %>
        % endif
        ########## Burst Flags ##########
<% if pred & eob_p: flags |= (0x1 << 24) %><% if pred & sob_p: flags |= (0x1 << 25) %>
        ########## Trailer ##########
        % if pred & tlr_p:
            {
                const size_t empty_bytes = if_packet_info.num_payload_words32*sizeof(uint32_t) - if_packet_info.num_payload_bytes;
                if_packet_info.tlr = (0x3 << 22) | (occ_table[empty_bytes & 0x3] << 10);
            }
            packet_buff[${num_header_words}+if_packet_info.num_payload_words32] = ${XE_MACRO}(if_packet_info.tlr);<% flags |= (0x1 << 26) %><% num_trailer_words = 1 %>
        % else:
<% num_trailer_words = 0 %>
        % endif
        ########## Variables ##########
            if_packet_info.num_header_words32 = ${num_header_words};
            if_packet_info.num_packet_words32 = ${num_header_words + num_trailer_words} + if_packet_info.num_payload_words32;
            vrt_hdr_flags = ${hex(flags)};
        break;
    % endfor
    }

    //fill in complete header word
    vrt_hdr_word32 = uint32_t(0
        | (if_packet_info.packet_type << 29)
        | vrt_hdr_flags
        | ((if_packet_info.packet_count & 0xf) << 16)
        | (if_packet_info.num_packet_words32 & 0xffff)
    );
}

/***********************************************************************
 * internal impl of unpacking VRT IF header only
 **********************************************************************/
UHD_INLINE void __if_hdr_unpack_${suffix}(
    const uint32_t *packet_buff,
    if_packet_info_t &if_packet_info,
    const uint32_t vrt_hdr_word32
){
    const size_t packet_words32 = vrt_hdr_word32 & 0xffff;

    //failure case
    if (if_packet_info.num_packet_words32 < packet_words32)
        throw uhd::value_error("bad vrt header or packet fragment");

    //extract fields from the header
    if_packet_info.packet_type = if_packet_info_t::packet_type_t(vrt_hdr_word32 >> 29);
    if_packet_info.packet_count = (vrt_hdr_word32 >> 16) & 0xf;

    const pred_type pred = pred_unpack_table[pred_table_index(vrt_hdr_word32)];

    size_t empty_bytes = 0;

    switch(pred){
    % for pred in range(2**7):
    case ${pred}:<% has_time_spec = False %><% num_header_words = 1 %>
        ########## Stream ID ##########
        % if pred & sid_p:
            if_packet_info.has_sid = true;
            if_packet_info.sid = ${XE_MACRO}(packet_buff[${num_header_words}]);<% num_header_words += 1 %>
        % else:
            if_packet_info.has_sid = false;
        % endif
        ########## Class ID ##########
        % if pred & cid_p:
            if_packet_info.has_cid = true;
            if_packet_info.cid = 0; //not implemented<% num_header_words += 2 %>
        % else:
            if_packet_info.has_cid = false;
        % endif
        ########## Integer Time ##########
        % if pred & tsi_p:
            if_packet_info.has_tsi = true;
            if_packet_info.tsi = ${XE_MACRO}(packet_buff[${num_header_words}]);
<% num_header_words += 1 %>
        % else:
            if_packet_info.has_tsi = false;
        % endif
        ########## Fractional Time ##########
        % if pred & tsf_p:
            if_packet_info.has_tsf = true;
            if_packet_info.tsf = uint64_t(${XE_MACRO}(packet_buff[${num_header_words}])) << 32;<% num_header_words += 1 %>
            if_packet_info.tsf |= ${XE_MACRO}(packet_buff[${num_header_words}]);<% num_header_words += 1 %>
        % else:
            if_packet_info.has_tsf = false;
        % endif
        ########## Burst Flags ##########
        % if pred & eob_p:
            if_packet_info.eob = true;
        % else:
            if_packet_info.eob = false;
        % endif
        % if pred & sob_p:
            if_packet_info.sob = true;
        % else:
            if_packet_info.sob = false;
        % endif
        ########## Trailer ##########
        % if pred & tlr_p:
            if_packet_info.has_tlr = true;
            if_packet_info.tlr = ${XE_MACRO}(packet_buff[packet_words32-1]);<% num_trailer_words = 1 %>
            {
                const int indicators = (if_packet_info.tlr >> 20) & (if_packet_info.tlr >> 8);
                if ((indicators & (1 << 0)) != 0) if_packet_info.eob = true;
                if ((indicators & (1 << 1)) != 0) if_packet_info.sob = true;
                empty_bytes = occ_table[(indicators >> 2) & 0x3];
            }
        % else:
            if_packet_info.has_tlr = false;<% num_trailer_words = 0 %>
        % endif
        ########## Variables ##########
            //another failure case
            if (packet_words32 < ${num_header_words + num_trailer_words})
                throw uhd::value_error("bad vrt header or invalid packet length");
            if_packet_info.num_header_words32 = ${num_header_words};
            if_packet_info.num_payload_words32 = packet_words32 - ${num_header_words + num_trailer_words};
            if_packet_info.num_payload_bytes = if_packet_info.num_payload_words32*sizeof(uint32_t) - empty_bytes;
        break;
    % endfor
    }
}

/***********************************************************************
 * link layer + VRT IF packing
 **********************************************************************/
void vrt::if_hdr_pack_${suffix}(
    uint32_t *packet_buff,
    if_packet_info_t &if_packet_info
){
    uint32_t vrt_hdr_word32 = 0;
    switch (if_packet_info.link_type)
    {
    case if_packet_info_t::LINK_TYPE_NONE:
        __if_hdr_pack_${suffix}(packet_buff, if_packet_info, vrt_hdr_word32);
        packet_buff[0] = ${XE_MACRO}(vrt_hdr_word32);
        break;

    case if_packet_info_t::LINK_TYPE_CHDR:
    {
        __if_hdr_pack_${suffix}(packet_buff, if_packet_info, vrt_hdr_word32);
        const uint32_t chdr = vrt_to_chdr(vrt_hdr_word32, if_packet_info);
        packet_buff[0] = ${XE_MACRO}(chdr);
        break;
    }

    case if_packet_info_t::LINK_TYPE_VRLP:
        __if_hdr_pack_${suffix}(packet_buff+2, if_packet_info, vrt_hdr_word32);
        if_packet_info.num_header_words32 += 2;
        if_packet_info.num_packet_words32 += 3;
        packet_buff[0] = ${XE_MACRO}(VRLP);
        packet_buff[1] = ${XE_MACRO}(uint32_t(
            (if_packet_info.num_packet_words32 & 0xfffff) |
            ((if_packet_info.packet_count & 0xfff) << 20)
        ));
        packet_buff[2] = ${XE_MACRO}(vrt_hdr_word32);
        packet_buff[if_packet_info.num_packet_words32-1] = ${XE_MACRO}(VEND);
        break;
    }
}

/***********************************************************************
 * link layer + VRT IF unpacking
 **********************************************************************/
void vrt::if_hdr_unpack_${suffix}(
    const uint32_t *packet_buff,
    if_packet_info_t &if_packet_info
){
    uint32_t vrt_hdr_word32 = 0;
    switch (if_packet_info.link_type)
    {
    case if_packet_info_t::LINK_TYPE_NONE:
        vrt_hdr_word32 = ${XE_MACRO}(packet_buff[0]);
        __if_hdr_unpack_${suffix}(packet_buff, if_packet_info, vrt_hdr_word32);
        break;

    case if_packet_info_t::LINK_TYPE_CHDR:
    {
        const uint32_t chdr = ${XE_MACRO}(packet_buff[0]);
        vrt_hdr_word32 = chdr_to_vrt(chdr, if_packet_info);
        size_t packet_count = if_packet_info.packet_count;
        __if_hdr_unpack_${suffix}(packet_buff, if_packet_info, vrt_hdr_word32);
        if_packet_info.num_payload_bytes -= (~chdr + 1) & 0x3;
        if_packet_info.packet_count = packet_count;
        break;
    }

    case if_packet_info_t::LINK_TYPE_VRLP:
    {
        if (${XE_MACRO}(packet_buff[0]) != VRLP) throw uhd::value_error("bad vrl header VRLP");
        const uint32_t vrl_hdr = ${XE_MACRO}(packet_buff[1]);
        vrt_hdr_word32 = ${XE_MACRO}(packet_buff[2]);
        if (if_packet_info.num_packet_words32 < (vrl_hdr & 0xfffff)) throw uhd::value_error("bad vrl header or packet fragment");
        if (${XE_MACRO}(packet_buff[(vrl_hdr & 0xfffff)-1]) != VEND) throw uhd::value_error("bad vrl trailer VEND");
        __if_hdr_unpack_${suffix}(packet_buff+2, if_packet_info, vrt_hdr_word32);
        if_packet_info.num_header_words32 += 2; //add vrl header
        if_packet_info.packet_count = (vrl_hdr >> 20) & 0xfff;
        break;
    }
    }
}

########################################################################
</%def>
########################################################################

${gen_code("BE_MACRO", "be")}
${gen_code("LE_MACRO", "le")}
"""

def parse_tmpl(_tmpl_text, **kwargs):
    from mako.template import Template
    return Template(_tmpl_text).render(**kwargs)

if __name__ == '__main__':
    import sys

    with open(sys.argv[1], 'w') as f:
        f.write(parse_tmpl(
            TMPL_TEXT,
            file=__file__,
            sid_p = 0b0000001,
            cid_p = 0b0000010,
            tsi_p = 0b0000100,
            tsf_p = 0b0001000,
            tlr_p = 0b0010000,
            sob_p = 0b0100000,
            eob_p = 0b1000000,
        ))
