/*-
 * Copyright (c) 2001-2002
 *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
 *	All rights reserved.
 * Copyright (c) 2003-2004
 *	Hartmut Brandt
 *	All rights reserved.
 *
 * Author: Harti Brandt <harti@freebsd.org>
 *
 * Redistribution of this software and documentation 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 or documentation 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 AND DOCUMENTATION IS PROVIDED BY THE AUTHOR
 * AND ITS 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 AUTHOR OR ITS 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.
 *
 * $FreeBSD: src/sys/netgraph/atm/ccatm/ng_ccatm.c,v 1.2 2005/01/07 01:45:41 imp Exp $
 *
 * ATM call control and API
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/netgraph/atm/ccatm/ng_ccatm.c,v 1.2 2005/01/07 01:45:41 imp Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/sbuf.h>
#include <machine/stdarg.h>

#include <netgraph/ng_message.h>
#include <netgraph/netgraph.h>
#include <netgraph/ng_parse.h>
#include <netnatm/unimsg.h>
#include <netnatm/msg/unistruct.h>
#include <netnatm/api/unisap.h>
#include <netnatm/sig/unidef.h>
#include <netgraph/atm/ngatmbase.h>
#include <netgraph/atm/ng_uni.h>
#include <netnatm/api/atmapi.h>
#include <netgraph/atm/ng_ccatm.h>
#include <netnatm/api/ccatm.h>

MODULE_DEPEND(ng_ccatm, ngatmbase, 1, 1, 1);

MALLOC_DEFINE(M_NG_CCATM, "ng_ccatm", "netgraph uni api node");

/*
 * Command structure parsing
 */

/* ESI */
static const struct ng_parse_fixedarray_info ng_ccatm_esi_type_info =
    NGM_CCATM_ESI_INFO;
static const struct ng_parse_type ng_ccatm_esi_type = {
	&ng_parse_fixedarray_type,
	&ng_ccatm_esi_type_info
};

/* PORT PARAMETERS */
static const struct ng_parse_struct_field ng_ccatm_atm_port_type_info[] =
    NGM_CCATM_ATM_PORT_INFO;
static const struct ng_parse_type ng_ccatm_atm_port_type = {
	&ng_parse_struct_type,
	ng_ccatm_atm_port_type_info
};

/* PORT structure */
static const struct ng_parse_struct_field ng_ccatm_port_type_info[] =
    NGM_CCATM_PORT_INFO;
static const struct ng_parse_type ng_ccatm_port_type = {
	&ng_parse_struct_type,
	ng_ccatm_port_type_info
};

/* the ADDRESS array itself */
static const struct ng_parse_fixedarray_info ng_ccatm_addr_array_type_info =
    NGM_CCATM_ADDR_ARRAY_INFO;
static const struct ng_parse_type ng_ccatm_addr_array_type = {
	&ng_parse_fixedarray_type,
	&ng_ccatm_addr_array_type_info
};

/* one ADDRESS */
static const struct ng_parse_struct_field ng_ccatm_uni_addr_type_info[] =
    NGM_CCATM_UNI_ADDR_INFO;
static const struct ng_parse_type ng_ccatm_uni_addr_type = {
	&ng_parse_struct_type,
	ng_ccatm_uni_addr_type_info
};

/* ADDRESS request */
static const struct ng_parse_struct_field ng_ccatm_addr_req_type_info[] =
    NGM_CCATM_ADDR_REQ_INFO;
static const struct ng_parse_type ng_ccatm_addr_req_type = {
	&ng_parse_struct_type,
	ng_ccatm_addr_req_type_info
};

/* ADDRESS var-array */
static int
ng_ccatm_addr_req_array_getlen(const struct ng_parse_type *type,
    const u_char *start, const u_char *buf)
{
	const struct ngm_ccatm_get_addresses *p;

	p = (const struct ngm_ccatm_get_addresses *)
	    (buf - offsetof(struct ngm_ccatm_get_addresses, addr));
	return (p->count);
}
static const struct ng_parse_array_info ng_ccatm_addr_req_array_type_info =
    NGM_CCATM_ADDR_REQ_ARRAY_INFO;
static const struct ng_parse_type ng_ccatm_addr_req_array_type = {
	&ng_parse_array_type,
	&ng_ccatm_addr_req_array_type_info
};

/* Outer get_ADDRESSes structure */
static const struct ng_parse_struct_field ng_ccatm_get_addresses_type_info[] =
    NGM_CCATM_GET_ADDRESSES_INFO;
static const struct ng_parse_type ng_ccatm_get_addresses_type = {
	&ng_parse_struct_type,
	ng_ccatm_get_addresses_type_info
};

/* Port array */
static int
ng_ccatm_port_array_getlen(const struct ng_parse_type *type,
    const u_char *start, const u_char *buf)
{
	const struct ngm_ccatm_portlist *p;

	p = (const struct ngm_ccatm_portlist *)
	    (buf - offsetof(struct ngm_ccatm_portlist, ports));
	return (p->nports);
}
static const struct ng_parse_array_info ng_ccatm_port_array_type_info =
    NGM_CCATM_PORT_ARRAY_INFO;
static const struct ng_parse_type ng_ccatm_port_array_type = {
	&ng_parse_array_type,
	&ng_ccatm_port_array_type_info
};

/* Portlist structure */
static const struct ng_parse_struct_field ng_ccatm_portlist_type_info[] =
    NGM_CCATM_PORTLIST_INFO;
static const struct ng_parse_type ng_ccatm_portlist_type = {
	&ng_parse_struct_type,
	ng_ccatm_portlist_type_info
};

/*
 * Command list
 */
static const struct ng_cmdlist ng_ccatm_cmdlist[] = {
	{
	  NGM_CCATM_COOKIE,
	  NGM_CCATM_DUMP,
	  "dump",
	  NULL,
	  NULL
	},
	{
	  NGM_CCATM_COOKIE,
	  NGM_CCATM_STOP,
	  "stop",
	  &ng_ccatm_port_type,
	  NULL
	},
	{
	  NGM_CCATM_COOKIE,
	  NGM_CCATM_START,
	  "start",
	  &ng_ccatm_port_type,
	  NULL
	},
	{
	  NGM_CCATM_COOKIE,
	  NGM_CCATM_GETSTATE,
	  "getstate",
	  &ng_ccatm_port_type,
	  &ng_parse_uint32_type
	},
	{
	  NGM_CCATM_COOKIE,
	  NGM_CCATM_GET_ADDRESSES,
	  "get_addresses",
	  &ng_ccatm_port_type,
	  &ng_ccatm_get_addresses_type
	},
	{
	  NGM_CCATM_COOKIE,
	  NGM_CCATM_CLEAR,
	  "clear",
	  &ng_ccatm_port_type,
	  NULL
	},
	{
	  NGM_CCATM_COOKIE,
	  NGM_CCATM_ADDRESS_REGISTERED,
	  "address_reg",
	  &ng_ccatm_addr_req_type,
	  NULL
	},
	{
	  NGM_CCATM_COOKIE,
	  NGM_CCATM_ADDRESS_UNREGISTERED,
	  "address_unreg",
	  &ng_ccatm_addr_req_type,
	  NULL
	},
	{
	  NGM_CCATM_COOKIE,
	  NGM_CCATM_SET_PORT_PARAM,
	  "set_port_param",
	  &ng_ccatm_atm_port_type,
	  NULL
	},
	{
	  NGM_CCATM_COOKIE,
	  NGM_CCATM_GET_PORT_PARAM,
	  "get_port_param",
	  &ng_ccatm_port_type,
	  &ng_ccatm_atm_port_type,
	},
	{
	  NGM_CCATM_COOKIE,
	  NGM_CCATM_GET_PORTLIST,
	  "get_portlist",
	  NULL,
	  &ng_ccatm_portlist_type,
	},
	{
	  NGM_CCATM_COOKIE,
	  NGM_CCATM_SETLOG,
	  "setlog",
	  &ng_parse_hint32_type,
	  &ng_parse_hint32_type,
	},
	{
	  NGM_CCATM_COOKIE,
	  NGM_CCATM_RESET,
	  "reset",
	  NULL,
	  NULL,
	},
	{ 0 }
};

/*
 * Module data
 */
static ng_constructor_t		ng_ccatm_constructor;
static ng_rcvmsg_t		ng_ccatm_rcvmsg;
static ng_shutdown_t		ng_ccatm_shutdown;
static ng_newhook_t		ng_ccatm_newhook;
static ng_rcvdata_t		ng_ccatm_rcvdata;
static ng_disconnect_t		ng_ccatm_disconnect;
static int ng_ccatm_mod_event(module_t, int, void *);

static struct ng_type ng_ccatm_typestruct = {
	.version =	NG_ABI_VERSION,
	.name =		NG_CCATM_NODE_TYPE,
	.mod_event =	ng_ccatm_mod_event,
	.constructor =	ng_ccatm_constructor,	/* Node constructor */
	.rcvmsg =	ng_ccatm_rcvmsg,	/* Control messages */
	.shutdown =	ng_ccatm_shutdown,	/* Node destructor */
	.newhook =	ng_ccatm_newhook,	/* Arrival of new hook */
	.rcvdata =	ng_ccatm_rcvdata,	/* receive data */
	.disconnect =	ng_ccatm_disconnect,	/* disconnect a hook */
	.cmdlist =	ng_ccatm_cmdlist,
};
NETGRAPH_INIT(ccatm, &ng_ccatm_typestruct);

static ng_rcvdata_t	ng_ccatm_rcvuni;
static ng_rcvdata_t	ng_ccatm_rcvdump;
static ng_rcvdata_t	ng_ccatm_rcvmanage;

/*
 * Private node data.
 */
struct ccnode {
	node_p	node;		/* the owning node */
	hook_p	dump;		/* dump hook */
	hook_p	manage;		/* hook to ILMI */

	struct ccdata *data;
	struct mbuf *dump_first;
	struct mbuf *dump_last;	/* first and last mbuf when dumping */

	u_int	hook_cnt;	/* count user and port hooks */
};

/*
 * Private UNI hook data
 */
struct cchook {
	int		is_uni;	/* true if uni hook, user otherwise */
	struct ccnode	*node;	/* the owning node */
	hook_p		hook;
	void		*inst;	/* port or user */
};

static void ng_ccatm_send_user(struct ccuser *, void *, u_int, void *, size_t);
static void ng_ccatm_respond_user(struct ccuser *, void *, int, u_int,
    void *, size_t);
static void ng_ccatm_send_uni(struct ccconn *, void *, u_int, u_int,
    struct uni_msg *);
static void ng_ccatm_send_uni_glob(struct ccport *, void *, u_int, u_int,
    struct uni_msg *);
static void ng_ccatm_log(const char *, ...) __printflike(1, 2);

static const struct cc_funcs cc_funcs = {
	.send_user =		ng_ccatm_send_user,
	.respond_user =		ng_ccatm_respond_user,
	.send_uni =		ng_ccatm_send_uni,
	.send_uni_glob =	ng_ccatm_send_uni_glob,
	.log =			ng_ccatm_log,
};

/************************************************************
 *
 * Create a new node
 */
static int
ng_ccatm_constructor(node_p node)
{
	struct ccnode *priv;

	priv = malloc(sizeof(*priv), M_NG_CCATM, M_NOWAIT | M_ZERO);
	if (priv == NULL)
		return (ENOMEM);

	priv->node = node;
	priv->data = cc_create(&cc_funcs);
	if (priv->data == NULL) {
		free(priv, M_NG_CCATM);
		return (ENOMEM);
	}

	NG_NODE_SET_PRIVATE(node, priv);

	return (0);
}

/*
 * Destroy a node. The user list is empty here, because all hooks are
 * previously disconnected. The connection lists may not be empty, because
 * connections may be waiting for responses from the stack. This also means,
 * that no orphaned connections will be made by the port_destroy routine.
 */
static int
ng_ccatm_shutdown(node_p node)
{
	struct ccnode *priv = NG_NODE_PRIVATE(node);

	cc_destroy(priv->data);

	free(priv, M_NG_CCATM);
	NG_NODE_SET_PRIVATE(node, NULL);

	NG_NODE_UNREF(node);

	return (0);
}

/*
 * Retrieve the registered addresses for one port or all ports.
 * Returns an error code or 0 on success.
 */
static int
ng_ccatm_get_addresses(node_p node, uint32_t portno, struct ng_mesg *msg,
    struct ng_mesg **resp)
{
	struct ccnode *priv = NG_NODE_PRIVATE(node);
	struct uni_addr *addrs;
	u_int *ports;
	struct ngm_ccatm_get_addresses *list;
	u_int count, i;
	size_t len;
	int err;

	err = cc_get_addrs(priv->data, portno, &addrs, &ports, &count);
	if (err != 0)
		return (err);

	len = sizeof(*list) + count * sizeof(list->addr[0]);
	NG_MKRESPONSE(*resp, msg, len, M_NOWAIT);
	if (*resp == NULL) {
		free(addrs, M_NG_CCATM);
		free(ports, M_NG_CCATM);
		return (ENOMEM);
	}
	list = (struct ngm_ccatm_get_addresses *)(*resp)->data;

	list->count = count;
	for (i = 0; i < count; i++) {
		list->addr[i].port = ports[i];
		list->addr[i].addr = addrs[i];
	}

	free(addrs, M_NG_CCATM);
	free(ports, M_NG_CCATM);

	return (0);
}

/*
 * Dumper function. Pack the data into an mbuf chain.
 */
static int
send_dump(struct ccdata *data, void *uarg, const char *buf)
{
	struct mbuf *m;
	struct ccnode *priv = uarg;

	if (priv->dump == NULL) {
		m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
		if (m == NULL)
			return (ENOBUFS);
		priv->dump_first = priv->dump_last = m;
		m->m_pkthdr.len = 0;
	} else {
		m = m_getcl(M_DONTWAIT, MT_DATA, 0);
		if (m == 0) {
			m_freem(priv->dump_first);
			return (ENOBUFS);
		}
		priv->dump_last->m_next = m;
		priv->dump_last = m;
	}

	strcpy(m->m_data, buf);
	priv->dump_first->m_pkthdr.len += (m->m_len = strlen(buf));

	return (0);
}

/*
 * Dump current status to dump hook
 */
static int
ng_ccatm_dump(node_p node)
{
	struct ccnode *priv = NG_NODE_PRIVATE(node);
	struct mbuf *m;
	int error;

	priv->dump_first = priv->dump_last = NULL;
	error = cc_dump(priv->data, MCLBYTES, send_dump, priv);
	if (error != 0)
		return (error);

	if ((m = priv->dump_first) != NULL) {
		priv->dump_first = priv->dump_last = NULL;
		NG_SEND_DATA_ONLY(error, priv->dump, m);
		return (error);
	}
	return (0);
}

/*
 * Control message
 */
static int
ng_ccatm_rcvmsg(node_p node, item_p item, hook_p lasthook)
{
	struct ng_mesg *resp = NULL;
	struct ng_mesg *msg;
	struct ccnode *priv = NG_NODE_PRIVATE(node);
	int error = 0;

	NGI_GET_MSG(item, msg);

	switch (msg->header.typecookie) {

	  case NGM_CCATM_COOKIE:
		switch (msg->header.cmd) {

		  case NGM_CCATM_DUMP:
			if (priv->dump)
				error = ng_ccatm_dump(node);
			else
				error = ENOTCONN;
			break;

		  case NGM_CCATM_STOP:
		    {
			struct ngm_ccatm_port *arg;

			if (msg->header.arglen != sizeof(*arg)) {
				error = EINVAL;
				break;
			}
			arg = (struct ngm_ccatm_port *)msg->data;
			error = cc_port_stop(priv->data, arg->port);
			break;
		    }

		  case NGM_CCATM_START:
		    {
			struct ngm_ccatm_port *arg;

			if (msg->header.arglen != sizeof(*arg)) {
				error = EINVAL;
				break;
			}
			arg = (struct ngm_ccatm_port *)msg->data;
			error = cc_port_start(priv->data, arg->port);
			break;
		    }

		  case NGM_CCATM_GETSTATE:
		    {
			struct ngm_ccatm_port *arg;
			int state;

			if (msg->header.arglen != sizeof(*arg)) {
				error = EINVAL;
				break;
			}
			arg = (struct ngm_ccatm_port *)msg->data;
			error = cc_port_isrunning(priv->data, arg->port,
			    &state);
			if (error == 0) {
				NG_MKRESPONSE(resp, msg, sizeof(uint32_t),
				    M_NOWAIT);
				if (resp == NULL) {
					error = ENOMEM;
					break;
				}
				*(uint32_t *)resp->data = state;
			}
			break;
		    }

		  case NGM_CCATM_GET_ADDRESSES:
		   {
			struct ngm_ccatm_port *arg;

			if (msg->header.arglen != sizeof(*arg)) {
				error = EINVAL;
				break;
			}
			arg = (struct ngm_ccatm_port *)msg->data;
			error = ng_ccatm_get_addresses(node, arg->port, msg,
			    &resp);
			break;
		    }

		  case NGM_CCATM_CLEAR:
		    {
			struct ngm_ccatm_port *arg;

			if (msg->header.arglen != sizeof(*arg)) {
				error = EINVAL;
				break;
			}
			arg = (struct ngm_ccatm_port *)msg->data;
			error = cc_port_clear(priv->data, arg->port);
			break;
		    }

		  case NGM_CCATM_ADDRESS_REGISTERED:
		    {
			struct ngm_ccatm_addr_req *arg;

			if (msg->header.arglen != sizeof(*arg)) {
				error = EINVAL;
				break;
			}
			arg = (struct ngm_ccatm_addr_req *)msg->data;
			error = cc_addr_register(priv->data, arg->port,
			    &arg->addr);
			break;
		    }

		  case NGM_CCATM_ADDRESS_UNREGISTERED:
		    {
			struct ngm_ccatm_addr_req *arg;

			if (msg->header.arglen != sizeof(*arg)) {
				error = EINVAL;
				break;
			}
			arg = (struct ngm_ccatm_addr_req *)msg->data;
			error = cc_addr_unregister(priv->data, arg->port,
			    &arg->addr);
			break;
		    }

		  case NGM_CCATM_GET_PORT_PARAM:
		    {
			struct ngm_ccatm_port *arg;

			if (msg->header.arglen != sizeof(*arg)) {
				error = EINVAL;
				break;
			}
			arg = (struct ngm_ccatm_port *)msg->data;
			NG_MKRESPONSE(resp, msg, sizeof(struct atm_port_info),
			    M_NOWAIT);
			if (resp == NULL) {
				error = ENOMEM;
				break;
			}
			error = cc_port_get_param(priv->data, arg->port,
			    (struct atm_port_info *)resp->data);
			if (error != 0) {
				free(resp, M_NETGRAPH_MSG);
				resp = NULL;
			}
			break;
		    }

		  case NGM_CCATM_SET_PORT_PARAM:
		    {
			struct atm_port_info *arg;

			if (msg->header.arglen != sizeof(*arg)) {
				error = EINVAL;
				break;
			}
			arg = (struct atm_port_info *)msg->data;
			error = cc_port_set_param(priv->data, arg);
			break;
		    }

		  case NGM_CCATM_GET_PORTLIST:
		    {
			struct ngm_ccatm_portlist *arg;
			u_int n, *ports;

			if (msg->header.arglen != 0) {
				error = EINVAL;
				break;
			}
			error = cc_port_getlist(priv->data, &n, &ports);
			if (error != 0)
				break;

			NG_MKRESPONSE(resp, msg, sizeof(*arg) +
			    n * sizeof(arg->ports[0]), M_NOWAIT);
			if (resp == NULL) {
				free(ports, M_NG_CCATM);
				error = ENOMEM;
				break;
			}
			arg = (struct ngm_ccatm_portlist *)resp->data;

			arg->nports = 0;
			for (arg->nports = 0; arg->nports < n; arg->nports++)
				arg->ports[arg->nports] = ports[arg->nports];
			free(ports, M_NG_CCATM);
			break;
		    }

		  case NGM_CCATM_SETLOG:
		    {
			uint32_t log_level;

			log_level = cc_get_log(priv->data);
			if (msg->header.arglen != 0) {
				if (msg->header.arglen != sizeof(log_level)) {
					error = EINVAL;
					break;
				}
				cc_set_log(priv->data, *(uint32_t *)msg->data);
			}

			NG_MKRESPONSE(resp, msg, sizeof(uint32_t), M_NOWAIT);
			if (resp == NULL) {
				error = ENOMEM;
				if (msg->header.arglen != 0)
					cc_set_log(priv->data, log_level);
				break;
			}
			*(uint32_t *)resp->data = log_level;
			break;
		    }

		  case NGM_CCATM_RESET:
			if (msg->header.arglen != 0) {
				error = EINVAL;
				break;
			}

			if (priv->hook_cnt != 0) {
				error = EBUSY;
				break;
			}
			cc_reset(priv->data);
			break;

		  case NGM_CCATM_GET_EXSTAT:
		    {
			struct atm_exstatus s;
			struct atm_exstatus_ep *eps;
			struct atm_exstatus_port *ports;
			struct atm_exstatus_conn *conns;
			struct atm_exstatus_party *parties;
			size_t offs;

			if (msg->header.arglen != 0) {
				error = EINVAL;
				break;
			}
			error = cc_get_extended_status(priv->data,
			    &s, &eps, &ports, &conns, &parties);
			if (error != 0)
				break;

			offs = sizeof(s) + s.neps * sizeof(*eps) +
			    s.nports * sizeof(*ports) +
			    s.nconns * sizeof(*conns) +
			    s.nparties * sizeof(*parties);

			NG_MKRESPONSE(resp, msg, offs, M_NOWAIT);
			if (resp == NULL) {
				error = ENOMEM;
				break;
			}

			memcpy(resp->data, &s, sizeof(s));
			offs = sizeof(s);

			memcpy(resp->data + offs, eps,
			    sizeof(*eps) * s.neps);
			offs += sizeof(*eps) * s.neps;

			memcpy(resp->data + offs, ports,
			    sizeof(*ports) * s.nports);
			offs += sizeof(*ports) * s.nports;

			memcpy(resp->data + offs, conns,
			    sizeof(*conns) * s.nconns);
			offs += sizeof(*conns) * s.nconns;

			memcpy(resp->data + offs, parties,
			    sizeof(*parties) * s.nparties);
			offs += sizeof(*parties) * s.nparties;

			free(eps, M_NG_CCATM);
			free(ports, M_NG_CCATM);
			free(conns, M_NG_CCATM);
			free(parties, M_NG_CCATM);
			break;
		    }

		  default:
			error = EINVAL;
			break;

		}
		break;

	  default:
		error = EINVAL;
		break;

	}

	NG_RESPOND_MSG(error, node, item, resp);
	NG_FREE_MSG(msg);
	return (error);
}

/************************************************************
 *
 * New hook arrival
 */
static int
ng_ccatm_newhook(node_p node, hook_p hook, const char *name)
{
	struct ccnode *priv = NG_NODE_PRIVATE(node);
	struct ccport *port;
	struct ccuser *user;
	struct cchook *hd;
	u_long lport;
	char *end;

	if (strncmp(name, "uni", 3) == 0) {
		/*
		 * This is a UNI hook. Should be a new port.
		 */
		if (name[3] == '\0')
			return (EINVAL);
		lport = strtoul(name + 3, &end, 10);
		if (*end != '\0' || lport == 0 || lport > 0xffffffff)
			return (EINVAL);

		hd = malloc(sizeof(*hd), M_NG_CCATM, M_NOWAIT);
		if (hd == NULL)
			return (ENOMEM);
		hd->is_uni = 1;
		hd->node = priv;
		hd->hook = hook;

		port = cc_port_create(priv->data, hd, (u_int)lport);
		if (port == NULL) {
			free(hd, M_NG_CCATM);
			return (ENOMEM);
		}
		hd->inst = port;

		NG_HOOK_SET_PRIVATE(hook, hd);
		NG_HOOK_SET_RCVDATA(hook, ng_ccatm_rcvuni);
		NG_HOOK_FORCE_QUEUE(hook);

		priv->hook_cnt++;

		return (0);
	}

	if (strcmp(name, "dump") == 0) {
		priv->dump = hook;
		NG_HOOK_SET_RCVDATA(hook, ng_ccatm_rcvdump);
		return (0);
	}

	if (strcmp(name, "manage") == 0) {
		priv->manage = hook;
		NG_HOOK_SET_RCVDATA(hook, ng_ccatm_rcvmanage);
		return (0);
	}

	/*
	 * User hook
	 */
	hd = malloc(sizeof(*hd), M_NG_CCATM, M_NOWAIT);
	if (hd == NULL)
		return (ENOMEM);
	hd->is_uni = 0;
	hd->node = priv;
	hd->hook = hook;

	user = cc_user_create(priv->data, hd, NG_HOOK_NAME(hook));
	if (user == NULL) {
		free(hd, M_NG_CCATM);
		return (ENOMEM);
	}

	hd->inst = user;
	NG_HOOK_SET_PRIVATE(hook, hd);
	NG_HOOK_FORCE_QUEUE(hook);

	priv->hook_cnt++;

	return (0);
}

/*
 * Disconnect a hook
 */
static int
ng_ccatm_disconnect(hook_p hook)
{
	node_p node = NG_HOOK_NODE(hook);
	struct ccnode *priv = NG_NODE_PRIVATE(node);
	struct cchook *hd = NG_HOOK_PRIVATE(hook);

	if (hook == priv->dump) {
		priv->dump = NULL;

	} else if (hook == priv->manage) {
		priv->manage = NULL;
		cc_unmanage(priv->data);

	} else {
		if (hd->is_uni)
			cc_port_destroy(hd->inst, 0);
		else
			cc_user_destroy(hd->inst);

		free(hd, M_NG_CCATM);
		NG_HOOK_SET_PRIVATE(hook, NULL);

		priv->hook_cnt--;

		cc_work(hd->node->data);
	}

	/*
	 * When the number of hooks drops to zero, delete the node.
	 */
	if (NG_NODE_NUMHOOKS(node) == 0 && NG_NODE_IS_VALID(node))
		ng_rmnode_self(node);

	return (0);
}

/************************************************************
 *
 * Receive data from user hook
 */
static int
ng_ccatm_rcvdata(hook_p hook, item_p item)
{
	struct cchook *hd = NG_HOOK_PRIVATE(hook);
	struct uni_msg *msg;
	struct mbuf *m;
	struct ccatm_op op;
	int err;

	NGI_GET_M(item, m);
	NG_FREE_ITEM(item);

	if ((err = uni_msg_unpack_mbuf(m, &msg)) != 0) {
		m_freem(m);
		return (err);
	}
	m_freem(m);

	if (uni_msg_len(msg) < sizeof(op)) {
		printf("%s: packet too short\n", __func__);
		uni_msg_destroy(msg);
		return (EINVAL);
	}

	bcopy(msg->b_rptr, &op, sizeof(op));
	msg->b_rptr += sizeof(op);

	err = cc_user_signal(hd->inst, op.op, msg);
	cc_work(hd->node->data);
	return (err);
}

/*
 * Pack a header and a data area into an mbuf chain
 */
static struct mbuf *
pack_buf(void *h, size_t hlen, void *t, size_t tlen)
{
	struct mbuf *m, *m0, *last;
	u_char *buf = (u_char *)t;
	size_t n;

	/* header should fit into a normal mbuf */
	MGETHDR(m0, M_NOWAIT, MT_DATA);
	if (m0 == NULL)
		return NULL;

	KASSERT(hlen <= MHLEN, ("hlen > MHLEN"));

	bcopy(h, m0->m_data, hlen);
	m0->m_len = hlen;
	m0->m_pkthdr.len = hlen;

	last = m0;
	while ((n = tlen) != 0) {
		if (n > MLEN) {
			m = m_getcl(M_NOWAIT, MT_DATA, 0);
			if (n > MCLBYTES)
				n = MCLBYTES;
		} else
			MGET(m, M_NOWAIT, MT_DATA);

		if(m == NULL)
			goto drop;

		last->m_next = m;
		last = m;

		bcopy(buf, m->m_data, n);
		buf += n;
		tlen -= n;
		m->m_len = n;
		m0->m_pkthdr.len += n;
	}

	return (m0);

  drop:
	m_freem(m0);
	return NULL;
}

/*
 * Send an indication to the user.
 */
static void
ng_ccatm_send_user(struct ccuser *user, void *uarg, u_int op,
    void *val, size_t len)
{
	struct cchook *hd = uarg;
	struct mbuf *m;
	struct ccatm_op	h;
	int error;

	h.op = op;
	m = pack_buf(&h, sizeof(h), val, len);
	if (m == NULL)
		return;

	NG_SEND_DATA_ONLY(error, hd->hook, m);
	if (error != 0)
		printf("%s: error=%d\n", __func__, error);
}

/*
 * Send a response to the user.
 */
static void
ng_ccatm_respond_user(struct ccuser *user, void *uarg, int err, u_int data,
    void *val, size_t len)
{
	struct cchook *hd = uarg;
	struct mbuf *m;
	struct {
		struct ccatm_op	op;
		struct atm_resp resp;
	} resp;
	int error;

	resp.op.op = ATMOP_RESP;
	resp.resp.resp = err;
	resp.resp.data = data;
	m = pack_buf(&resp, sizeof(resp), val, len);
	if (m == NULL)
		return;

	NG_SEND_DATA_ONLY(error, hd->hook, m);
	if (error != 0)
		printf("%s: error=%d\n", __func__, error);
}

/*
 * Receive data from UNI.
 */
static int
ng_ccatm_rcvuni(hook_p hook, item_p item)
{
	struct cchook *hd = NG_HOOK_PRIVATE(hook);
	struct uni_msg *msg;
	struct uni_arg arg;
	struct mbuf *m;
	int err;

	NGI_GET_M(item, m);
	NG_FREE_ITEM(item);

	if ((err = uni_msg_unpack_mbuf(m, &msg)) != 0) {
		m_freem(m);
		return (err);
	}
	m_freem(m);

	if (uni_msg_len(msg) < sizeof(arg)) {
		printf("%s: packet too short\n", __func__);
		uni_msg_destroy(msg);
		return (EINVAL);
	}

	bcopy(msg->b_rptr, &arg, sizeof(arg));
	msg->b_rptr += sizeof(arg);

	if (arg.sig == UNIAPI_ERROR) {
		if (uni_msg_len(msg) != sizeof(struct uniapi_error)) {
			printf("%s: bad UNIAPI_ERROR size %zu\n", __func__,
			    uni_msg_len(msg));
			uni_msg_destroy(msg);
			return (EINVAL);
		}
		err = cc_uni_response(hd->inst, arg.cookie,
		    ((struct uniapi_error *)msg->b_rptr)->reason,
		    ((struct uniapi_error *)msg->b_rptr)->state);
		uni_msg_destroy(msg);
	} else
		err = cc_uni_signal(hd->inst, arg.cookie, arg.sig, msg);

	cc_work(hd->node->data);
	return (err);
}

/*
 * Uarg is the port's uarg.
 */
static void
ng_ccatm_send_uni(struct ccconn *conn, void *uarg, u_int op, u_int cookie,
    struct uni_msg *msg)
{
	struct cchook *hd = uarg;
	struct uni_arg arg;
	struct mbuf *m;
	int error;

	arg.sig = op;
	arg.cookie = cookie;

	m = uni_msg_pack_mbuf(msg, &arg, sizeof(arg));
	uni_msg_destroy(msg);
	if (m == NULL)
		return;

	NG_SEND_DATA_ONLY(error, hd->hook, m);
	if (error != 0)
		printf("%s: error=%d\n", __func__, error);
}

/*
 * Send a global message to the UNI
 */
static void
ng_ccatm_send_uni_glob(struct ccport *port, void *uarg, u_int op, u_int cookie,
    struct uni_msg *msg)
{
	struct cchook *hd = uarg;
	struct uni_arg arg;
	struct mbuf *m;
	int error;

	arg.sig = op;
	arg.cookie = cookie;

	m = uni_msg_pack_mbuf(msg, &arg, sizeof(arg));
	if (msg != NULL)
		uni_msg_destroy(msg);
	if (m == NULL)
		return;

	NG_SEND_DATA_ONLY(error, hd->hook, m);
	if (error != 0)
		printf("%s: error=%d\n", __func__, error);
}
/*
 * Receive from ILMID
 */
static int
ng_ccatm_rcvmanage(hook_p hook, item_p item)
{
	NG_FREE_ITEM(item);
	return (0);
}

static int
ng_ccatm_rcvdump(hook_p hook, item_p item)
{
	NG_FREE_ITEM(item);
	return (0);
}

static void
ng_ccatm_log(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vprintf(fmt, ap);
	printf("\n");
	va_end(ap);
}

/*
 * Loading and unloading of node type
 */
static int
ng_ccatm_mod_event(module_t mod, int event, void *data)
{
	int s;
	int error = 0;

	s = splnet();
	switch (event) {

	  case MOD_LOAD:
		break;

	  case MOD_UNLOAD:
		break;

	  default:
		error = EOPNOTSUPP;
		break;
	}
	splx(s);
	return (error);
}
