/*
 * Copyright (C) 2004  Internet Systems Consortium, Inc. ("ISC")
 * Copyright (C) 2000, 2001  Internet Software Consortium.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

/* $Id: lwres_grbn.c,v 1.4.12.3 2004/03/08 09:05:11 marka Exp $ */

#include <config.h>

#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include <lwres/lwbuffer.h>
#include <lwres/lwpacket.h>
#include <lwres/lwres.h>
#include <lwres/result.h>

#include "context_p.h"
#include "assert_p.h"

lwres_result_t
lwres_grbnrequest_render(lwres_context_t *ctx, lwres_grbnrequest_t *req,
			 lwres_lwpacket_t *pkt, lwres_buffer_t *b)
{
	unsigned char *buf;
	size_t buflen;
	int ret;
	size_t payload_length;
	lwres_uint16_t datalen;

	REQUIRE(ctx != NULL);
	REQUIRE(req != NULL);
	REQUIRE(req->name != NULL);
	REQUIRE(pkt != NULL);
	REQUIRE(b != NULL);

	datalen = strlen(req->name);

	payload_length = 4 + 2 + 2 + 2 + req->namelen + 1;

	buflen = LWRES_LWPACKET_LENGTH + payload_length;
	buf = CTXMALLOC(buflen);
	if (buf == NULL)
		return (LWRES_R_NOMEMORY);

	lwres_buffer_init(b, buf, buflen);

	pkt->length = buflen;
	pkt->version = LWRES_LWPACKETVERSION_0;
	pkt->pktflags &= ~LWRES_LWPACKETFLAG_RESPONSE;
	pkt->opcode = LWRES_OPCODE_GETRDATABYNAME;
	pkt->result = 0;
	pkt->authtype = 0;
	pkt->authlength = 0;

	ret = lwres_lwpacket_renderheader(b, pkt);
	if (ret != LWRES_R_SUCCESS) {
		lwres_buffer_invalidate(b);
		CTXFREE(buf, buflen);
		return (ret);
	}

	INSIST(SPACE_OK(b, payload_length));

	/*
	 * Flags.
	 */
	lwres_buffer_putuint32(b, req->flags);

	/*
	 * Class.
	 */
	lwres_buffer_putuint16(b, req->rdclass);

	/*
	 * Type.
	 */
	lwres_buffer_putuint16(b, req->rdtype);

	/*
	 * Put the length and the data.  We know this will fit because we
	 * just checked for it.
	 */
	lwres_buffer_putuint16(b, datalen);
	lwres_buffer_putmem(b, (unsigned char *)req->name, datalen);
	lwres_buffer_putuint8(b, 0); /* trailing NUL */

	INSIST(LWRES_BUFFER_AVAILABLECOUNT(b) == 0);

	return (LWRES_R_SUCCESS);
}

lwres_result_t
lwres_grbnresponse_render(lwres_context_t *ctx, lwres_grbnresponse_t *req,
			  lwres_lwpacket_t *pkt, lwres_buffer_t *b)
{
	unsigned char *buf;
	size_t buflen;
	int ret;
	size_t payload_length;
	lwres_uint16_t datalen;
	int x;

	REQUIRE(ctx != NULL);
	REQUIRE(req != NULL);
	REQUIRE(pkt != NULL);
	REQUIRE(b != NULL);

	/* flags, class, type, ttl, nrdatas, nsigs */
	payload_length = 4 + 2 + 2 + 4 + 2 + 2;
	/* real name encoding */
	payload_length += 2 + req->realnamelen + 1;
	/* each rr */
	for (x = 0; x < req->nrdatas; x++)
		payload_length += 2 + req->rdatalen[x];
	for (x = 0; x < req->nsigs; x++)
		payload_length += 2 + req->siglen[x];

	buflen = LWRES_LWPACKET_LENGTH + payload_length;
	buf = CTXMALLOC(buflen);
	if (buf == NULL)
		return (LWRES_R_NOMEMORY);
	lwres_buffer_init(b, buf, buflen);

	pkt->length = buflen;
	pkt->version = LWRES_LWPACKETVERSION_0;
	pkt->pktflags |= LWRES_LWPACKETFLAG_RESPONSE;
	pkt->opcode = LWRES_OPCODE_GETRDATABYNAME;
	pkt->authtype = 0;
	pkt->authlength = 0;

	ret = lwres_lwpacket_renderheader(b, pkt);
	if (ret != LWRES_R_SUCCESS) {
		lwres_buffer_invalidate(b);
		CTXFREE(buf, buflen);
		return (ret);
	}

	/*
	 * Check space needed here.
	 */
	INSIST(SPACE_OK(b, payload_length));

	/* Flags. */
	lwres_buffer_putuint32(b, req->flags);

	/* encode class, type, ttl, and nrdatas */
	lwres_buffer_putuint16(b, req->rdclass);
	lwres_buffer_putuint16(b, req->rdtype);
	lwres_buffer_putuint32(b, req->ttl);
	lwres_buffer_putuint16(b, req->nrdatas);
	lwres_buffer_putuint16(b, req->nsigs);

	/* encode the real name */
	datalen = req->realnamelen;
	lwres_buffer_putuint16(b, datalen);
	lwres_buffer_putmem(b, (unsigned char *)req->realname, datalen);
	lwres_buffer_putuint8(b, 0);

	/* encode the rdatas */
	for (x = 0; x < req->nrdatas; x++) {
		datalen = req->rdatalen[x];
		lwres_buffer_putuint16(b, datalen);
		lwres_buffer_putmem(b, req->rdatas[x], datalen);
	}

	/* encode the signatures */
	for (x = 0; x < req->nsigs; x++) {
		datalen = req->siglen[x];
		lwres_buffer_putuint16(b, datalen);
		lwres_buffer_putmem(b, req->sigs[x], datalen);
	}

	INSIST(LWRES_BUFFER_AVAILABLECOUNT(b) == 0);
	INSIST(LWRES_BUFFER_USEDCOUNT(b) == pkt->length);

	return (LWRES_R_SUCCESS);
}

lwres_result_t
lwres_grbnrequest_parse(lwres_context_t *ctx, lwres_buffer_t *b,
			lwres_lwpacket_t *pkt, lwres_grbnrequest_t **structp)
{
	int ret;
	char *name;
	lwres_grbnrequest_t *grbn;
	lwres_uint32_t flags;
	lwres_uint16_t rdclass, rdtype;
	lwres_uint16_t namelen;

	REQUIRE(ctx != NULL);
	REQUIRE(pkt != NULL);
	REQUIRE(b != NULL);
	REQUIRE(structp != NULL && *structp == NULL);

	if ((pkt->pktflags & LWRES_LWPACKETFLAG_RESPONSE) != 0)
		return (LWRES_R_FAILURE);

	if (!SPACE_REMAINING(b, 4 + 2 + 2))
		return (LWRES_R_UNEXPECTEDEND);

	/*
	 * Pull off the flags, class, and type.
	 */
	flags = lwres_buffer_getuint32(b);
	rdclass = lwres_buffer_getuint16(b);
	rdtype = lwres_buffer_getuint16(b);

	/*
	 * Pull off the name itself
	 */
	ret = lwres_string_parse(b, &name, &namelen);
	if (ret != LWRES_R_SUCCESS)
		return (ret);

	if (LWRES_BUFFER_REMAINING(b) != 0)
		return (LWRES_R_TRAILINGDATA);

	grbn = CTXMALLOC(sizeof(lwres_grbnrequest_t));
	if (grbn == NULL)
		return (LWRES_R_NOMEMORY);

	grbn->flags = flags;
	grbn->rdclass = rdclass;
	grbn->rdtype = rdtype;
	grbn->name = name;
	grbn->namelen = namelen;

	*structp = grbn;
	return (LWRES_R_SUCCESS);
}

lwres_result_t
lwres_grbnresponse_parse(lwres_context_t *ctx, lwres_buffer_t *b,
			lwres_lwpacket_t *pkt, lwres_grbnresponse_t **structp)
{
	lwres_result_t ret;
	unsigned int x;
	lwres_uint32_t flags;
	lwres_uint16_t rdclass, rdtype;
	lwres_uint32_t ttl;
	lwres_uint16_t nrdatas, nsigs;
	lwres_grbnresponse_t *grbn;

	REQUIRE(ctx != NULL);
	REQUIRE(pkt != NULL);
	REQUIRE(b != NULL);
	REQUIRE(structp != NULL && *structp == NULL);

	grbn = NULL;

	if ((pkt->pktflags & LWRES_LWPACKETFLAG_RESPONSE) == 0)
		return (LWRES_R_FAILURE);

	/*
	 * Pull off the flags, class, type, ttl, nrdatas, and nsigs
	 */
	if (!SPACE_REMAINING(b, 4 + 2 + 2 + 4 + 2 + 2))
		return (LWRES_R_UNEXPECTEDEND);
	flags = lwres_buffer_getuint32(b);
	rdclass = lwres_buffer_getuint16(b);
	rdtype = lwres_buffer_getuint16(b);
	ttl = lwres_buffer_getuint32(b);
	nrdatas = lwres_buffer_getuint16(b);
	nsigs = lwres_buffer_getuint16(b);

	/*
	 * Pull off the name itself
	 */

	grbn = CTXMALLOC(sizeof(lwres_grbnresponse_t));
	if (grbn == NULL)
		return (LWRES_R_NOMEMORY);
	grbn->rdatas = NULL;
	grbn->rdatalen = NULL;
	grbn->sigs = NULL;
	grbn->siglen = NULL;
	grbn->base = NULL;

	grbn->flags = flags;
	grbn->rdclass = rdclass;
	grbn->rdtype = rdtype;
	grbn->ttl = ttl;
	grbn->nrdatas = nrdatas;
	grbn->nsigs = nsigs;

	if (nrdatas > 0) {
		grbn->rdatas = CTXMALLOC(sizeof(char *) * nrdatas);
		if (grbn->rdatas == NULL) {
			ret = LWRES_R_NOMEMORY;
			goto out;
		}

		grbn->rdatalen = CTXMALLOC(sizeof(lwres_uint16_t) * nrdatas);
		if (grbn->rdatalen == NULL) {
			ret = LWRES_R_NOMEMORY;
			goto out;
		}
	}

	if (nsigs > 0) {
		grbn->sigs = CTXMALLOC(sizeof(char *) * nsigs);
		if (grbn->sigs == NULL) {
			ret = LWRES_R_NOMEMORY;
			goto out;
		}

		grbn->siglen = CTXMALLOC(sizeof(lwres_uint16_t) * nsigs);
		if (grbn->siglen == NULL) {
			ret = LWRES_R_NOMEMORY;
			goto out;
		}
	}

	/*
	 * Now, pull off the real name.
	 */
	ret = lwres_string_parse(b, &grbn->realname, &grbn->realnamelen);
	if (ret != LWRES_R_SUCCESS)
		goto out;

	/*
	 * Parse off the rdatas.
	 */
	for (x = 0; x < grbn->nrdatas; x++) {
		ret = lwres_data_parse(b, &grbn->rdatas[x],
					 &grbn->rdatalen[x]);
		if (ret != LWRES_R_SUCCESS)
			goto out;
	}

	/*
	 * Parse off the signatures.
	 */
	for (x = 0; x < grbn->nsigs; x++) {
		ret = lwres_data_parse(b, &grbn->sigs[x], &grbn->siglen[x]);
		if (ret != LWRES_R_SUCCESS)
			goto out;
	}

	if (LWRES_BUFFER_REMAINING(b) != 0) {
		ret = LWRES_R_TRAILINGDATA;
		goto out;
	}

	*structp = grbn;
	return (LWRES_R_SUCCESS);

 out:
	if (grbn != NULL) {
		if (grbn->rdatas != NULL)
			CTXFREE(grbn->rdatas, sizeof(char *) * nrdatas);
		if (grbn->rdatalen != NULL)
			CTXFREE(grbn->rdatalen,
				sizeof(lwres_uint16_t) * nrdatas);
		if (grbn->sigs != NULL)
			CTXFREE(grbn->sigs, sizeof(char *) * nsigs);
		if (grbn->siglen != NULL)
			CTXFREE(grbn->siglen, sizeof(lwres_uint16_t) * nsigs);
		CTXFREE(grbn, sizeof(lwres_grbnresponse_t));
	}

	return (ret);
}

void
lwres_grbnrequest_free(lwres_context_t *ctx, lwres_grbnrequest_t **structp)
{
	lwres_grbnrequest_t *grbn;

	REQUIRE(ctx != NULL);
	REQUIRE(structp != NULL && *structp != NULL);

	grbn = *structp;
	*structp = NULL;

	CTXFREE(grbn, sizeof(lwres_grbnrequest_t));
}

void
lwres_grbnresponse_free(lwres_context_t *ctx, lwres_grbnresponse_t **structp)
{
	lwres_grbnresponse_t *grbn;

	REQUIRE(ctx != NULL);
	REQUIRE(structp != NULL && *structp != NULL);

	grbn = *structp;
	*structp = NULL;

	if (grbn->nrdatas > 0) {
		CTXFREE(grbn->rdatas, sizeof(char *) * grbn->nrdatas);
		CTXFREE(grbn->rdatalen,
			sizeof(lwres_uint16_t) * grbn->nrdatas);
	}
	if (grbn->nsigs > 0) {
		CTXFREE(grbn->sigs, sizeof(char *) * grbn->nsigs);
		CTXFREE(grbn->siglen, sizeof(lwres_uint16_t) * grbn->nsigs);
	}
	if (grbn->base != NULL)
		CTXFREE(grbn->base, grbn->baselen);
	CTXFREE(grbn, sizeof(lwres_grbnresponse_t));
}
