/* mrtrace.c

   Subroutines that support minires tracing... */

/*
 * Copyright (c) 2004,2007,2009 by Internet Systems Consortium, Inc. ("ISC")
 * Copyright (c) 2001-2003 by 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.
 *
 *   Internet Systems Consortium, Inc.
 *   950 Charter Street
 *   Redwood City, CA 94063
 *   <info@isc.org>
 *   https://www.isc.org/
 *
 * This software has been written for Internet Systems Consortium
 * by Ted Lemon, as part of a project for Nominum, Inc.   To learn more
 * about Internet Systems Consortium, see https://www.isc.org/.  To
 * learn more about Nominum, Inc., see ``http://www.nominum.com''.
 */

#include "dhcpd.h"
#include <omapip/omapip_p.h>

#include "minires/minires.h"
#include "arpa/nameser.h"

#include <errno.h>

#if defined(TRACING)
static void trace_mr_output_input (trace_type_t *, unsigned, char *);
static void trace_mr_output_stop (trace_type_t *);
static void trace_mr_input_input (trace_type_t *, unsigned, char *);
static void trace_mr_input_stop (trace_type_t *);
static void trace_mr_statp_input (trace_type_t *, unsigned, char *);
static void trace_mr_statp_stop (trace_type_t *);
static void trace_mr_randomid_input (trace_type_t *, unsigned, char *);
static void trace_mr_randomid_stop (trace_type_t *);
#endif /* TRACING */
trace_type_t *trace_mr_output;
trace_type_t *trace_mr_input;
trace_type_t *trace_mr_statp;
trace_type_t *trace_mr_randomid;
ssize_t trace_mr_send (int, void *, size_t, int);
ssize_t trace_mr_read_playback (struct sockaddr_in *, void *, size_t);
void trace_mr_read_record (struct sockaddr_in *, void *, ssize_t);
ssize_t trace_mr_recvfrom (int s, void *, size_t, int,
			   struct sockaddr *, SOCKLEN_T *);
ssize_t trace_mr_read (int, void *, size_t);
int trace_mr_connect (int s, struct sockaddr *, SOCKLEN_T);
int trace_mr_socket (int, int, int);
int trace_mr_bind (int, struct sockaddr *, SOCKLEN_T);
int trace_mr_close (int);
time_t trace_mr_time (time_t *);
int trace_mr_select (int, fd_set *, fd_set *, fd_set *, struct timeval *);
unsigned int trace_mr_res_randomid (unsigned int);

#if defined (TRACING)
void trace_mr_init ()
{
	trace_mr_output = trace_type_register ("mr-output", (void *)0,
					       trace_mr_output_input,
					       trace_mr_output_stop, MDL);
	trace_mr_input = trace_type_register ("mr-input", (void *)0,
					      trace_mr_input_input,
					      trace_mr_input_stop, MDL);
	trace_mr_statp = trace_type_register ("mr-statp", (void *)0,
					      trace_mr_statp_input,
					      trace_mr_statp_stop, MDL);
	trace_mr_randomid = trace_type_register ("mr-randomid", (void *)0,
						 trace_mr_randomid_input,
						 trace_mr_randomid_stop, MDL);
}

void trace_mr_statp_setup (res_state statp)
{
	unsigned buflen = 0;
	char *buf = (char *)0;
	isc_result_t status;
	int i;

	if (trace_playback ()) {
		int nscount;
		status = trace_get_packet (&trace_mr_statp, &buflen, &buf);
		if (status != ISC_R_SUCCESS) {
			log_error ("trace_mr_statp: no statp packet found.");
			return;
		}
		nscount = buflen / sizeof (struct in_addr);
		if (nscount * (sizeof (struct in_addr)) != buflen ||
		    nscount < 1) {
			log_error ("trace_mr_statp: bogus length: %d",
				   buflen);
			return;
		}
		if (nscount > MAXNS)
			nscount = MAXNS;
		for (i = 0; i < nscount; i++) {
#if defined (HAVE_SA_LEN)
			statp -> nsaddr_list [i].sin_len =
				sizeof (struct sockaddr_in);
#endif
			memset (&statp -> nsaddr_list [i].sin_zero, 0,
				sizeof statp -> nsaddr_list [i].sin_zero);
			statp -> nsaddr_list [i].sin_port = htons (53); /*XXX*/
			statp -> nsaddr_list [i].sin_family = AF_INET;
			memcpy (&statp -> nsaddr_list [i].sin_addr,
				(buf + i * (sizeof (struct in_addr))),
				sizeof (struct in_addr));
		}
		statp -> nscount = nscount;
		dfree (buf, MDL);
		buf = (char *)0;
	}
	if (trace_record ()) {
		trace_iov_t *iov;
		iov = dmalloc ((statp -> nscount *
				sizeof (trace_iov_t)), MDL);
		if (!iov) {
			trace_stop ();
			log_error ("No memory for statp iov.");
			return;
		}
		for (i = 0; i < statp -> nscount; i++) {
			iov [i].buf =
				(char *)&statp -> nsaddr_list [i].sin_addr;
			iov [i].len = sizeof (struct in_addr);
		}
		trace_write_packet_iov (trace_mr_statp, i, iov, MDL);
		dfree (iov, MDL);
	}
}
#endif

ssize_t trace_mr_send (int fd, void *msg, size_t len, int flags)
{
	ssize_t rv;
#if defined (TRACING)
	isc_result_t status;
	unsigned buflen = 0;
	char *inbuf = (char *)0;
	u_int32_t result;
	u_int32_t sflags;

	if (trace_playback()) {
		status = trace_get_packet (&trace_mr_output, &buflen, &inbuf);
		if (status != ISC_R_SUCCESS) {
			log_error ("trace_mr_recvfrom: no input found.");
			errno = ECONNREFUSED;
			return -1;
		}
		if (buflen < sizeof result) {
			log_error ("trace_mr_recvfrom: data too short.");
			errno = ECONNREFUSED;
			dfree (inbuf, MDL);
			return -1;
		}
		memcpy (&result, inbuf, sizeof result);
		rv = ntohl (result);
		dfree (inbuf, MDL);
	} else
#endif
		rv = send (fd, msg, len, flags);
#if defined (TRACING)
	if (trace_record ()) {
		trace_iov_t iov [3];
		result = htonl (rv);
		sflags = htonl (flags);
		iov [0].len = sizeof result;
		iov [0].buf = (char *)&result;
		iov [1].len = sizeof sflags;
		iov [1].buf = (char *)&flags;
		iov [2].len = len;
		iov [2].buf = msg;
		trace_write_packet_iov (trace_mr_output, 3, iov, MDL);
	}
#endif
	return rv;
}

#if defined (TRACING)
ssize_t trace_mr_read_playback (struct sockaddr_in *from,
				void *buf, size_t nbytes)
{
	isc_result_t status;
	unsigned buflen = 0, left;
	char *inbuf = (char *)0;
	char *bufp;
	u_int32_t result;

	status = trace_get_packet (&trace_mr_input, &buflen, &inbuf);
	if (status != ISC_R_SUCCESS) {
		log_error ("trace_mr_recvfrom: no input found.");
		errno = ECONNREFUSED;
		return -1;
	}
	if (buflen < sizeof result) {
		log_error ("trace_mr_recvfrom: data too short.");
		errno = ECONNREFUSED;
		dfree (inbuf, MDL);
		return -1;
	}
	bufp = inbuf;
	left = buflen;
	memcpy (&result, bufp, sizeof result);
	result = ntohl (result);
	bufp += sizeof result;
	left -= sizeof result;
	if (result == 0) {
		if (left < ((sizeof from -> sin_port) +
			    sizeof (from -> sin_addr))) {
			log_error ("trace_mr_recvfrom: data too short.");
			errno = ECONNREFUSED;
			dfree (inbuf, MDL);
			return -1;
		}
		if (from)
			memcpy (&from -> sin_addr, bufp,
				sizeof from -> sin_addr);
		bufp += sizeof from -> sin_addr;
		left -= sizeof from -> sin_addr;
		if (from)
			memcpy (&from -> sin_port, bufp,
				sizeof from -> sin_port);
		bufp += sizeof from -> sin_port;
		left -= sizeof from -> sin_port;
		if (from) {
			from -> sin_family = AF_INET;
#if defined(HAVE_SA_LEN)
			from -> sin_len = sizeof (struct sockaddr_in);
#endif
			memset (from -> sin_zero, 0, sizeof from -> sin_zero);
		}
		if (left > nbytes) {
			log_error ("trace_mr_recvfrom: too much%s",
				   " data.");
			errno = ECONNREFUSED;
			dfree (inbuf, MDL);
			return -1;
		}
		memcpy (buf, bufp, left);
		dfree (inbuf, MDL);
		return left;
	}
	errno = ECONNREFUSED;
	return -1;
}

void trace_mr_read_record (struct sockaddr_in *from, void *buf, ssize_t rv)
{
	trace_iov_t iov [4];
	u_int32_t result;
	int iolen = 0;
	static char zero [4] = { 0, 0, 0, 0 };
	
	if (rv < 0)
		result = htonl (errno);		/* XXX */
	else
		result = 0;
	iov [iolen].buf = (char *)&result;
	iov [iolen++].len = sizeof result;
	if (rv > 0) {
		if (from) {
			iov [iolen].buf = (char *)&from -> sin_addr;
			iov [iolen++].len = sizeof from -> sin_addr;
			iov [iolen].buf = (char *)&from -> sin_port;
			iov [iolen++].len = sizeof from -> sin_port;
		} else {
			iov [iolen].buf = zero;
			iov [iolen++].len = sizeof from -> sin_addr;
			iov [iolen].buf = zero;
			iov [iolen++].len = sizeof from -> sin_port;
		}

		iov [iolen].buf = buf;
		iov [iolen++].len = rv;
	}
	trace_write_packet_iov (trace_mr_input, iolen, iov, MDL);
}
#endif

ssize_t trace_mr_recvfrom (int s, void *buf, size_t len, int flags,
			   struct sockaddr *from, SOCKLEN_T *fromlen)
{
	ssize_t rv;

#if defined (TRACING)
	if (trace_playback ())
		rv = trace_mr_read_playback ((struct sockaddr_in *)from,
					     buf, len);
	else
#endif
		rv = recvfrom (s, buf, len, flags, from, fromlen);
#if defined (TRACING)
	if (trace_record ()) {
		trace_mr_read_record ((struct sockaddr_in *)from, buf, rv);
	}
#endif
	return rv;
}

ssize_t trace_mr_read (int d, void *buf, size_t nbytes)
{
	ssize_t rv;

#if defined (TRACING)
	if (trace_playback ())
		rv = trace_mr_read_playback ((struct sockaddr_in *)0,
					     buf, nbytes);
	else
#endif
		rv = read (d, buf, nbytes);
#if defined (TRACING)
	if (trace_record ()) {
		trace_mr_read_record ((struct sockaddr_in *)0, buf, rv);
	}
#endif
	return rv;
}

int trace_mr_connect (int s, struct sockaddr *name, SOCKLEN_T namelen)
{
#if defined (TRACING)
	if (!trace_playback ())
#endif
		return connect (s, name, namelen);
#if defined (TRACING)
	return 0;
#endif
}

int trace_mr_socket (int domain, int type, int protocol)
{
#if defined (TRACING)
	if (!trace_playback ())
#endif
		return socket (domain, type, protocol);
#if defined (TRACING)
	return 100;
#endif
}

int trace_mr_bind (int s, struct sockaddr *name, SOCKLEN_T namelen)
{
#if defined (TRACING)
	if (!trace_playback ())
#endif
		return bind (s, name, namelen);
#if defined (TRACING)
	return 0;
#endif
}

int trace_mr_close (int s)
{
#if defined (TRACING)
	if (!trace_playback ())
#endif
		return close (s);
#if defined (TRACING)
	return 0;
#endif
}

time_t trace_mr_time (time_t *tp)
{
#if defined (TRACING)
	if (trace_playback ()) {
		if (tp)
			*tp = cur_time;
		return cur_time;
	}
#endif
	return time (tp);
}

int trace_mr_select (int s, fd_set *r, fd_set *w, fd_set *x, struct timeval *t)
{
#if defined (TRACING)
	trace_type_t *ttp = (trace_type_t *)0;

	if (trace_playback ()) {
		time_t nct = trace_snoop_time (&ttp);
		time_t secr = t -> tv_sec;
		t -> tv_sec = nct - cur_time;
		if (t -> tv_sec > secr)
			return 0;
		if (ttp == trace_mr_input)
			return 1;
		return 0;
	}
#endif
	return select (s, r, w, x, t);
}

unsigned int trace_mr_res_randomid (unsigned int oldid)
{
	int rid = oldid;
#if defined (TRACING)
	u_int32_t id;
	unsigned buflen = 0;
	char *buf = (char *)0;
	isc_result_t status;

	if (trace_playback ()) {
		status = trace_get_packet (&trace_mr_randomid, &buflen, &buf);
		if (status != ISC_R_SUCCESS) {
			log_error ("trace_mr_statp: no statp packet found.");
			return oldid;
		}
		if (buflen != sizeof id) {
			log_error ("trace_mr_randomid: bogus length: %d",
				   buflen);
			return oldid;
		}
		memcpy (&id, buf, sizeof id);
		dfree (buf, MDL);
		buf = (char *)0;
		rid = ntohl (id);
	}
	if (trace_record ()) {
		id = htonl (rid);
		trace_write_packet (trace_mr_randomid,
				    sizeof id, (char *)&id, MDL);
	}
#endif
	return rid;
}

#if defined (TRACING)
static void trace_mr_output_input (trace_type_t *ttype,
				   unsigned length, char *buf)
{
}

static void trace_mr_output_stop (trace_type_t *ttype)
{
}

static void trace_mr_input_input (trace_type_t *ttype,
				  unsigned length, char *buf)
{
	log_error ("unaccounted-for minires input.");
}

static void trace_mr_input_stop (trace_type_t *ttype)
{
}

static void trace_mr_statp_input (trace_type_t *ttype,
				  unsigned length, char *buf)
{
	log_error ("unaccounted-for minires statp input.");
}

static void trace_mr_statp_stop (trace_type_t *ttype)
{
}

static void trace_mr_randomid_input (trace_type_t *ttype,
				     unsigned length, char *buf)
{
	log_error ("unaccounted-for minires randomid input.");
}

static void trace_mr_randomid_stop (trace_type_t *ttype)
{
}
#endif
