/* generic.c

   Subroutines that support the generic object. */

/*
 * Copyright (C) 2004-2022 Internet Systems Consortium, Inc. ("ISC")
 * Copyright (c) 1999-2003 by Internet Software Consortium
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * 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.
 *
 *   Internet Systems Consortium, Inc.
 *   PO Box 360
 *   Newmarket, NH 03857 USA
 *   <info@isc.org>
 *   https://www.isc.org/
 *
 */

#include "dhcpd.h"

#include <omapip/omapip_p.h>

OMAPI_OBJECT_ALLOC (omapi_generic,
		    omapi_generic_object_t, omapi_type_generic)

isc_result_t omapi_generic_new (omapi_object_t **gen,
				const char *file, int line)
{
	/* Backwards compatibility. */
	return omapi_generic_allocate ((omapi_generic_object_t **)gen,
				       file, line);
}

isc_result_t omapi_generic_set_value (omapi_object_t *h,
				      omapi_object_t *id,
				      omapi_data_string_t *name,
				      omapi_typed_data_t *value)
{
	omapi_generic_object_t *g;
	omapi_value_t *new;
	omapi_value_t **va;
	u_int8_t *ca;
	int vm_new;
	int i, vfree = -1;
	isc_result_t status;

	if (h -> type != omapi_type_generic)
		return DHCP_R_INVALIDARG;
	g = (omapi_generic_object_t *)h;

	/* See if there's already a value with this name attached to
	   the generic object, and if so, replace the current value
	   with the new one. */
	for (i = 0; i < g -> nvalues; i++) {
		if (!g -> values[i])
			continue;

		if (!omapi_data_string_cmp (name, g -> values [i] -> name)) {
			/* There's an inconsistency here: the standard
			   behaviour of a set_values method when
			   passed a matching name and a null value is
			   to delete the value associated with that
			   name (where possible).  In the generic
			   object, we remember the name/null pair,
			   because generic objects are generally used
			   to pass messages around, and this is the
			   way that remote entities delete values from
			   local objects.  If the get_value method of
			   a generic object is called for a name that
			   maps to a name/null pair, ISC_R_NOTFOUND is
			   returned. */
			new = (omapi_value_t *)0;
			status = (omapi_value_new (&new, MDL));
			if (status != ISC_R_SUCCESS)
				return status;
			omapi_data_string_reference (&new -> name, name, MDL);
			if (value)
				omapi_typed_data_reference (&new -> value,
							    value, MDL);

			omapi_value_dereference (&(g -> values [i]), MDL);
			status = (omapi_value_reference
				  (&(g -> values [i]), new, MDL));
			omapi_value_dereference (&new, MDL);
			g -> changed [i] = 1;
			return status;
		}
		/* Notice a free slot if we pass one. */
		else if (vfree == -1 && !g -> values [i])
			vfree = i;
	}

	/* If the name isn't already attached to this object, see if an
	   inner object has it. */
	if (h -> inner && h -> inner -> type -> set_value) {
		status = ((*(h -> inner -> type -> set_value))
			  (h -> inner, id, name, value));
		if (status != ISC_R_NOTFOUND)
			return status;
	}

	/* Okay, so it's a value that no inner object knows about, and
	   (implicitly, since the outer object set_value method would
	   have called this object's set_value method) it's an object that
	   no outer object knows about, it's this object's responsibility
	   to remember it - that's what generic objects do. */

	/* Arrange for there to be space for the pointer to the new
           name/value pair if necessary: */
	if (vfree == -1) {
		vfree = g -> nvalues;
		if (vfree == g -> va_max) {
			if (g -> va_max)
				vm_new = 2 * g -> va_max;
			else
				vm_new = 10;
			va = dmalloc (vm_new * sizeof *va, MDL);
			if (!va)
				return ISC_R_NOMEMORY;
			ca = dmalloc (vm_new * sizeof *ca, MDL);
			if (!ca) {
				dfree (va, MDL);
				return ISC_R_NOMEMORY;
			}
			if (g -> va_max) {
				memcpy (va, g -> values,
					g -> va_max * sizeof *va);
				memcpy (ca, g -> changed,
					g -> va_max * sizeof *ca);
			}
			memset (va + g -> va_max, 0,
				(vm_new - g -> va_max) * sizeof *va);
			memset (ca + g -> va_max, 0,
				(vm_new - g -> va_max) * sizeof *ca);
			if (g -> values)
				dfree (g -> values, MDL);
			if (g -> changed)
				dfree (g -> changed, MDL);
			g -> values = va;
			g -> changed = ca;
			g -> va_max = vm_new;
		}
	}
	status = omapi_value_new (&g -> values [vfree], MDL);
	if (status != ISC_R_SUCCESS)
		return status;
	omapi_data_string_reference (&g -> values [vfree] -> name,
				     name, MDL);
	if (value)
		omapi_typed_data_reference
			(&g -> values [vfree] -> value, value, MDL);
	g -> changed [vfree] = 1;
	if (vfree == g -> nvalues)
		g -> nvalues++;
	return ISC_R_SUCCESS;
}

isc_result_t omapi_generic_get_value (omapi_object_t *h,
				      omapi_object_t *id,
				      omapi_data_string_t *name,
				      omapi_value_t **value)
{
	int i;
	omapi_generic_object_t *g;

	if (h -> type != omapi_type_generic)
		return DHCP_R_INVALIDARG;
	g = (omapi_generic_object_t *)h;

	/* Look up the specified name in our list of objects. */
	for (i = 0; i < g -> nvalues; i++) {
		if (!g -> values[i])
			continue;
		if (!omapi_data_string_cmp (name, g -> values [i] -> name)) {
			/* If this is a name/null value pair, this is the
			   same as if there were no value that matched
			   the specified name, so return ISC_R_NOTFOUND. */
			if (!g -> values [i] -> value)
				return ISC_R_NOTFOUND;
			/* Otherwise, return the name/value pair. */
			return omapi_value_reference (value,
						      g -> values [i], MDL);
		}
	}

	if (h -> inner && h -> inner -> type -> get_value)
		return (*(h -> inner -> type -> get_value))
			(h -> inner, id, name, value);
	return ISC_R_NOTFOUND;
}

isc_result_t omapi_generic_destroy (omapi_object_t *h,
				    const char *file, int line)
{
	omapi_generic_object_t *g;
	int i;

	if (h -> type != omapi_type_generic)
		return ISC_R_UNEXPECTED;
	g = (omapi_generic_object_t *)h;

	if (g -> values) {
		for (i = 0; i < g -> nvalues; i++) {
			if (g -> values [i])
				omapi_value_dereference (&g -> values [i],
							 file, line);
		}
		dfree (g -> values, file, line);
		dfree (g -> changed, file, line);
		g -> values = (omapi_value_t **)0;
		g -> changed = (u_int8_t *)0;
		g -> va_max = 0;
	}

	return ISC_R_SUCCESS;
}

isc_result_t omapi_generic_signal_handler (omapi_object_t *h,
					   const char *name, va_list ap)
{
	if (h -> type != omapi_type_generic)
		return DHCP_R_INVALIDARG;

	if (h -> inner && h -> inner -> type -> signal_handler)
		return (*(h -> inner -> type -> signal_handler)) (h -> inner,
								  name, ap);
	return ISC_R_NOTFOUND;
}

/* Write all the published values associated with the object through the
   specified connection. */

isc_result_t omapi_generic_stuff_values (omapi_object_t *c,
					 omapi_object_t *id,
					 omapi_object_t *g)
{
	omapi_generic_object_t *src;
	int i;
	isc_result_t status;

	if (g -> type != omapi_type_generic)
		return DHCP_R_INVALIDARG;
	src = (omapi_generic_object_t *)g;

	for (i = 0; i < src -> nvalues; i++) {
		if (src -> values [i] && src -> values [i] -> name -> len &&
		    src -> changed [i]) {
			status = (omapi_connection_put_uint16
				  (c, src -> values [i] -> name -> len));
			if (status != ISC_R_SUCCESS)
				return status;
			status = (omapi_connection_copyin
				  (c, src -> values [i] -> name -> value,
				   src -> values [i] -> name -> len));
			if (status != ISC_R_SUCCESS)
				return status;

			status = (omapi_connection_write_typed_data
				  (c, src -> values [i] -> value));
			if (status != ISC_R_SUCCESS)
				return status;
		}
	}

	if (g -> inner && g -> inner -> type -> stuff_values)
		return (*(g -> inner -> type -> stuff_values)) (c, id,
								g -> inner);
	return ISC_R_SUCCESS;
}

/* Clear the changed flags on the object.   This has the effect that if
   generic_stuff is called, any attributes that still have a cleared changed
   flag aren't sent to the peer.   This also deletes any values that are
   null, presuming that these have now been properly handled. */

isc_result_t omapi_generic_clear_flags (omapi_object_t *o)
{
	int i;
	omapi_generic_object_t *g;

	if (o -> type != omapi_type_generic)
		return DHCP_R_INVALIDARG;
	g = (omapi_generic_object_t *)o;

	for (i = 0; i < g -> nvalues; i++) {
		g -> changed [i] = 0;
		if (g -> values [i] &&
		    !g -> values [i] -> value)
			omapi_value_dereference (&g -> values [i], MDL);
	}
	return ISC_R_SUCCESS;
}
