/*
 * Vince version 1.0.1
 * 
 * 
 * +++++++++++
 *  V I N C E  ---  Vendor Independent Network Control Entity
 * +++++++++++
 * Copyright (c) 1994     Advanced Research Projects Agency (ARPA/CSTO)
 *                                   and the
 *                        Naval Research Laboratory (NRL/CCS)
 * All Rights Reserved.
 * 
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the software,
 * derivative works or modified versions, and any portions thereof, and
 * that both notices appear in supporting documentation.
 * 
 * ARPA AND NRL ALLOW FREE USE OF THIS SOFTWARE IN ITS "AS IS" CONDITION
 * AND DISCLAIM ANY LIABILITY OF ANY KIND FOR ANY DAMAGES WHATSOEVER
 * RESULTING FROM THE USE OF THIS SOFTWARE.
 * 
 * ARPA and NRL request users of this software to return to:
 * 
 *                 vince-distribution@cmf.nrl.navy.mil
 * 
 *                            or
 * 
 *                 Naval Research Laboratory, Code 5590
 *                 Center for Computational Science
 *                 Washington, D.C. 20375-5000
 * 
 * any improvements or extensions that they make and grant ARPA and NRL 
 * the rights to redistribute these changes.
 * 
 */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <elib/table.h>
#include <elib/machine.h>
#include <uni.h>
#include <uni_debug.h>
#include <uni_enums.h>
#include <uni_mesg.h>
#include <uni_errors.h>
#include <uni_interpret.h>
#include <uni_encode.h>

extern element_desc the_elements_tbl[];
static int reference_value;
u_char* gen_elem_encoder();
u_char* encode_octet_group();
int encode_message_header();
int encode_number();
void encode_short();
void encode_element_header();
u_char* bb_low_layer_info_encoder();


/******************************************************************************
 *  char* encode_mesg()
 *****************************************************************************/
u_char* encode_mesg(uni_message mesg, int *mesg_size)  
{
    int emi=0, mi=0, ei = 0;      /* table index variables */
    list e, o, f;                 /* list of elements, octet groups, fields */
    list r, q, s, l;              /* temp lists */
    int mesg_len = 0;
    u_char *mesg_buf;
    int mbi = 0; 
    int found = 0;
    u_char *elem_buf;
    element elem;
    
    debug("parse", "uni: encode_message\n");
    if ((mesg_len = calc_message_length(mesg, &mi)) < 0) {
        print_errors("encode_mesg", MESSAGE_NONEXISTENT);
        print_mesg(mesg);
        return 0;
    }
    if ((mesg_buf=(u_char *)allocate_memory(mesg_len+UNI_MESG_HDR_LEN))==NULL){
        fprintf(stderr, "uni: encode_mesg: couldn't alloc memory for mesg\n");
        return 0;
    }
    bzero(mesg_buf, mesg_len + UNI_MESG_HDR_LEN);
    /*
     * encode message header */
    encode_message_header(mesg_buf, mesg_len, mesg->hdr->crv, 
			              mesg->hdr->crv_flag, mi); 
    mbi = 9;    
    debug("parse", "uni: encode_message - message: %s         len %d\n",
	      messages_tbl[mi].name, mesg_len);
    /*
     * Encode appropriate elements and add to mesg_buf.
     * Elements can be repeated, the first occurance takes precedence */
    e = mesg->elements;
    dolist(r, e) {
        elem = r->first;
        /* 
         * check if element type is valid << should chech if valid in mesg! */
        GET_TBL_IDX(ei, elem->hdr->code, the_elements_tbl, MAX_ELEMENTS);
        if (ei >= MAX_ELEMENTS) {
            printf("uni: encode_generic_element - unrecognized element");
            break; /* return NULL */
        }
        GET_TBL_IDX(emi, elem->hdr->code, messages_tbl[mi].elist,MAX_ELEMENTS);
        /* 
         * encode element header and increment mesg buf idx */
        encode_element_header(elem, mesg_buf+mbi);
        mbi += UNI_ELEM_HDR_LEN;

        /* 
         * encode rest of element and copy in mesg buffer */
        elem_buf = (coders[ei].encoder_func)(mesg, mi, elem, ei,
                                           messages_tbl[mi].elist[emi].def_ogs);
        bcopy(elem_buf, (mesg_buf+mbi), elem->hdr->length);
        mbi += elem->hdr->length;
        deallocate_memory(elem_buf);
    }
    *mesg_size = mesg_len + UNI_MESG_HDR_LEN; 
    return mesg_buf;
}


/******************************************************************************
 *  gen_elem_encoder()
 *****************************************************************************/
u_char* gen_elem_encoder(uni_message mesg, int mi, element elem, int ei, 
			 int oglist)
    /* mi: index of message in message table */
    /* elem: elem to encode */    
    /* ei: index of element in the_element_tbl */
    /* oglist: bit mask of default og's in elem */
{
    register int oi = 0;
    u_char *og_buf;
    u_char *elem_buf;                           /* element buffer */
    int ebi = 0;                                /* element buffer index */
    list q;
    octet_group og;
    octet_group_desc *ogs = the_elements_tbl[ei].ogs;

    debug("parse", "uni: encode_generic_element - elem name %s     size: %d\n",
           the_elements_tbl[ei].name, elem->hdr->length);
    elem_buf=(u_char*) allocate_memory (elem->hdr->length);
    dolist (q, elem->octet_groups) {
        og = (octet_group) q->first;
        while (!(oglist & 1)) {
            oi++;
            oglist >>= 1;
        }
        debug("parse","uni: encode_generic_element - octet_group #: %d\n", oi);
        if (og_buf = encode_octet_group(mesg, elem, oi, ei)) {
	        bcopy(og_buf, (elem_buf + ebi), ogs[oi].fixed_length);
	        ebi += ogs[oi].fixed_length;
	        deallocate_memory(og_buf);
	}
        oglist >>= 1;
        oi++;
    }    
    return elem_buf;
}

/******************************************************************************
 *  encode_short
 *****************************************************************************/
void encode_short(unsigned int valn, u_char* buf, int bi)
{
    buf[bi++] = (valn>>8)&255;
    buf[bi] = (valn)&255;
}

/******************************************************************************
 *  encode_message_header()
 *****************************************************************************/
int encode_message_header(unsigned char* mesgbuf, int mesg_len,u_long call_ref,
			  int call_ref_flag, int mi)
{
    int bi = 0;
    int ei;

   /*
    * fill in protocol discriminator, spare, ref_#_size, ref_# */
    mesgbuf[bi++] = uni;
    mesgbuf[bi++] = 0x03;
    encode_number(call_ref, mesgbuf + bi, 3); 
    if (call_ref_flag)
        *(mesgbuf + bi) |= 0x80;
    else
        *(mesgbuf + bi) &= 0x7f;
    bi = 5;
   /*
    * fill in type, and length */
    mesgbuf[bi++] = (u_char) ((u_int)messages_tbl[mi].code); 
    mesgbuf[bi++] = 0x80; 
    encode_short(mesg_len, mesgbuf, bi); 
}

/******************************************************************************
 *  encode_element_header
 *****************************************************************************/
void encode_element_header(element elem, u_char* mesg_buf)
{
    register int bi = 0;

    mesg_buf[bi++] = (u_char) elem->hdr->code;    
    mesg_buf[bi++] = 0x80;
    encode_short(elem->hdr->length, mesg_buf, bi);
}


/******************************************************************************
 *  encode_octet_group
 *****************************************************************************/
u_char* encode_octet_group(uni_message mesg, element elem, int oi, int ei)
/* mesg,elem = message and element being encoded
 * oi = octet group table index (within elem table) 
 * ei = element table index 
 */
{
    register int fi=0, vlen, shift;
    u_char *og_buf;             /* buffer to hold encoded octet group */
    int og_buf_ptr=CHAR_7;      /* ptr in og_buf,used for 7-bit-encoded og's */
    int bi = 0;                 /* index of og_buf */
    int og_length;              /* lenght of octet group in bits */
    int done=0;
    field fd;
    u_int *ival, temp;
    u_char ctemp;
    uni_address *cval;
    octet_group_desc *ogd = &(the_elements_tbl[ei].ogs[oi]);

    og_buf = (u_char *) allocate_memory (ogd->fixed_length);
    bzero(og_buf, ogd->fixed_length);
    og_length = ogd->fixed_length * ((ogd->encoded==ext) ? CHAR_7 : CHAR);

    /*---------------------------------------------------------------
    * encode an "address" field, i.e. > than 24 bits and return
    *--------------------------------------------------------------*/
    if (ogd->encoded == addr_coded) {
	    if ((cval = (uni_address *) field_lookup_in_elem(elem, 
             ogd->internal_fields[fi][0], addr_coded, 0))) {
            bcopy(cval, og_buf, UNI_ADDR_LEN);
            bi += UNI_ADDR_LEN;
            return og_buf;
        } else {
	        printf("uni: encode_octet_group - didn't find address.\n"); 
            deallocate_memory(og_buf);
            return 0;
        }
    }
     
    while (og_length && !done) {
        /*---------------------------------------------------------------
        * encode an encoded field, i.e. with an extension bit
        *--------------------------------------------------------------*/
        if (ogd->encoded == ext) {
            if (!(ival = (u_int *) field_lookup_in_elem(elem,
                                   ogd->internal_fields[fi][0], ext, 0))) {
                printf("uni: encode_octet_group - couldn't find ext field\n");
                return 0;
            }
            vlen = ogd->internal_fields[fi][1];
            if (vlen > CHAR_7) {      /* variable greater than 7 bits */
                temp = *ival; 
                while(vlen > 0) {
                    og_buf[bi] = 0;
                    if (vlen > 7) {
                        shift = ((vlen/7)-1)*7;
                        if (vlen % 7) {
                            shift += (vlen%7);
			}
                        og_buf[bi] = (temp >> shift) & 255;
                        bi++;
                    } else {
                        og_buf[bi] <<= (og_buf_ptr = 7 - vlen);
		    }
                    vlen -= 7;
                }
            } else {                /* variable less than 7 bits */
	        ctemp = (u_char)*ival; 
		shift = ((og_buf_ptr - vlen) <= 0) ? 0 : og_buf_ptr - vlen;
		og_buf[bi] |=  ctemp << shift;
		vlen -= (og_buf_ptr - shift);
		og_buf_ptr -= (og_buf_ptr - shift);
		if (og_buf_ptr <= 0) {
                    if (!(og_length - ogd->internal_fields[fi][1])) {
		        done = 1;
			og_buf[bi] |= 0x80;  /* end of og, set ext bit */
		    } else {
                        og_buf[bi] &= 0x7f;  /* continue, set ext bit 0 */
                    }
		    bi++;
		    og_buf[bi] = 0;
		    og_buf_ptr = 7;
		}
            
            }
              
        }
        /*---------------------------------------------------------------
        * else, encode what has to be a "flat" field
        *--------------------------------------------------------------*/
        else 
        {
            if (ival =(u_int *)field_lookup_in_elem(elem,
                                        ogd->internal_fields[fi][0], flat, 0)){
                encode_number((u_int)*ival, og_buf+bi, 
                                ogd->internal_fields[fi][1]/8);
                bi += ogd->internal_fields[fi][1] / 8;
                og_buf[bi] = 0;
            } else {
                printf("uni: encode_octet_group: couldn't find flat field\n");  
                return 0;
            }
        }
        og_length -= ogd->internal_fields[fi++][1]; 
    }
    return og_buf;
}

/******************************************************************************
 *  encode_number()
 *****************************************************************************/
int encode_number(u_int num, u_char *buf, int field_len)
{
    int i, j = 0;
    u_long temp;

    for (i = sizeof(u_int); i > 0; i--) {
        temp = num & 0xff000000;
        if (i <= field_len)
            buf[j++] = (u_char) (temp >> 24);
        num <<= 8;
    }
}


/******************************************************************************
 *  bb_low_layer_info_encoder()
 *****************************************************************************/
u_char* bb_low_layer_info_encoder(uni_message mesg, int mi, element elem, 
int ei, int oglist)
 
/* mi = index of message in message table */
/* elem = elem to encode */    
/* ei = index of element in the_element_tbl */
/* oglist = bit mask of default og's in elem */
{
    register int oi = 0;
    u_char *og_buf;
    u_char *elem_buf;                           /* element buffer */
    int ebi = 0;                                /* element buffer index */
    list q;
    int *t;
    octet_group og;
    octet_group_desc *ogs = the_elements_tbl[ei].ogs;
    
    elem_buf=(u_char*) allocate_memory (elem->hdr->length);
    /* Layer 1 parsing */
    if (field_lookup_in_elem(elem, layer_1_id, ext, 0)) {
        og_buf = encode_octet_group(mesg, elem, 0, ei);
        bcopy(og_buf, (elem_buf + ebi), ogs[0].fixed_length);
        ebi += ogs[0].fixed_length;
        deallocate_memory(og_buf);
    }
    /* Layer 2 parsing */
    if (field_lookup_in_elem(elem, layer_2_id, ext, 0)) {
        if (t = (int *)field_lookup_in_elem(elem, mode, flat, 0)) 
            og_buf = encode_octet_group(mesg, elem, oi=3, ei);
        else if (t = (int *)field_lookup_in_elem(elem, 
                                  user_spec_layer_2_proto_info, flat, 0)) 
            og_buf = encode_octet_group(mesg, elem, oi=2, ei);
        else
            og_buf = encode_octet_group(mesg, elem, oi=1, ei);
        bcopy(og_buf, (elem_buf + ebi), ogs[oi].fixed_length);
        ebi += ogs[oi].fixed_length;
        deallocate_memory(og_buf);
    }
    /* Layer 3 parsing */
    if (field_lookup_in_elem(elem, layer_3_id, ext, 0)) {
        if (t = (int *)field_lookup_in_elem(elem, mode, flat, 0)) 
            og_buf = encode_octet_group(mesg, elem, oi=10, ei);
        else if (t = (int *)field_lookup_in_elem(elem, 
                                  user_spec_layer_3_proto_info, flat, 0)) 
            og_buf = encode_octet_group(mesg, elem, oi=9, ei);
        else if (t = (int *)field_lookup_in_elem(elem, 
                                  init_proto_ident, flat, 0)) 
             og_buf = encode_octet_group(mesg, elem, oi=5, ei);
        else
             og_buf = encode_octet_group(mesg, elem, oi=4, ei);
        bcopy(og_buf, (elem_buf + ebi), ogs[oi].fixed_length);
        ebi += ogs[oi].fixed_length;
        deallocate_memory(og_buf);
    }
    /* SNAP parsing */
    if (field_lookup_in_elem(elem, snap_id, ext, 0)) {
        og_buf = encode_octet_group(mesg, elem, oi=6, ei); /* snap id */
        bcopy(og_buf, (elem_buf + ebi), ogs[oi].fixed_length);
        ebi += ogs[oi].fixed_length;
        deallocate_memory(og_buf);
        og_buf = encode_octet_group(mesg, elem, oi=7, ei); /* oui */
        bcopy(og_buf, (elem_buf + ebi), ogs[oi].fixed_length);
        ebi += ogs[oi].fixed_length;
        deallocate_memory(og_buf);
        og_buf = encode_octet_group(mesg, elem, oi=8, ei); /* pid */
        bcopy(og_buf, (elem_buf + ebi), ogs[oi].fixed_length);
        ebi += ogs[oi].fixed_length;
        deallocate_memory(og_buf);
    }
    return elem_buf;
}




