/*	$NetBSD: qop_cdnr.c,v 1.4 2001/08/22 08:52:37 itojun Exp $	*/
/*	$KAME: qop_cdnr.c,v 1.9 2001/08/16 10:39:14 kjc Exp $	*/
/*
 * Copyright (C) 1999-2000
 *	Sony Computer Science Laboratories, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY SONY CSL 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 SONY CSL 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.
 */

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <syslog.h>
#include <netdb.h>

#include <altq/altq.h>
#include <altq/altq_cdnr.h>
#include "altq_qop.h"
#include "qop_cdnr.h"
/*
 * diffserve traffic conditioner support
 *
 * we use the existing qop interface to support conditioner.
 */

static struct ifinfo *cdnr_ifname2ifinfo(const char *);
static int cdnr_attach(struct ifinfo *);
static int cdnr_detach(struct ifinfo *);
static int cdnr_enable(struct ifinfo *);
static int cdnr_disable(struct ifinfo *);
static int cdnr_add_class(struct classinfo *);
static int cdnr_modify_class(struct classinfo *, void *);
static int cdnr_delete_class(struct classinfo *);
static int cdnr_add_filter(struct fltrinfo *);
static int cdnr_delete_filter(struct fltrinfo *);
static int verify_tbprofile(struct tb_profile *, const char *);

#define CDNR_DEVICE	"/dev/altq/cdnr"

static int cdnr_fd = -1;
static int cdnr_refcount = 0;

static struct qdisc_ops cdnr_qdisc = {
	ALTQT_CDNR,
	"cdnr",
	cdnr_attach,
	cdnr_detach,
	NULL,			/* clear */
	cdnr_enable,
	cdnr_disable,
	cdnr_add_class,
	cdnr_modify_class,
	cdnr_delete_class,
	cdnr_add_filter,
	cdnr_delete_filter,
};

u_long
cdnr_name2handle(const char *ifname, const char *cdnr_name)
{
	struct ifinfo		*ifinfo;
	struct classinfo	*clinfo;

	if ((ifinfo = cdnr_ifname2ifinfo(ifname)) == NULL)
		return (CDNR_NULL_HANDLE);

	if ((clinfo = clname2clinfo(ifinfo, cdnr_name)) == NULL)
		return (CDNR_NULL_HANDLE);

	return (clinfo->handle);
}

static struct ifinfo *
cdnr_ifname2ifinfo(const char *ifname)
{
	struct ifinfo	*ifinfo;
	char input_ifname[64];

	/*
	 * search for an existing input interface
	 */
	if ((ifinfo = input_ifname2ifinfo(ifname)) != NULL)
		return (ifinfo);

	/*
	 * if there is a corresponding output interface,
	 * create an input interface by prepending "_" to
	 * its name.
	 */
	if ((ifinfo = ifname2ifinfo(ifname)) == NULL)
		return (NULL);

	input_ifname[0] = '_';
	strlcpy(input_ifname+1, ifname, sizeof(input_ifname)-1);
	if (qop_add_if(&ifinfo, input_ifname, 0, &cdnr_qdisc, NULL) != 0) {
		LOG(LOG_ERR, errno,
		    "cdnr_ifname2ifinfo: can't add a input interface %s",
		    ifname);
		return (NULL);
	}
	return (ifinfo);
}

int
qcmd_cdnr_add_element(struct tc_action *rp, const char *ifname,
		   const char *cdnr_name, struct tc_action *action)
{
	struct ifinfo		*ifinfo;
	struct classinfo	*clinfo;
	int error;

	if ((ifinfo = cdnr_ifname2ifinfo(ifname)) == NULL)
		return (QOPERR_BADIF);

	if ((error = qop_cdnr_add_element(&clinfo, cdnr_name, ifinfo,
					  action)) != 0) {
		LOG(LOG_ERR, errno, "%s: add element failed!",
		    qoperror(error));
		return (error);
	}

	if (rp != NULL) {
		rp->tca_code = TCACODE_HANDLE;
		rp->tca_handle = clinfo->handle;
	}
	return (0);
}

int
qcmd_cdnr_add_tbmeter(struct tc_action *rp, const char *ifname,
		      const char *cdnr_name, 
		      struct tb_profile *profile,
		      struct tc_action *in_action,
		      struct tc_action *out_action)
{
	struct ifinfo		*ifinfo;
	struct classinfo	*clinfo;
	int error;

	if ((ifinfo = cdnr_ifname2ifinfo(ifname)) == NULL)
		return (QOPERR_BADIF);

	verify_tbprofile(profile, cdnr_name);

	if ((error = qop_cdnr_add_tbmeter(&clinfo, cdnr_name, ifinfo,
				  profile, in_action, out_action)) != 0) {
		LOG(LOG_ERR, errno, "%s: add tbmeter failed!",
		    qoperror(error));
		return (error);
	}
	
	if (rp != NULL) {
		rp->tca_code = TCACODE_HANDLE;
		rp->tca_handle = clinfo->handle;
	}
	return (0);
}

int
qcmd_cdnr_add_trtcm(struct tc_action *rp, const char *ifname,
		    const char *cdnr_name, 
		    struct tb_profile *cmtd_profile,
		    struct tb_profile *peak_profile,
		    struct tc_action *green_action,
		    struct tc_action *yellow_action,
		    struct tc_action *red_action, int coloraware)
{
	struct ifinfo		*ifinfo;
	struct classinfo	*clinfo;
	int error;

	if ((ifinfo = cdnr_ifname2ifinfo(ifname)) == NULL)
		return (QOPERR_BADIF);

	verify_tbprofile(cmtd_profile, cdnr_name);
	verify_tbprofile(peak_profile, cdnr_name);

	if ((error = qop_cdnr_add_trtcm(&clinfo, cdnr_name, ifinfo,
			  cmtd_profile, peak_profile,
			  green_action, yellow_action, red_action,
	     		  coloraware)) != 0) {
		LOG(LOG_ERR, errno, "%s: add trtcm failed!",
		    qoperror(error));
		return (error);
	}
	
	if (rp != NULL) {
		rp->tca_code = TCACODE_HANDLE;
		rp->tca_handle = clinfo->handle;
	}
	return (0);
}

int
qcmd_cdnr_add_tswtcm(struct tc_action *rp, const char *ifname,
		     const char *cdnr_name, const u_int32_t cmtd_rate,
		     const u_int32_t peak_rate, const u_int32_t avg_interval,
		     struct tc_action *green_action,
		     struct tc_action *yellow_action,
		     struct tc_action *red_action)
{
	struct ifinfo		*ifinfo;
	struct classinfo	*clinfo;
	int error;

	if ((ifinfo = cdnr_ifname2ifinfo(ifname)) == NULL)
		return (QOPERR_BADIF);

	if (cmtd_rate > peak_rate) {
		LOG(LOG_ERR, 0,
		    "add tswtcm: cmtd_rate larger than peak_rate!");
		return (QOPERR_INVAL);
	}

	if ((error = qop_cdnr_add_tswtcm(&clinfo, cdnr_name, ifinfo,
					cmtd_rate, peak_rate, avg_interval,
					green_action, yellow_action,
					red_action)) != 0) {
		LOG(LOG_ERR, errno, "%s: add tswtcm failed!",
		    qoperror(error));
		return (error);
	}
	
	if (rp != NULL) {
		rp->tca_code = TCACODE_HANDLE;
		rp->tca_handle = clinfo->handle;
	}
	return (0);
}

int
qcmd_cdnr_delete(const char *ifname, const char *cdnr_name)
{
	struct ifinfo		*ifinfo;
	struct classinfo	*clinfo;

	if ((ifinfo = cdnr_ifname2ifinfo(ifname)) == NULL)
		return (QOPERR_BADIF);

	if ((clinfo = clname2clinfo(ifinfo, cdnr_name)) == NULL)
		return (QOPERR_BADCLASS);

	return qop_delete_cdnr(clinfo);
}

/*
 * class operations:
 *	class structure is used to hold conditioners.
 *	XXX
 *	conditioners has dependencies in the reverse order; parent nodes
 *	refere to child nodes, and thus, a child is created first and
 *	parents should be removed first.
 *	qop_add_cdnr() and qop_delete_cdnr() are wrapper functions
 *	of qop_add_class() and qop_delete_class(), and takes care
 *	of dependencies.
 *	1. when adding a conditioner, it is created as a child of a
 *	   dummy root class.  then, the child conditioners are made
 *	   as its children.
 *	2. when deleting a conditioner, its child conditioners are made
 *	   as children of the dummy root class.  then, the conditioner
 *	   is deleted.
 */

int
qop_add_cdnr(struct classinfo **rp, const char *cdnr_name,
	     struct ifinfo *ifinfo, struct classinfo **childlist,
	     void *cdnr_private)
{
	struct classinfo	*clinfo, *root, *cl, *prev;
	int error;

	/*
	 * if there is no root cdnr, create one.
	 */
	if ((root = get_rootclass(ifinfo)) == NULL) {
		if ((error = qop_add_class(&root, "cdnr_root",
					   ifinfo, NULL, NULL)) != 0) {
			LOG(LOG_ERR, errno,
			    "cdnr: %s: can't create dummy root cdnr on %s!",
			    qoperror(error), ifinfo->ifname);
			return (QOPERR_CLASS);
		}
	}

	/*
	 * create a class as a child of a root class.
	 */
	if ((error = qop_add_class(&clinfo, cdnr_name,
				   ifinfo, root, cdnr_private)) != 0)
		return (error);
	/*
	 * move child nodes
	 */
	for (cl = *childlist; cl != NULL; cl = *++childlist) {
		if (cl->parent != root) {
			/*
			 * this conditioner already has a non-root parent.
			 * we can't track down a multi-parent node by a
			 * tree structure; leave it as it is.
			 * (we need a mechanism similar to a symbolic link
			 * in a file system)
			 */
			continue;
		}
		/* remove this child from the root */
		if (root->child == cl)
			root->child = cl->sibling;
		else for (prev = root->child;
			  prev->sibling != NULL; prev = prev->sibling)
			if (prev->sibling == cl) {
				prev->sibling = cl->sibling;
				break;
			}

		/* add as a child */
		cl->sibling = clinfo->child;
		clinfo->child = cl;
		cl->parent = clinfo;
	}
		
	if (rp != NULL)
		*rp = clinfo;
	return (0);
}

int
qop_delete_cdnr(struct classinfo *clinfo)
{
	struct classinfo *cl, *root;
	int error;

	if ((root = get_rootclass(clinfo->ifinfo)) == NULL) {
		LOG(LOG_ERR, 0, "qop_delete_cdnr: no root cdnr!");
		return (QOPERR_CLASS);
	}

	if (clinfo->parent != root)
		return (QOPERR_CLASS_PERM);

	if ((cl = clinfo->child) != NULL) {
		/* change child's parent to root, find the last child */
		while (cl->sibling != NULL) {
			cl->parent = root;
			cl = cl->sibling;
		}
		cl->parent = root;

		/* move children to siblings */
		cl->sibling = clinfo->sibling;
		clinfo->sibling = cl;
		clinfo->child = NULL;
	}

	error = qop_delete_class(clinfo);

	if (error) {
		/* ick! restore the class tree */
		if (cl != NULL) {
			clinfo->child = clinfo->sibling;
			clinfo->sibling = cl->sibling;
			cl->sibling = NULL;
			/* restore parent field */
			for (cl = clinfo->child; cl != NULL; cl = cl->sibling)
				cl->parent = clinfo;
		}
	}
	return (error);
}

int 
qop_cdnr_add_element(struct classinfo **rp, const char *cdnr_name,
		     struct ifinfo *ifinfo, struct tc_action *action)
{
	struct classinfo *clinfo, *clist[2];
	struct cdnrinfo *cdnrinfo = NULL;
	int error;

	if (action->tca_code == TCACODE_HANDLE) {
		clinfo = clhandle2clinfo(ifinfo, action->tca_handle);
		if (clinfo == NULL)
			return (QOPERR_BADCLASS);
		clist[0] = clinfo;
		clist[1] = NULL;
#if 1
		/*
		 * if the conditioner referred to doesn't have a name,
		 * this is called just to add a name to it.
		 * we can simply add the name to the existing conditioner
		 * and return it.
		 */
		if (cdnr_name != NULL &&
		    strcmp(clinfo->clname, "(null)") == 0) {
			free(clinfo->clname);
			clinfo->clname = strdup(cdnr_name);
			if (rp != NULL)
				*rp = clinfo;
			return (0);
		}
#endif
	} else
		clist[0] = NULL;

	if ((cdnrinfo = calloc(1, sizeof(*cdnrinfo))) == NULL)
		return (QOPERR_NOMEM);

	cdnrinfo->tce_type = TCETYPE_ELEMENT;
	cdnrinfo->tce_un.element.action = *action;

	if ((error = qop_add_cdnr(&clinfo, cdnr_name, ifinfo, clist,
				  cdnrinfo)) != 0)
		goto err_ret;

	if (rp != NULL)
		*rp = clinfo;
	return (0);

 err_ret:
	if (cdnrinfo != NULL)
		free(cdnrinfo);
	return (error);
}

int 
qop_cdnr_add_tbmeter(struct classinfo **rp, const char *cdnr_name,
		     struct ifinfo *ifinfo,
		     struct tb_profile *profile,
		     struct tc_action *in_action,
		     struct tc_action *out_action)
{
	struct classinfo *clinfo, *clist[3];
	struct cdnrinfo *cdnrinfo = NULL;
	int n, error;

	n = 0;
	if (in_action->tca_code == TCACODE_HANDLE) {
		clist[n] = clhandle2clinfo(ifinfo, in_action->tca_handle);
		if (clist[n] == NULL)
			return (QOPERR_BADCLASS);
		n++;
	}
	if (out_action->tca_code == TCACODE_HANDLE) {
		clist[n] = clhandle2clinfo(ifinfo, out_action->tca_handle);
		if (clist[n] == NULL)
			return (QOPERR_BADCLASS);
		n++;
	}
	clist[n] = NULL;

	if ((cdnrinfo = calloc(1, sizeof(*cdnrinfo))) == NULL)
		return (QOPERR_NOMEM);

	cdnrinfo->tce_type = TCETYPE_TBMETER;
	cdnrinfo->tce_un.tbmeter.profile = *profile;
	cdnrinfo->tce_un.tbmeter.in_action = *in_action;
	cdnrinfo->tce_un.tbmeter.out_action = *out_action;

	if ((error = qop_add_cdnr(&clinfo, cdnr_name, ifinfo, clist,
				   cdnrinfo)) != 0)
		goto err_ret;

	if (rp != NULL)
		*rp = clinfo;
	return (0);

 err_ret:
	if (cdnrinfo != NULL)
		free(cdnrinfo);
	return (error);
}

int 
qop_cdnr_modify_tbmeter(struct classinfo *clinfo, struct tb_profile *profile)
{
	struct cdnrinfo *cdnrinfo = clinfo->private;

	if (cdnrinfo->tce_type != TCETYPE_TBMETER)
		return (QOPERR_CLASS_INVAL);
	cdnrinfo->tce_un.tbmeter.profile = *profile;

	return qop_modify_class(clinfo, NULL);
}

int 
qop_cdnr_add_trtcm(struct classinfo **rp, const char *cdnr_name,
		   struct ifinfo *ifinfo,
		   struct tb_profile *cmtd_profile,
		   struct tb_profile *peak_profile,
		   struct tc_action *green_action,
		   struct tc_action *yellow_action,
		   struct tc_action *red_action, int coloraware)
{
	struct classinfo *clinfo, *clist[4];
	struct cdnrinfo *cdnrinfo = NULL;
	int n, error;

	n = 0;
	if (green_action->tca_code == TCACODE_HANDLE) {
		clist[n] = clhandle2clinfo(ifinfo, green_action->tca_handle);
		if (clist[n] == NULL)
			return (QOPERR_BADCLASS);
		n++;
	}
	if (yellow_action->tca_code == TCACODE_HANDLE) {
		clist[n] = clhandle2clinfo(ifinfo, yellow_action->tca_handle);
		if (clist[n] == NULL)
			return (QOPERR_BADCLASS);
		n++;
	}
	if (red_action->tca_code == TCACODE_HANDLE) {
		clist[n] = clhandle2clinfo(ifinfo, yellow_action->tca_handle);
		if (clist[n] == NULL)
			return (QOPERR_BADCLASS);
		n++;
	}
	clist[n] = NULL;

	if ((cdnrinfo = calloc(1, sizeof(*cdnrinfo))) == NULL)
		return (QOPERR_NOMEM);

	cdnrinfo->tce_type = TCETYPE_TRTCM;
	cdnrinfo->tce_un.trtcm.cmtd_profile = *cmtd_profile;
	cdnrinfo->tce_un.trtcm.peak_profile = *peak_profile;
	cdnrinfo->tce_un.trtcm.green_action = *green_action;
	cdnrinfo->tce_un.trtcm.yellow_action = *yellow_action;
	cdnrinfo->tce_un.trtcm.red_action = *red_action;
	cdnrinfo->tce_un.trtcm.coloraware = coloraware;

	if ((error = qop_add_cdnr(&clinfo, cdnr_name, ifinfo, clist,
				  cdnrinfo)) != 0)
		goto err_ret;

	if (rp != NULL)
		*rp = clinfo;
	return (0);

 err_ret:
	if (cdnrinfo != NULL)
		free(cdnrinfo);
	return (error);
}

int 
qop_cdnr_modify_trtcm(struct classinfo *clinfo,
		      struct tb_profile *cmtd_profile,
		      struct tb_profile *peak_profile, int coloraware)
{
	struct cdnrinfo *cdnrinfo = clinfo->private;

	if (cdnrinfo->tce_type != TCETYPE_TRTCM)
		return (QOPERR_CLASS_INVAL);
	cdnrinfo->tce_un.trtcm.cmtd_profile = *cmtd_profile;
	cdnrinfo->tce_un.trtcm.peak_profile = *peak_profile;
	cdnrinfo->tce_un.trtcm.coloraware = coloraware;

	return qop_modify_class(clinfo, NULL);
}

int 
qop_cdnr_add_tswtcm(struct classinfo **rp, const char *cdnr_name,
		    struct ifinfo *ifinfo, const u_int32_t cmtd_rate,
		    const u_int32_t peak_rate, const u_int32_t avg_interval,
		    struct tc_action *green_action,
		    struct tc_action *yellow_action,
		    struct tc_action *red_action)
{
	struct classinfo *clinfo, *clist[4];
	struct cdnrinfo *cdnrinfo = NULL;
	int n, error;

	n = 0;
	if (green_action->tca_code == TCACODE_HANDLE) {
		clist[n] = clhandle2clinfo(ifinfo, green_action->tca_handle);
		if (clist[n] == NULL)
			return (QOPERR_BADCLASS);
		n++;
	}
	if (yellow_action->tca_code == TCACODE_HANDLE) {
		clist[n] = clhandle2clinfo(ifinfo, yellow_action->tca_handle);
		if (clist[n] == NULL)
			return (QOPERR_BADCLASS);
		n++;
	}
	if (red_action->tca_code == TCACODE_HANDLE) {
		clist[n] = clhandle2clinfo(ifinfo, yellow_action->tca_handle);
		if (clist[n] == NULL)
			return (QOPERR_BADCLASS);
		n++;
	}
	clist[n] = NULL;

	if ((cdnrinfo = calloc(1, sizeof(*cdnrinfo))) == NULL)
		return (QOPERR_NOMEM);

	cdnrinfo->tce_type = TCETYPE_TSWTCM;
	cdnrinfo->tce_un.tswtcm.cmtd_rate = cmtd_rate;
	cdnrinfo->tce_un.tswtcm.peak_rate = peak_rate;
	cdnrinfo->tce_un.tswtcm.avg_interval = avg_interval;
	cdnrinfo->tce_un.tswtcm.green_action = *green_action;
	cdnrinfo->tce_un.tswtcm.yellow_action = *yellow_action;
	cdnrinfo->tce_un.tswtcm.red_action = *red_action;

	if ((error = qop_add_cdnr(&clinfo, cdnr_name, ifinfo, clist,
				  cdnrinfo)) != 0)
		goto err_ret;

	if (rp != NULL)
		*rp = clinfo;
	return (0);

 err_ret:
	if (cdnrinfo != NULL)
		free(cdnrinfo);
	return (error);
}

int 
qop_cdnr_modify_tswtcm(struct classinfo *clinfo, const u_int32_t cmtd_rate,
		       const u_int32_t peak_rate, const u_int32_t avg_interval)
{
	struct cdnrinfo *cdnrinfo = clinfo->private;

	if (cdnrinfo->tce_type != TCETYPE_TSWTCM)
		return (QOPERR_CLASS_INVAL);
	cdnrinfo->tce_un.tswtcm.cmtd_rate = cmtd_rate;
	cdnrinfo->tce_un.tswtcm.peak_rate = peak_rate;
	cdnrinfo->tce_un.tswtcm.avg_interval = avg_interval;
	
	return qop_modify_class(clinfo, NULL);
}

/*
 *  system call interfaces for qdisc_ops
 */
static int
cdnr_attach(struct ifinfo *ifinfo)
{
	struct cdnr_interface iface;

	if (cdnr_fd < 0 &&
	    (cdnr_fd = open(CDNR_DEVICE, O_RDWR)) < 0 &&
	    (cdnr_fd = open_module(CDNR_DEVICE, O_RDWR)) < 0) {
		LOG(LOG_ERR, errno, "CDNR open");
		return (QOPERR_SYSCALL);
	}

	cdnr_refcount++;
	memset(&iface, 0, sizeof(iface));
	strncpy(iface.cdnr_ifname, ifinfo->ifname+1, IFNAMSIZ);

	if (ioctl(cdnr_fd, CDNR_IF_ATTACH, &iface) < 0)
		return (QOPERR_SYSCALL);
#if 1
	LOG(LOG_INFO, 0, "conditioner attached to %s", iface.cdnr_ifname);
#endif
	return (0);
}

static int
cdnr_detach(struct ifinfo *ifinfo)
{
	struct cdnr_interface iface;
	
	memset(&iface, 0, sizeof(iface));
	strncpy(iface.cdnr_ifname, ifinfo->ifname+1, IFNAMSIZ);

	if (ioctl(cdnr_fd, CDNR_IF_DETACH, &iface) < 0)
		return (QOPERR_SYSCALL);

	if (--cdnr_refcount == 0) {
		close(cdnr_fd);
		cdnr_fd = -1;
	}
	return (0);
}

static int
cdnr_enable(struct ifinfo *ifinfo)
{
	struct cdnr_interface iface;

	memset(&iface, 0, sizeof(iface));
	strncpy(iface.cdnr_ifname, ifinfo->ifname+1, IFNAMSIZ);

	if (ioctl(cdnr_fd, CDNR_ENABLE, &iface) < 0)
		return (QOPERR_SYSCALL);
	return (0);
}

static int
cdnr_disable(struct ifinfo *ifinfo)
{
	struct cdnr_interface iface;

	memset(&iface, 0, sizeof(iface));
	strncpy(iface.cdnr_ifname, ifinfo->ifname+1, IFNAMSIZ);

	if (ioctl(cdnr_fd, CDNR_DISABLE, &iface) < 0)
		return (QOPERR_SYSCALL);
	return (0);
}

static int
cdnr_add_class(struct classinfo *clinfo)
{
	struct cdnr_add_element element_add;
	struct cdnr_add_tbmeter tbmeter_add;
	struct cdnr_add_trtcm   trtcm_add;
	struct cdnr_add_tswtcm  tswtcm_add;
	struct cdnrinfo *cdnrinfo;
	
	cdnrinfo = clinfo->private;

	/* root class is a dummy class */
	if (clinfo->parent == NULL) {
		clinfo->handle = 0;
		return (0);
	}

	switch (cdnrinfo->tce_type) {
	case TCETYPE_ELEMENT:
		memset(&element_add, 0, sizeof(element_add));
		strncpy(element_add.iface.cdnr_ifname,
			clinfo->ifinfo->ifname+1, IFNAMSIZ);
		element_add.action = cdnrinfo->tce_un.element.action;
		if (ioctl(cdnr_fd, CDNR_ADD_ELEM, &element_add) < 0) {
			clinfo->handle = CDNR_NULL_HANDLE;
			return (QOPERR_SYSCALL);
		}
		clinfo->handle = element_add.cdnr_handle;
		break;

	case TCETYPE_TBMETER:
		memset(&tbmeter_add, 0, sizeof(tbmeter_add));
		strncpy(tbmeter_add.iface.cdnr_ifname,
			clinfo->ifinfo->ifname+1, IFNAMSIZ);
		tbmeter_add.profile = cdnrinfo->tce_un.tbmeter.profile;
		tbmeter_add.in_action = cdnrinfo->tce_un.tbmeter.in_action;
		tbmeter_add.out_action = cdnrinfo->tce_un.tbmeter.out_action;
		if (ioctl(cdnr_fd, CDNR_ADD_TBM, &tbmeter_add) < 0) {
			clinfo->handle = CDNR_NULL_HANDLE;
			return (QOPERR_SYSCALL);
		}
		clinfo->handle = tbmeter_add.cdnr_handle;
		break;

	case TCETYPE_TRTCM:
		memset(&trtcm_add, 0, sizeof(trtcm_add));
		strncpy(trtcm_add.iface.cdnr_ifname,
			clinfo->ifinfo->ifname+1, IFNAMSIZ);
		trtcm_add.cmtd_profile = cdnrinfo->tce_un.trtcm.cmtd_profile;
		trtcm_add.peak_profile = cdnrinfo->tce_un.trtcm.peak_profile;
		trtcm_add.green_action = cdnrinfo->tce_un.trtcm.green_action;
		trtcm_add.yellow_action = cdnrinfo->tce_un.trtcm.yellow_action;
		trtcm_add.red_action = cdnrinfo->tce_un.trtcm.red_action;
		trtcm_add.coloraware = cdnrinfo->tce_un.trtcm.coloraware;
		if (ioctl(cdnr_fd, CDNR_ADD_TCM, &trtcm_add) < 0) {
			clinfo->handle = CDNR_NULL_HANDLE;
			return (QOPERR_SYSCALL);
		}
		clinfo->handle = trtcm_add.cdnr_handle;
		break;

	case TCETYPE_TSWTCM:
		memset(&tswtcm_add, 0, sizeof(tswtcm_add));
		strncpy(tswtcm_add.iface.cdnr_ifname,
			clinfo->ifinfo->ifname+1, IFNAMSIZ);
		tswtcm_add.cmtd_rate = cdnrinfo->tce_un.tswtcm.cmtd_rate;
		tswtcm_add.peak_rate = cdnrinfo->tce_un.tswtcm.peak_rate;
		tswtcm_add.avg_interval = cdnrinfo->tce_un.tswtcm.avg_interval;
		tswtcm_add.green_action = cdnrinfo->tce_un.tswtcm.green_action;
		tswtcm_add.yellow_action = cdnrinfo->tce_un.tswtcm.yellow_action;
		tswtcm_add.red_action = cdnrinfo->tce_un.tswtcm.red_action;
		if (ioctl(cdnr_fd, CDNR_ADD_TSW, &tswtcm_add) < 0) {
			clinfo->handle = CDNR_NULL_HANDLE;
			return (QOPERR_SYSCALL);
		}
		clinfo->handle = tswtcm_add.cdnr_handle;
		break;

	default:
		return (QOPERR_CLASS_INVAL);
	}
	return (0);
}

static int
cdnr_modify_class(struct classinfo *clinfo, void *arg)
{
	struct cdnr_modify_tbmeter tbmeter_modify;
	struct cdnr_modify_trtcm   trtcm_modify;
	struct cdnr_modify_tswtcm  tswtcm_modify;
	struct cdnrinfo *cdnrinfo;

	cdnrinfo = clinfo->private;

	switch (cdnrinfo->tce_type) {
	case TCETYPE_TBMETER:
		memset(&tbmeter_modify, 0, sizeof(tbmeter_modify));
		strncpy(tbmeter_modify.iface.cdnr_ifname,
			clinfo->ifinfo->ifname+1, IFNAMSIZ);
		tbmeter_modify.cdnr_handle = clinfo->handle;
		tbmeter_modify.profile = cdnrinfo->tce_un.tbmeter.profile;
		if (ioctl(cdnr_fd, CDNR_MOD_TBM, &tbmeter_modify) < 0)
			return (QOPERR_SYSCALL);
		break;

	case TCETYPE_TRTCM:
		memset(&trtcm_modify, 0, sizeof(trtcm_modify));
		strncpy(trtcm_modify.iface.cdnr_ifname,
			clinfo->ifinfo->ifname+1, IFNAMSIZ);
		trtcm_modify.cdnr_handle = clinfo->handle;
		trtcm_modify.cmtd_profile =
			cdnrinfo->tce_un.trtcm.cmtd_profile;
		trtcm_modify.peak_profile =
			cdnrinfo->tce_un.trtcm.peak_profile;
		trtcm_modify.coloraware = cdnrinfo->tce_un.trtcm.coloraware;
		if (ioctl(cdnr_fd, CDNR_MOD_TCM, &trtcm_modify) < 0)
			return (QOPERR_SYSCALL);
		break;

	case TCETYPE_TSWTCM:
		memset(&tswtcm_modify, 0, sizeof(tswtcm_modify));
		strncpy(tswtcm_modify.iface.cdnr_ifname,
			clinfo->ifinfo->ifname+1, IFNAMSIZ);
		tswtcm_modify.cdnr_handle = clinfo->handle;
		tswtcm_modify.cmtd_rate = cdnrinfo->tce_un.tswtcm.cmtd_rate;
		tswtcm_modify.peak_rate = cdnrinfo->tce_un.tswtcm.peak_rate;
		tswtcm_modify.avg_interval = cdnrinfo->tce_un.tswtcm.avg_interval;
		if (ioctl(cdnr_fd, CDNR_MOD_TSW, &tswtcm_modify) < 0)
			return (QOPERR_SYSCALL);
		break;

	default:
		return (QOPERR_CLASS_INVAL);
	}
	return (0);
}

static int
cdnr_delete_class(struct classinfo *clinfo)
{
	struct cdnr_delete_element element_delete;

	if (clinfo->handle == CDNR_NULL_HANDLE)
		return (0);

	memset(&element_delete, 0, sizeof(element_delete));
	strncpy(element_delete.iface.cdnr_ifname, clinfo->ifinfo->ifname+1,
		IFNAMSIZ);
	element_delete.cdnr_handle = clinfo->handle;

	if (ioctl(cdnr_fd, CDNR_DEL_ELEM, &element_delete) < 0)
		return (QOPERR_SYSCALL);
	return (0);
}

static int
cdnr_add_filter(struct fltrinfo *fltrinfo)
{
	struct cdnr_add_filter fltr_add;
	
	memset(&fltr_add, 0, sizeof(fltr_add));
	strncpy(fltr_add.iface.cdnr_ifname,
		fltrinfo->clinfo->ifinfo->ifname+1, IFNAMSIZ);
	fltr_add.cdnr_handle = fltrinfo->clinfo->handle;
	fltr_add.filter = fltrinfo->fltr;

	if (ioctl(cdnr_fd, CDNR_ADD_FILTER, &fltr_add) < 0)
		return (QOPERR_SYSCALL);
	fltrinfo->handle = fltr_add.filter_handle;
	return (0);
}

static int
cdnr_delete_filter(struct fltrinfo *fltrinfo)
{
	struct cdnr_delete_filter fltr_del;

	memset(&fltr_del, 0, sizeof(fltr_del));
	strncpy(fltr_del.iface.cdnr_ifname,
		fltrinfo->clinfo->ifinfo->ifname+1, IFNAMSIZ);
	fltr_del.filter_handle = fltrinfo->handle;

	if (ioctl(cdnr_fd, CDNR_DEL_FILTER, &fltr_del) < 0)
		return (QOPERR_SYSCALL);
	return (0);
}


static int 
verify_tbprofile(struct tb_profile *profile, const char *cdnr_name)
{
	if (profile->depth < 1500) {
		LOG(LOG_WARNING, 0,
		    "warning: token bucket depth for %s is too small (%d)",
		    cdnr_name, profile->depth);
		return (-1);
	}
	return (0);
}

