/*------------------------------------------------------------------------------
*
* Copyright (c) 2011, EURid. All rights reserved.
* The YADIFA TM software product is provided under the BSD 3-clause license:
* 
* Redistribution and use in source and binary forms, with or without 
* modification, are permitted provided that the following conditions
* are met:
*
*        * Redistributions of source code must retain the above copyright 
*          notice, this list of conditions and the following disclaimer.
*        * Redistributions in binary form must reproduce the above copyright 
*          notice, this list of conditions and the following disclaimer in the 
*          documentation and/or other materials provided with the distribution.
*        * Neither the name of EURid nor the names of its contributors may be 
*          used to endorse or promote products derived from this software 
*          without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*------------------------------------------------------------------------------
*
*/
/** @defgroup dnsdbupdate Dynamic update functions
 *  @ingroup dnsdb
 *  @brief
 *
 * @{
 */
/*------------------------------------------------------------------------------
 *
 * USE INCLUDES */
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>

#include <dnscore/rfc.h>
#include <dnscore/ptr_vector.h>
#include <dnscore/dnsname.h>

#include "dnsdb/dynupdate.h"
#include "dnsdb/zdb_rr_label.h"
#include "dnsdb/zdb_record.h"
#include "dnsdb/zdb_zone.h"

/*
 *
 */

#define ZDB_DYNUPDATE_TAG 0x1111111111111111

typedef struct name_type_rdata name_type_rdata;

struct name_type_rdata
{
    u8* rname;
    u8* rdata;
    u16 rtype;
    u16 rdata_size;
};

static int
name_type_rdata_compare(const void* a, const void* b)
{
    int cmp;

    name_type_rdata* ia = *(name_type_rdata**)a;
    name_type_rdata* ib = *(name_type_rdata**)b;

    /* strcmp is adequate for this test */

    if((cmp = strcmp((char*)ia->rname, (char*)ib->rname)) != 0)
    {
        return cmp;
    }

    cmp = ia->rtype;
    cmp -= ib->rtype;

    return cmp;
}

static void
name_type_rdata_free(void* a)
{
    free(a);
}

static void
free_rrsets(ptr_vector* rrsetsp)
{
    ptr_vector_free_empties(rrsetsp, name_type_rdata_free);
    ptr_vector_destroy(rrsetsp);
}

/*
 * Input stream here is not a good idea because
 *
 * A) Like Gery said, the buffer is 64K max.
 * B) Using an input stream would require me to copy & allocate memory,
 *    this way only requires it for the last case, and only a "token",
 *    never a string.
 */

ya_result
dynupdate_check_prerequisites(zdb_zone* zone, packet_unpack_reader_data *reader, u16 count)
{
    if(ZDB_ZONE_INVALID(zone))
    {
        return ZDB_ERROR_ZONE_INVALID;
    }
    
    if(count == 0)
    {
        return SUCCESS;
    }
    
    dnsname_vector origin_path;
    dnsname_vector name_path;

    ptr_vector rrsets;

    u8* rname;
    u8* rdata;
    u32 rname_size;
    //u32 rttl;
    u16 rtype;
    u16 rclass;
    u16 rdata_size;
    u8 wire[MAX_DOMAIN_LENGTH + 10 + 65536];

    ptr_vector_init(&rrsets);

    dnsname_to_dnsname_vector(zone->origin, &origin_path);

    while(count-- > 0)
    {
        ya_result return_value;
        
        if(FAIL(return_value = packet_reader_read_zone_record(reader, wire, sizeof(wire))))
        {
            free_rrsets(&rrsets);
            return SERVER_ERROR_CODE(RCODE_FORMERR);
        }
        
        rname = wire;
        rname_size = dnsname_len(wire);
        rtype = GET_U16_AT(wire[rname_size]);
        rclass = GET_U16_AT(wire[rname_size + 2]);
        //rttl = ntohl(GET_U32_AT(wire[rname_size + 4]));
        rdata_size = ntohs(GET_U16_AT(wire[rname_size + 8]));        
        rdata = &wire[rname_size + 10];

        dnsname_to_dnsname_vector(rname, &name_path);

        s32 idx;

        for(idx = 0; idx < origin_path.size; idx++)
        {
            if(!dnslabel_equals(origin_path.labels[origin_path.size - idx], name_path.labels[name_path.size - idx]))
            {
                free_rrsets(&rrsets);
                return SERVER_ERROR_CODE(RCODE_NOTZONE);
            }
        }

        if(rclass == CLASS_ANY)
        {
            if(rdata_size != 0)
            {
                free_rrsets(&rrsets);
                return SERVER_ERROR_CODE(RCODE_FORMERR);
            }

            zdb_rr_label* label = zdb_rr_label_find_exact(zone->apex, name_path.labels, (name_path.size - origin_path.size) - 1);

            if(rtype == TYPE_ANY)
            {
                if(label == NULL)
                {
                    free_rrsets(&rrsets);
                    return SERVER_ERROR_CODE(RCODE_NXDOMAIN);
                }
            }
            else
            {
                if(label == NULL)
                {
                    free_rrsets(&rrsets);
                    return SERVER_ERROR_CODE(RCODE_NXRRSET);
                }

                if(zdb_record_find(&label->resource_record_set, rtype) == NULL)
                {
                    free_rrsets(&rrsets);
                    return SERVER_ERROR_CODE(RCODE_NXRRSET);
                }
            }
        }
        else if(rclass == CLASS_NONE)
        {
            if(rdata_size != 0)
            {
                free_rrsets(&rrsets);
                return SERVER_ERROR_CODE(RCODE_FORMERR);
            }

            zdb_rr_label* label = zdb_rr_label_find_exact(zone->apex, name_path.labels, (name_path.size - origin_path.size) - 1);

            if(rtype == TYPE_ANY)
            {
                if(label != NULL)
                {
                    free_rrsets(&rrsets);
                    return SERVER_ERROR_CODE(RCODE_YXDOMAIN);
                }
            }
            else
            {
                if(label != NULL)
                {
                    if(zdb_record_find(&label->resource_record_set, rtype) != NULL)
                    {
                        free_rrsets(&rrsets);
                        return SERVER_ERROR_CODE(RCODE_YXRRSET);
                    }
                }
            }
        }
        else if(rclass == zdb_zone_getclass(zone))
        {
            name_type_rdata* item;
            MALLOC_OR_DIE(name_type_rdata*, item, sizeof (name_type_rdata), ZDB_DYNUPDATE_TAG);
            item->rname = rname;
            item->rdata = rdata;
            item->rtype = rtype;
            item->rdata_size = rdata_size;
            ptr_vector_append(&rrsets, item);
        }
        else
        {
            free_rrsets(&rrsets);
            return SERVER_ERROR_CODE(RCODE_FORMERR);
        }
    }
    
    ptr_vector_qsort(&rrsets, name_type_rdata_compare);

    /*
     * Get the first name.
     * While the next records have this name ...
     *   Get the first type
     *   while the next records have this type ...
     *      Test that the record has a match
     *      Decrement the match count (dual record queries are an error)
     *
     */

    if(rrsets.offset >= 0)
    {
        zdb_rr_label* label = NULL;
        zdb_packed_ttlrdata* rr_sll = NULL;

        u8* last_name = (u8*)"\0377";
        u16 last_type = 0;
        s32 required_matches = 0;

        name_type_rdata** itemp;
        s32 count = rrsets.offset; /* count - 1 actually */
        itemp = (name_type_rdata**)rrsets.data;

        while(count >= 0)
        {
            name_type_rdata* item = *itemp++;

            if(!dnsname_equals(item->rname, last_name))
            {
                if(required_matches != 0)
                {
                    free_rrsets(&rrsets);
                    return SERVER_ERROR_CODE(RCODE_NXRRSET);
                }

                last_name = item->rname;

                /*
                 * It's a new name: get the rr_label
                 */

                dnsname_to_dnsname_vector(item->rname, &name_path);

                label = zdb_rr_label_find_exact(zone->apex, name_path.labels, (name_path.size - origin_path.size) - 1);

                last_type = 0; // forces the next test
            }

            if(last_type != item->rtype)
            {
                if(required_matches != 0)
                {
                    free_rrsets(&rrsets);
                    return SERVER_ERROR_CODE(RCODE_NXRRSET);
                }

                last_type = item->rtype;

                /*
                 * get the type's rr list
                 * compute the size of the list
                 */

                rr_sll = zdb_record_find(&label->resource_record_set, last_type);

                required_matches = 0;

                zdb_packed_ttlrdata* rr = rr_sll;

                while(rr != NULL)
                {
                    required_matches++;

                    rr = rr->next;
                }
            }

            /*
             * check that the rdata exists
             *
             * if not: break
             *
             * if yes: decrement the counter
             */

            zdb_packed_ttlrdata* rr = rr_sll;

            while(rr != NULL)
            {
                if(rr->rdata_size == item->rdata_size)
                {
                    /**
                     * @todo: This should be done by a type-aware comparator.
                     *        Sometimes case (in)sensitivity is important.
                     *
                     *        Do I have to assume that, as for the zone load, the RDATA are low-cased where allowed ?
                     */

                    if(memcmp(&rr->rdata_start[0], item->rdata, item->rdata_size) == 0)
                    {
                        /*
                         * match
                         */

                        required_matches--;
                        break;
                    }
                }

                rr = rr->next;
            }

            if(rr == NULL)
            {
                /*
                 * no match
                 */

                free_rrsets(&rrsets);
                return SERVER_ERROR_CODE(RCODE_NXRRSET);
            }
        }
    }

    free_rrsets(&rrsets);

    return reader->offset;
}

/*    ------------------------------------------------------------    */

/** @} */

/*----------------------------------------------------------------------------*/
