/*
 * Copyright (c) 2002-2008 Endace Technology Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Technology Limited and no part
 * of it may be redistributed, published or disclosed except as outlined in
 * the written contract supplied with this product.
 *
 * $Id: ib_filter_tests.c 15597 2012-03-28 22:21:32Z jomi.gregory $
 */

/* Documentation notes: 
 * --------------------
 *
 * This file contains the implemetation to test  infiniband filtering.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "../../../include/dagcrc.h"
#include "../../../include/dagclarg.h"
#include "../../../include/dag_component.h"
#include "../../../include/dag_component_codes.h"
#include "../../../include/dagapi.h"
#include "../../../include/dagerf.h"
#include "../../../include/dag_config.h"
#include "ib_filter_tests.h"

/* infiniband  filter rule parsing related headers */
#include "dagcam/infiniband_proto.h"
#include "../../../lib/dagcam/infini_parse.h"

/* Some constants */
#define MAX_RULES 15
#define FILENAME_BYTES 256
static char uInfileBuf[FILENAME_BYTES] = "";
static uint64_t zero_64bit = 0;
/* opcode tells  presense of deth in the packet.  Here if the array value  is -1 no deth, or else it stores the offset frm the start of extension headers*/
static int8_t opcode_to_deth_offset[256];
/* function to initialize the array */
static void init_deth_offset()
{
    uint8_t i;
    memset(opcode_to_deth_offset, -1,256);

    /* Fill DETH offsets for RD Not all RDs hv DETH */
    /* from 010 0000 to 0x010 01100  has DETH*/
    for(i = 0x40; i < 0x4d;i++)
    {
        opcode_to_deth_offset[i] = 4;
    }
    for(i = 0x53; i < 0x56 ;i++)
    {
        opcode_to_deth_offset[i] = 4;
    }
    /*Fill DETH offsets for UD */
    
    opcode_to_deth_offset[0x64] = 0;
    opcode_to_deth_offset[0x65] = 0;
}

/*temp debg */
#define DBG_RULE_PARSE 

/* Command Line Arguments constants */
enum
{
	CLA_TESTCASE,
	CLA_TESTNUM,
	CLA_INPUT_FILE
};

typedef union temp_lrh
{
    ib_lrh_t lrh;
    uint64_t word;
}temp_lrh_t;
typedef union temp_bth
{
    ib_bth_t bth;
    uint64_t word;
}temp_bth_t;
typedef union temp_deth
{
    ib_ext_deth_t deth;
    uint64_t word;
}temp_deth_t;


/* Structure to hold the information required by the tests */
typedef struct ib_filter_fields
{
    uint8_t  ifc;
    uint8_t  rectype;
    uint16_t rlen;
    uint16_t wlen;
#if 0
    uint32_t tag; /* or color*/
    uint8_t match;
    uint8_t f;
    uint8_t class; /*steering */
    uint8_t  stream;
#endif 
    uint32_t result_word;
    /*uint16_t slid;
    uint16_t dlid;
    uint8_t service_level;
    uint8_t lnh;*/
    uint64_t *lrh_ptr;
    /*uint8_t opcode;
    uint32_t dqp;
    */
    uint64_t *bth_ptr;
    /*uint32_t sqp;
    */
    uint64_t *deth_ptr;
} ib_filter_fields_t;



/* internal representation of the filter rules */
typedef struct ib_filter_rules
{
    /* color info has steering (2 bits ) and user tag (16 bits) */
    uint32_t color_info;

    temp_lrh_t lrh_data;
    temp_lrh_t lrh_mask;
    temp_bth_t bth_data;
    temp_bth_t bth_mask;
    temp_deth_t deth_data;
    temp_deth_t deth_mask;
 }ib_filter_rules_t;


uint32_t filter_rules_count  = 0;
ib_filter_rules_t rules[MAX_RULES];

/* Internal Function prototypes */
static int ib_test_parse_rec(char *rec, int len, ib_filter_fields_t *flds);
void incr_counters(int res);

/* Functions to parse the filter rules */
static int ib_test_parse_rules(const char* filter_rule_filename);
#if 0
static temp_lrh_t ib_filter_get_lrh(char *ptr_144bits);
static temp_bth_t ib_filter_get_bth(char *ptr_144bits);
static temp_deth_t ib_filter_get_deth(char *ptr_144bits);
#endif
static void convert_rule(ib_filter_rules_t* internal_rule, infiniband_filter_rule_p parsed_rules);
/* Global variables for this test */
char rec_msg[MAX_STR_LEN];
int rec_msg_len = 0;

global_params_t settings;

/* Global counters for final reporting */
static uint32_t ib_filter_total = 0;
static uint32_t ib_filter_ignored = 0;
static uint32_t ib_filter_failed = 0;
static uint32_t ib_filter_passed = 0;
static uint32_t ib_filter_warning = 0;
static uint32_t ib_filter_unknown = 0;

int pkt_cap[MAX_IFC_CNT];



/* And some configurations... */
test_printf pf;


int ib_filter_tests_init(char *params[], int param_cnt, global_params_t *global_params, test_printf f)
{

	ClArgPtr clarg = NULL;
	FILE* errorfile = NULL;
	int argindex = 0;
	int clarg_result = 0;
	int code = 0;
	int cnt;

	/* Have a local copy of the settings */
	memcpy(&settings, global_params, sizeof(settings));

	/* Set up the command line options. */
	clarg = dagclarg_init(param_cnt, (const char* const *) params);
	
	/* General options. */
	dagclarg_add_string(clarg, "Name of a text file containing one filter per line.", "--infile", 'i', "filename", uInfileBuf, FILENAME_BYTES, CLA_INPUT_FILE);
	/* Parse the command line options. */
	clarg_result = dagclarg_parse(clarg, errorfile, &argindex, &code);

	while (1 == clarg_result)
	{
		switch (code)
		{
		case CLA_TESTCASE:
		case CLA_TESTNUM:
			/* Do nothing */
			break;
        case CLA_INPUT_FILE:
            /*What to do? */
            break;
		default:
			if (params[argindex][0] == '-')
			{
				/* Unknown option. */
				dagutil_error("unknown option %s\n", params[argindex]);
				/* print_usage(clarg);*/
				return TEST_FAIL;
			}
			break;
		}
		clarg_result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	}

	/* Check for errors in the arguments */
	if (-1 == clarg_result)
	{
		if (argindex < param_cnt)
		{
			dagutil_error("while processing option %s\n", params[argindex]);
		}
		dagclarg_display_usage(clarg, stderr);
		return TEST_FAIL;
	}

	/* Initialize local variables */
	for (cnt=0; cnt<MAX_IFC_CNT; cnt++)
	{
		pkt_cap[cnt] = 0;
	}
    
    init_deth_offset();
    /* parse and store the rules in internal structure*/
    ib_test_parse_rules(uInfileBuf/*params to decide */);

	return TEST_PASS;
}

/* This is the main test function.
 *
 * This function basically calls the correct function for testing
 * and increments the correct counters depending on the test result.
 *
 */
int ib_filter_tests_run(char *rec, int len, char* lastpkt, struct_protocol_t prot, uint64_t rec_cnt)
{

	ib_filter_fields_t flds;
	int ret_val = TEST_PASS;
    uint8_t match = 0;
    uint8_t multi_match = 0;
    int i = 0 ;
    int len_print = 0;
	rec_msg_len = 0;

	/* Parse the record and verify that we have a valid record. */
	if ((ret_val = ib_test_parse_rec(rec, len, &flds)) != TEST_PASS)
	{
		incr_counters(ret_val);
		return ret_val;
	}
    if (flds.ifc < MAX_IFC_CNT)
    {
        pkt_cap[flds.ifc]++;
    }
    else
    {
        len_print = snprintf(&rec_msg[rec_msg_len], MAX_STR_LEN-rec_msg_len, "Interface reported was not valid (%u)\n", flds.ifc);
        rec_msg_len += len_print;
        ret_val = TEST_FAIL;
    }

    for ( i = 0 ; i < filter_rules_count;i++)
    {
        if ( ( (*(flds.lrh_ptr) & rules[i].lrh_mask.word) == rules[i].lrh_data.word) 
                && ( (*(flds.bth_ptr) & rules[i].bth_mask.word) == rules[i].bth_data.word) 
                 && ( (*(flds.deth_ptr) & rules[i].deth_mask.word) == rules[i].deth_data.word) ) 
        {
            if ( 1 == match ) multi_match = 1;
            match = 1;
            /* now compare tag(color) drop etc etc Not sure how the multimatch option has to be handled*/
            /* Tag comparison has to be verified - Not all 32 bits need to be compared*/
            /* assuming 0-19 bits are needs to be verified (bit 3 is reserved */
            if ( flds.result_word == rules[i].color_info)
            {
                ret_val = TEST_PASS;
            }
            else
            {
                len_print = snprintf(&rec_msg[rec_msg_len], MAX_STR_LEN-rec_msg_len,"Rule %d matched, but Test failed 0x%04x and 0x%04x \n",i,flds.result_word,rules[i].color_info);
                rec_msg_len += len_print;
                ret_val = TEST_FAIL;
            }
        }
        
    }
	incr_counters(ret_val);
	return ret_val;
}

int ib_filter_tests_err_msg(char *buf, int size)
{
    if (size > 0)
    {
	    strncpy(buf, rec_msg, size);
	    buf[size-1] = '\0'; /* Just in case the buffer was too short...*/
    }
	return TEST_PASS;
}

int ib_filter_tests_final_msg(char *buf, int size)
{
	char msg[MAX_STR_LEN];

	snprintf(msg, MAX_STR_LEN, "Infiniband filter:\n \tpass %u\n \tfail %u\n \twarning %u\n \tignore %u\n", ib_filter_passed,ib_filter_failed, ib_filter_warning,ib_filter_ignored);

    if (size > 0)
    {
	    strncpy(buf, msg, size);
	    buf[size-1] = '\0';
    }

	/* Test will fail if we had failuirs captures or we didn't capture any packets */
	/* Test will have wanting if we idn't fail, but we encountered wanting during the test, or we ignored packets */
	/* Otherwise, we pass the test */
	if ((ib_filter_failed > 0) || (ib_filter_total == 0))
		return TEST_FAIL;
	else if ((ib_filter_warning > 0) || (ib_filter_ignored > 0))
		return TEST_WARNING;
	else
		return TEST_PASS;
}

int ib_filter_tests_cleanup()
{
	return TEST_PASS;
}

int ib_filter_tests_printf (char *format, ...)
{

	printf ("%s: %s - Test printf\n", __FILE__, __FUNCTION__);

	return TEST_PASS;
}


void incr_counters(int res)
{
	switch (res)
	{
	case TEST_PASS:
		ib_filter_passed++;
		break;

	case TEST_IGNORE:
		ib_filter_ignored++;
		break;

	case TEST_FAIL:
		ib_filter_failed++;
		break;
		
	case TEST_WARNING:
		ib_filter_warning++;
		break;

	default:
		ib_filter_unknown++;
		break;
	}
}
int ib_test_parse_rules(const char* filter_rule_filename)
{
    int retval = 0;
    FILE * fin;
	
    memset(&rules, 0, MAX_RULES * sizeof(ib_filter_rules_t));

	fin = fopen(filter_rule_filename,"r");
	if( fin == NULL )
	{
		printf("File is missing or no access\n");
		return -1;
	}
    infinirestart(fin);
    
    while(1)
	{
		retval = infinilex();
		if(retval == T_RULE_DONE)
		{
			filter_rules_count ++;
            
        if (filter_rules_count > MAX_RULES )
        {
            filter_rules_count = MAX_RULES;
            printf("Warning. Only %d Rules are taken\n", filter_rules_count);
            break;
        }
		}
		else if (retval == T_RULE_CONTINUE)
		{
			printf("This state is unused please contact suppot@endace.com \
 and send the rule file used and this line print out. retval: %d rules_count:%d\n",retval,filter_rules_count);
		}
		else if ( retval < 0 )
		{
			printf(" errors infiniflex returns: %d at rule: %d\n",retval,filter_rules_count);
			break;
		}
		else if (retval == 0)
		{
			break;
			
		} else {
			printf("Unknown state please contact suppot@endace.com \
 and send the rule file used and this line print out. retval: %d rules_count:%d\n",retval,filter_rules_count);
		}
		
		//copy the parsed rule into the ruleset
		//memcpy( &rules[rules_count-1],&infini_filter_rule,sizeof(infini_filter_rule) );
		convert_rule ( &rules[filter_rules_count -1], &infini_filter_rule);
	}	
	/* complete */
	printf("Ruleset file parsed successifully, %d rules have been created.\n", filter_rules_count);
		

	/* clean up */
	fclose(fin);

#if 0
    for( i = 0; i < filter_rules_count; i++)
    {
        /* convert the 144 bit char array into internal rule format */
        rules[i].lrh_data = ib_filter_get_lrh(ptr_144bits_data);
        rules[i].lrh_mask = ib_filter_get_lrh(ptr_144bits_mask);
        rules[i].bth_data = ib_filter_get_bth(ptr_144bits_data);
        rules[i].bth_mask = ib_filter_get_bth(ptr_144bits_mask);
        rules[i].deth_data = ib_filter_get_deth(ptr_144bits_data);
        rules[i].deth_mask = ib_filter_get_deth(ptr_144bits_mask);
        //rules[i].a_ram = ; TODO
    }
#endif
    return TEST_PASS;
}
int  ib_test_parse_rec(char *rec, int len, ib_filter_fields_t *flds)
{
	dag_record_t* drec = (dag_record_t*) rec;
    uint32_t ext_header_count = 0;
    uint64_t ext_hdr = 0;
    ib_lrh_t *this_lrh = NULL;
    ib_grh_t *this_grh = NULL;
    ib_bth_t  *this_bth = NULL;
    ib_ext_deth_t *this_deth = NULL;
    int  len_print = 0;
    erf_payload_t *proto_hdr = NULL;
    
    /* initialize the ptrs */
    flds->lrh_ptr = &zero_64bit;
    flds->bth_ptr = &zero_64bit;
    flds->deth_ptr = &zero_64bit;


	if ( (drec->type & 0x7f) != ERF_TYPE_INFINIBAND)
    {
        len_print = snprintf(&rec_msg[rec_msg_len], MAX_STR_LEN-rec_msg_len,"Rec is not ERF_TYPE_INFINIBAND\n");
        rec_msg_len += len_print;
        return TEST_IGNORE;
    }
    if ( (ext_header_count = dagerf_ext_header_count((uint8_t*) rec,len)) <= 0 )
    {
        len_print = snprintf(&rec_msg[rec_msg_len], MAX_STR_LEN-rec_msg_len,"Rec does not have ext header\n");
        rec_msg_len += len_print;
        return TEST_IGNORE;
    }
    /* parse the extension header */
    /* len has to be atleast erf length (16) + ext hdr count * 8 */
    if ( len < 16 + ( ext_header_count * 8) )
    {
        len_print = snprintf(&rec_msg[rec_msg_len], MAX_STR_LEN-rec_msg_len,"Rec does not have enough length to process ");
        rec_msg_len += len_print;
        return TEST_FAIL;
    }
    /* Now starts parsing the data from the erf header */
    flds->rectype = drec->type;
    flds->ifc = drec->flags.iface;
    flds->rlen = ntohs(drec->rlen);
    flds->wlen = ntohs(drec->wlen);
    
    /* Now starts parsing the data from the ext headers */
    ext_hdr = *( (uint64_t*) &rec[16]);
    ext_hdr = bswap_64(ext_hdr);
    flds->result_word = (uint32_t) (ext_hdr >> 32) & 0xffff3;
    
    proto_hdr = (erf_payload_t*)((uintptr_t)rec + (ext_header_count * 8) + dag_record_size);

    this_lrh = & ((proto_hdr->infiniband).ib_rec.ib_with_no_grh.lrh);
    /*flds->slid = ntohs (this_lrh->src_local_id);
    flds->dlid = ntohs (this_lrh->dest_local_id);
    flds->service_level = this_lrh->service_level;
    flds->lhh = this_lrh->lnh;*/
    flds->lrh_ptr = (uint64_t*) this_lrh ;
    //printf("VLane=0x%01x LVer=0x%01x SLevel=0x%01x LNH=0x%01x Packet Length=0x%03x DLID=0x%08x SLID=0x%08x\n", this_lrh->virtual_lane, this_lrh->link_version, this_lrh->service_level, this_lrh->lnh, ntohs( this_lrh->packet_length) & 0x7ff , ntohs (this_lrh->dest_local_id), ntohs (this_lrh->src_local_id));
    if ( this_lrh->lnh == 0x3)
    {
        /* next header is GRH */
        this_grh =   &((drec->rec.infiniband).ib_rec.ib_with_grh.grh);
    }
    else if ( this_lrh->lnh == 0x2 )
    {
        this_bth = &((drec->rec.infiniband).ib_rec.ib_with_no_grh.bth);
    }
    if ( this_grh )
    {
      //    printf("IP Ver=0x%02x Traffic Class=0x%03x Flow Label=0x%05x PayLen=0x%04x NxtHdr=0x%02x HopLmt=0x%2x\n",(ntohl(this_grh->word0) & 0xf0000000) >> 28, (ntohl(this_grh->word0) & 0x0ff00000 ) >> 16,( ntohl( this_grh->word0) & 0x000fffff) , ntohs( this_grh->pay_len) ,this_grh->next_header, this_grh->hop_limit) ;
         if ( this_grh->next_header == 0x1b ) 
            {
             this_bth  = &((drec->rec.infiniband).ib_rec.ib_with_grh.bth);
            }
    }
    if ( this_bth )
    {
        // printf("OpCode=0x%01x TVer=0x%02x Pad Count=0x%x Migration state=0x%x Solicited Event=0x%x Dest QP=0x%05x AckReq=0x%x PSN=0x%05x\n",this_bth->op_code,this_bth->t_header_version, this_bth->pad_count, this_bth->migration_state, this_bth->solicited_event,ntohl(this_bth->dest_qp)>> 8,this_bth->ack_req, ntohl(this_bth->packet_seq_number)>> 8) ;
        /*flds->opcode = this_bth->op_code;
        flds->dqp = ntohl(this_bth->dest_qp)>> 8;*/
        flds->bth_ptr = (uint64_t*) this_bth;
    }
   if ( this_bth && (-1 != opcode_to_deth_offset[this_bth->op_code]) )
    {
        if( this_grh)
            this_deth =  (ib_ext_deth_t *)((uint8_t *)&((drec->rec.infiniband).ib_rec.ib_with_grh.ib_ext) + opcode_to_deth_offset[this_bth->op_code]);
        else
            this_deth = (ib_ext_deth_t *)((uint8_t *) &((drec->rec.infiniband).ib_rec.ib_with_no_grh.ib_ext) + opcode_to_deth_offset[this_bth->op_code]);
        flds->deth_ptr = (uint64_t*) this_deth;
    }
    
	return TEST_PASS;
}
#if 0
temp_deth_t ib_filter_get_deth(char *ptr_144bits)
{
    uint32_t temp = 0;
    temp_deth_t ret_val;
    /* bit 50 to 73 (24 bits) forms the source queue pair */
    temp = * ( (uint32_t*) (ptr_144bits + 50) );
    /* endiannes conversion is needed*/
    temp = ntohl(temp & 0xffffff);
    ret_val.deth.source_qp = temp ;
#ifdef DBG_RULE_PARSE
    printf("Parsed DETH - Src QP=0x%05x  \n", ret_val.deth.source_qp);
#endif 
    return ret_val;
}

temp_bth_t ib_filter_get_bth(char *ptr_144bits)
{
    uint32_t temp = 0;
    temp_bth_t ret_val;
    /* bit 74 to 97 (24 bits) forms the destn queue pair */
    temp = * ( (uint32_t*) (ptr_144bits + 74) );
    /* endiannes conversion is needed*/
    temp = ntohl(temp & 0xffffff);
    ret_val.bth.dest_qp = temp;
    /* bit 98 to 105 (8 bits) forms the bth opcode */
    temp = * ( (uint32_t*) (ptr_144bits + 98) );
    ret_val.bth.op_code = temp & 0xff;
#ifdef DBG_RULE_PARSE
    printf("Parsed BTH - OpCode=0x%01x Dest QP=0x%05x \n", ret_val.bth.dest_qp, ret_val.bth.op_code);
#endif 
    return ret_val;
}
temp_lrh_t ib_filter_get_lrh(char *ptr_144bits)
{
    uint32_t temp = 0;
    temp_lrh_t ret_val;
    /* bit 106 to 107 (2 bits) forms the LNH */
    temp = * ( (uint32_t*) (ptr_144bits + 106) );
    /* endiannes conversion is needed*/
    ret_val.lrh.lnh = temp & 0x3;
    /* bit 108 to 111 (4 bits) forms the service level */
    temp = * ( (uint32_t*) (ptr_144bits + 108) );
    ret_val.lrh.service_level = temp & 0xf;
    /* bit 112 to 127 (16 bits) forms the DLID*/
    temp = * ( (uint32_t*) (ptr_144bits + 112) );
    temp = ntohl( temp & 0xffff);
    ret_val.lrh.dest_local_id = temp ;
    /* bit 128 to 143 (16 bits) forms the SLID*/
    temp = * ( (uint32_t*) (ptr_144bits + 128) );
    temp = ntohl( temp & 0xffff);
    ret_val.lrh.src_local_id = temp ;
#ifdef DBG_RULE_PARSE
    printf("Parsed LRH . LNH=0x%01x SLevel=0x%01x DLID=0x%08x SLID=0x%08x\n",  ret_val.lrh.lnh, ret_val.lrh.service_level, ret_val.lrh.dest_local_id , ret_val.lrh.src_local_id);
#endif 
    return ret_val;
}
#endif 
void convert_rule(ib_filter_rules_t* internal_rule, infiniband_filter_rule_p parsed_rules)
{
    /* get LRH fields */
    internal_rule->lrh_data.lrh.lnh = parsed_rules->lnh.data;
    internal_rule->lrh_mask.lrh.lnh = parsed_rules->lnh.mask;

    internal_rule->lrh_data.lrh.service_level = parsed_rules->service_level.data;
    internal_rule->lrh_mask.lrh.service_level = parsed_rules->service_level.mask;

    internal_rule->lrh_data.lrh.dest_local_id = htons(parsed_rules->dest_local_id.data);
    internal_rule->lrh_mask.lrh.dest_local_id = htons(parsed_rules->dest_local_id.mask);

    internal_rule->lrh_data.lrh.src_local_id = htons(parsed_rules->src_local_id.data);
    internal_rule->lrh_mask.lrh.src_local_id = htons(parsed_rules->src_local_id.mask);
    #ifdef DBG_RULE_PARSE
    printf("Parsed LRH Data. LNH=0x%01x SLevel=0x%01x DLID=0x%08x SLID=0x%08x\n",  internal_rule->lrh_data.lrh.lnh, internal_rule->lrh_data.lrh.service_level, internal_rule->lrh_data.lrh.dest_local_id , internal_rule->lrh_data.lrh.src_local_id);
    printf("Parsed LRH Mask . LNH=0x%01x SLevel=0x%01x DLID=0x%08x SLID=0x%08x\n",  internal_rule->lrh_mask.lrh.lnh, internal_rule->lrh_mask.lrh.service_level, internal_rule->lrh_mask.lrh.dest_local_id , internal_rule->lrh_mask.lrh.src_local_id);
    #endif 
    /* get BTH fields */
    internal_rule->bth_data.bth.op_code = parsed_rules->opcode.data;
    internal_rule->bth_mask.bth.op_code = parsed_rules->opcode.mask;

    internal_rule->bth_data.bth.dest_qp = htonl ( (parsed_rules->dest_qp.data << 8) & 0xffffff00 );
    internal_rule->bth_mask.bth.dest_qp = htonl ( (parsed_rules->dest_qp.mask << 8) & 0xffffff00 );
    
#ifdef DBG_RULE_PARSE
    printf("Parsed BTH DATA - OpCode=0x%01x Dest QP=0x%05x \n", internal_rule->bth_data.bth.dest_qp, internal_rule->bth_data.bth.op_code);
    printf("Parsed BTH MASK - OpCode=0x%01x Dest QP=0x%05x \n", internal_rule->bth_mask.bth.dest_qp, internal_rule->bth_mask.bth.op_code);
#endif 
    /* get DETH fields */
    internal_rule->deth_data.deth.source_qp = htonl ( (parsed_rules->src_qp.data << 8) & 0xffffff00 );
    internal_rule->deth_mask.deth.source_qp = htonl ( (parsed_rules->src_qp.mask << 8) & 0xffffff00 );

    /* Get color information */
    internal_rule->color_info = parsed_rules->user_class; /* steering bits */
    internal_rule->color_info |= (parsed_rules->user_tag << 4);
 }
