#include "stdhead.h"
#include "advertise.h"
#include "lib.h"

struct OPTIONS * copy_client_id (struct DHCP_MESSAGE * solicit_message, struct OPTIONS * opt_ptr)
{
    int flag;
    struct OPTIONS *search_ptr = (struct OPTIONS *) solicit_message -> opt;
    struct DUID *duid_ptr;
    struct DUID1 *duid1_ptr;
    struct DUID2 *duid2_ptr;
    struct DUID3 *duid3_ptr;

    //use to copy solicit message's duid
    struct DUID *sol_duid_ptr;
    struct DUID1 *sol_duid1_ptr;
    struct DUID2 *sol_duid2_ptr;
    struct DUID3 *sol_duid3_ptr;

    
    while (search_ptr)
    {
	    flag = 0;
	
	    switch (search_ptr -> u_opt_code.opt_code)
	    {
	        case OPTION_CLIENTID :
		        flag = 1;
		        break;
		
	        case OPTION_IA :
		        search_ptr = ((struct IA *) search_ptr -> opt_data) -> opt;
		        break;
		
	        case OPTION_IAADDR :
		        search_ptr = ((struct IA_ADDRESS *) search_ptr -> opt_data) -> opt;
		        break;

            case OPTION_ELAPSED:
                search_ptr = ((struct ELAPSED_TIME *) search_ptr -> opt_data) -> opt;
                break;
            
            default:
                BLOG("BLOG: unknown opt: %d",search_ptr -> u_opt_code.opt_code);
                search_ptr = ((struct ELAPSED_TIME *) search_ptr -> opt_data) -> opt;
                break;
	    }
	
	    if (flag)
	        break;
    }
    
    if (!search_ptr)
	return NULL;

    //i send back the client id payload back to client, not send duid_type = 3,  
    //duid_type may be 1 or 2 or 3.                     //bruce hsu
    opt_ptr -> u_opt_code.opt_code = OPTION_CLIENTID;
    opt_ptr -> u_opt_len.opt_len = search_ptr -> u_opt_len.opt_len;
    //copy the DUID payload:   search_ptr copy to opt_ptr 
    opt_ptr ->opt_data = (struct DUID *) malloc (sizeof (struct DUID));
    duid_ptr = (struct DUID *) opt_ptr ->opt_data;
    sol_duid_ptr = search_ptr->opt_data;
    duid_ptr ->u_duid_type.duid_type = sol_duid_ptr->u_duid_type.duid_type;
    switch(duid_ptr ->u_duid_type.duid_type)
    {
        case 1:
            BLOG("copy duid type 1\n");
            duid_ptr->duid_type = malloc (sizeof (struct DUID1));
            duid1_ptr = (struct DUID1 *) duid_ptr->duid_type;
            copy_duid1 ((struct DUID1 *) ((struct DUID *) search_ptr -> opt_data) -> duid_type, duid1_ptr);
            break;
        case 2:
            BLOG("copy duid type 2\n");
            duid_ptr->duid_type = malloc (sizeof (struct DUID2));
            duid2_ptr = (struct DUID1 *) duid_ptr->duid_type;
            copy_duid2 ((struct DUID2 *) ((struct DUID *) search_ptr -> opt_data) -> duid_type, duid2_ptr);
            
            break;
        case 3:
            BLOG("copy duid type 3\n");
            duid_ptr->duid_type = malloc (sizeof (struct DUID3));
            duid3_ptr = (struct DUID3 *) duid_ptr -> duid_type;
            copy_duid3 ((struct DUID3 *) ((struct DUID *) search_ptr -> opt_data) -> duid_type, duid3_ptr);
            break;
        default:
            BLOG("unknown copy duid type %d\n",duid_ptr ->u_duid_type.duid_type);
    }
    
    opt_ptr = duid_ptr -> opt = (struct OPTIONS *) malloc (sizeof (struct OPTIONS));
    return opt_ptr;
}

struct OPTIONS * add_ia_option (struct DHCP_MESSAGE * solicit_message, struct addr_details *available_addr, struct OPTIONS * opt_ptr)
{
    int flag;
    struct OPTIONS *search_ptr = (struct OPTIONS *) solicit_message -> opt;
    struct IA *ia_ptr;
    
    while (search_ptr)
    {
	flag = 0;
	
	switch (search_ptr -> u_opt_code.opt_code)
	{
	    case OPTION_CLIENTID :
		    search_ptr = ((struct DUID *) search_ptr -> opt_data) -> opt;
		    break;
		
	    case OPTION_IA :
		    flag = 1;
		    break;
		
	    case OPTION_IAADDR :
		    search_ptr = ((struct IA_ADDRESS *) search_ptr -> opt_data) -> opt;
		    break;

        case OPTION_ELAPSED:
            search_ptr = ((struct ELAPSED_TIME *) search_ptr -> opt_data) -> opt;
            break;

        default:
            BLOG("BLOG: unknown opt: %d",search_ptr -> u_opt_code.opt_code);
            search_ptr = ((struct ELAPSED_TIME *) search_ptr -> opt_data) -> opt;
            break;
            
	}
	
	if (flag)
	    break;
    }
    
    if (!search_ptr)
	return NULL;
	
    opt_ptr -> u_opt_code.opt_code = OPTION_IA;
    opt_ptr -> u_opt_len.opt_len = search_ptr -> u_opt_len.opt_len;

    ia_ptr = opt_ptr -> opt_data = (struct IA *) malloc (sizeof (struct IA));
    ia_ptr -> u_iaid.iaid = ((struct IA *) search_ptr -> opt_data) -> u_iaid.iaid;
    ia_ptr -> u_t1.t1 = available_addr -> t1;
    ia_ptr -> u_t2.t2 = available_addr -> t2;
    ia_ptr -> status = Success;

    opt_ptr = ia_ptr -> opt = (struct OPTIONS *) malloc (sizeof (struct OPTIONS));
    return opt_ptr;
}

void add_iaaddr_option (struct DHCP_MESSAGE * solicit_message, struct addr_details *available_addr, struct OPTIONS * opt_ptr)
{
    int flag, i;
    struct OPTIONS *search_ptr = (struct OPTIONS *) solicit_message -> opt;
    struct IA_ADDRESS *ia_addr_ptr;
    
    int iaaddr_payload_len=0;
    
    while (search_ptr)
    {
	    flag = 0;	
	    switch (search_ptr -> u_opt_code.opt_code)
	    {
	        case OPTION_CLIENTID :
		        search_ptr = ((struct DUID *) search_ptr -> opt_data) -> opt;
		        break;
		
	        case OPTION_IA :
		        search_ptr = ((struct IA *) search_ptr -> opt_data) -> opt;
		        break;
                
            case OPTION_ELAPSED:
                search_ptr = ((struct ELAPSED_TIME *) search_ptr -> opt_data) -> opt;
                break;
                
	        case OPTION_IAADDR :
		        flag = 1;
		        break;

            default:
                BLOG("BLOG: unknown opt: %d",search_ptr -> u_opt_code.opt_code);
                search_ptr = ((struct ELAPSED_TIME *) search_ptr -> opt_data) -> opt;
                break;
	    }
	
	    if (flag)
	        break;
    }

    //we can not just return if we do not find IA address, we should also create 
    // IA address payload for user to get IPv6 address   //bruce hsu 
    if (NULL==search_ptr){
        BLOG("BLOG: can not find IA ADDR in solicit message\n");
        opt_ptr -> u_opt_code.opt_code = OPTION_IAADDR;
        opt_ptr -> u_opt_len.opt_len = 16+4+4;  // [v6_addr][pre_t][val_t]
        ia_addr_ptr = opt_ptr -> opt_data = (struct IA_ADDRESS *) malloc (sizeof (struct IA_ADDRESS));
        ia_addr_ptr -> t_bit = 0;   //set to 0
        ia_addr_ptr -> addr_status = Success;   //set to success
        ia_addr_ptr -> prefix_length = 128;  //set to 128
        for (i = 0; i < 16; i++){
	        ia_addr_ptr -> addr[i] = available_addr -> ipv6_addr.s6_addr[i];
        }
        ia_addr_ptr -> u_pref_lifetime.pref_lifetime = available_addr -> pref_life;
        ia_addr_ptr -> u_valid_lifetime.valid_lifetime = available_addr -> valid_life;
        ia_addr_ptr -> ia_addr_opt = 0;
        ia_addr_ptr -> opt = 0;
    }else{
        BLOG("BLOG: find IA ADDR in solicit message\n");
        opt_ptr -> u_opt_code.opt_code = OPTION_IAADDR;
        opt_ptr -> u_opt_len.opt_len = search_ptr -> u_opt_len.opt_len;
        ia_addr_ptr = opt_ptr -> opt_data = (struct IA_ADDRESS *) malloc (sizeof (struct IA_ADDRESS));
        ia_addr_ptr -> t_bit = 0;
        ia_addr_ptr -> addr_status = Success;
        ia_addr_ptr -> prefix_length = ((struct IA_ADDRESS *) search_ptr -> opt_data) -> prefix_length;
        for (i = 0; i < 16; i++)
	    ia_addr_ptr -> addr[i] = available_addr -> ipv6_addr.s6_addr[i];
        ia_addr_ptr -> u_pref_lifetime.pref_lifetime = available_addr -> pref_life;
        ia_addr_ptr -> u_valid_lifetime.valid_lifetime = available_addr -> valid_life;
        ia_addr_ptr -> ia_addr_opt = 0;
        ia_addr_ptr -> opt = 0;
    }
    
}

void server_fix_ia_option_length(struct DHCP_MESSAGE * dhcp_message_ptr)
{
    struct OPTIONS *ia_opt_ptr;
    struct OPTIONS *ia_addr_opt_ptr;
    //IA option length must add IAADDR payload length. (include [hdr] and [len] length)
    ia_opt_ptr=get_options_ptr(dhcp_message_ptr, OPTION_IA);
    ia_addr_opt_ptr=get_options_ptr(dhcp_message_ptr, OPTION_IAADDR);

    ia_opt_ptr->u_opt_len.opt_len+=ia_addr_opt_ptr->u_opt_len.opt_len+4;
    BLOG("Total IA payload length: %d", ia_opt_ptr->u_opt_len.opt_len);
}

struct DHCP_MESSAGE * create_advertise_message (struct DHCP_MESSAGE *solicit_message, struct addr_details *available_addr)
{
    struct DHCP_MESSAGE * dhcp_message_ptr = malloc (sizeof (struct DHCP_MESSAGE));
    struct OPTIONS * opt_ptr;

    // MESSAGE TYPE
    dhcp_message_ptr->u_msg_type.msg_type = ADVERTISE;

    // Copy TRANSACTION ID from solicit message
    dhcp_message_ptr -> u_trans_id.trans_id = solicit_message -> u_trans_id.trans_id;

    // ASSIGN MEMORY FOR OPTIONS
    dhcp_message_ptr->opt = malloc (sizeof (struct OPTIONS));
    opt_ptr = dhcp_message_ptr -> opt;
    
    // copy CLIENT ID OPTION CODE from solicit message
    opt_ptr = copy_client_id (solicit_message, opt_ptr);
	
    // send SERVER Id option
    opt_ptr = send_server_id (opt_ptr);

    BLOG("BLOG: pref_val: %d\n",available_addr ->pref_val);
    // Add preference option if specified
    if (available_addr->pref_val){
	    opt_ptr = add_preference_option (available_addr -> pref_val, opt_ptr);
    }
    // add IA option
    opt_ptr = add_ia_option (solicit_message, available_addr, opt_ptr);
    
    // add IAADDR option
    add_iaaddr_option (solicit_message, available_addr, opt_ptr);

    // change IA option length
    server_fix_ia_option_length(dhcp_message_ptr);
    
    return dhcp_message_ptr;
}

struct OPTIONS * add_preference_option (int pref_val, struct OPTIONS *opt_ptr)
{
    struct PREFERENCE *pref_ptr;

    opt_ptr -> u_opt_code.opt_code = OPTION_PREFERENCE;
    opt_ptr -> u_opt_len.opt_len = 1;		// Preference value = 1 octet
    pref_ptr = opt_ptr -> opt_data = (struct PREFERENCE *) malloc (sizeof (struct PREFERENCE));

    pref_ptr -> preference_value = pref_val;
    opt_ptr = pref_ptr -> opt = (struct OPTIONS *) malloc (sizeof (struct OPTIONS *));
    return opt_ptr;
}
