/*
 * Copyright 2009 Red Hat Inc., Durham, North Carolina.
 * All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 * Authors:
 *      "Daniel Kopecek" <dkopecek@redhat.com>
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

#include "generic/common.h"
#include "public/sexp-manip.h"
#include "_seap-packetq.h"
#include "_seap-packet.h"
#include "_seap-types.h"
#include "seap-descriptor.h"
#include "sch_queue.h"
#include "debug_priv.h"

SEAP_packet_t *SEAP_packet_new (void)
{
	SEAP_packet_t *p = malloc(sizeof(SEAP_packet_t));
        memset (p, 0, sizeof (SEAP_packet_t));
        p->type = SEAP_PACKET_INV;

        return (p);
}

void SEAP_packet_free (SEAP_packet_t *packet)
{
	free(packet);
}

void *SEAP_packet_settype (SEAP_packet_t *packet, uint8_t type)
{
        _A(packet != NULL);
        _A(type == SEAP_PACKET_MSG ||
           type == SEAP_PACKET_CMD ||
           type == SEAP_PACKET_ERR);

        packet->type = type;

        switch (type) {
        case SEAP_PACKET_MSG:
                return ((void *)&(packet->data.msg));
        case SEAP_PACKET_ERR:
                return ((void *)&(packet->data.msg));
        case SEAP_PACKET_CMD:
                return ((void *)&(packet->data.msg));
        default:
                abort ();
        }

        /* not reached */
        return (NULL);
}

uint8_t SEAP_packet_gettype (SEAP_packet_t *packet)
{
        _A(packet != NULL);
        return (packet->type);
}

SEAP_msg_t *SEAP_packet_msg (SEAP_packet_t *packet)
{
        return (&(packet->data.msg));
}

SEAP_cmd_t *SEAP_packet_cmd (SEAP_packet_t *packet)
{
        return (&(packet->data.cmd));
}

SEAP_err_t *SEAP_packet_err (SEAP_packet_t *packet)
{
        return (&(packet->data.err));
}

static int SEAP_packet_sexp2msg (SEXP_t *sexp_msg, SEAP_msg_t *seap_msg)
{
        size_t msg_icnt, msg_n, attr_i;
        SEXP_t *attr_name, *attr_val;

        _A(sexp_msg != NULL);
        _A(seap_msg != NULL);

        msg_icnt = SEXP_list_length (sexp_msg);

        seap_msg->attrs_cnt = msg_icnt - 4;
	seap_msg->attrs = malloc(sizeof (SEAP_attr_t) * seap_msg->attrs_cnt);

        for (msg_n = 2, attr_i = 0; msg_n < msg_icnt; ++msg_n) {

                attr_name = SEXP_list_nth (sexp_msg, msg_n);
                if (attr_name == NULL) {
                        dD("Unexpected error: No S-exp (attr_name) at position %lu in the message (%p).",
                           msg_n, sexp_msg);

		free(seap_msg->attrs);

                        return (SEAP_EUNEXP);
                }

                if (SEXP_string_nth (attr_name, 1) == ':') {
                        if (SEXP_strcmp (attr_name, ":id") == 0) {

                                attr_val = SEXP_list_nth (sexp_msg, msg_n + 1);
                                if (attr_val == NULL) {
                                        dD("Unexpected error: \"%s\": No attribute value at position %lu in the message (%p).",
                                           "id", msg_n + 1, sexp_msg);

					free(seap_msg->attrs);
					SEXP_free(attr_name);

                                        return (SEAP_EUNEXP);
                                }

#if SEAP_MSGID_BITS == 64
                                seap_msg->id = SEXP_number_getu_64 (attr_val);
                                if (seap_msg->id == UINT64_MAX)
#else
                                seap_msg->id = SEXP_number_getu_32 (attr_val);
                                if (seap_msg->id == UINT32_MAX)
#endif
                                {
                                        switch (errno) {
                                        case EDOM:
                                                dD("id: invalid value or type: s_exp=%p, type=%s",
                                                   (void *)attr_val, SEXP_strtype (attr_val));

						free(seap_msg->attrs);
                                                SEXP_free(attr_val);
                                                SEXP_free(attr_name);

                                                return (SEAP_EINVAL);
                                        case EFAULT:
                                                dD("id: not found");

						free(seap_msg->attrs);
                                                SEXP_free(attr_val);
                                                SEXP_free(attr_name);

                                                return (SEAP_EINVAL);
                                        }
                                }

                                SEXP_free (attr_val);
                        } else {
                                seap_msg->attrs[attr_i].name  = SEXP_string_subcstr (attr_name, 1, 0);
                                seap_msg->attrs[attr_i].value = SEXP_list_nth (sexp_msg, msg_n + 1);

                                if (seap_msg->attrs[attr_i].value == NULL) {
                                        dD("Unexpected error: \"%s\": No attribute value at position %lu in the message (%p).",
                                           seap_msg->attrs[attr_i].name, msg_n + 1, sexp_msg);

					free(seap_msg->attrs[attr_i].name);

                                        for (; attr_i > 0; --attr_i) {
						free(seap_msg->attrs[attr_i - 1].name);

                                                if (seap_msg->attrs[attr_i - 1].value != NULL)
                                                        SEXP_free (seap_msg->attrs[attr_i - 1].value);
                                        }

					free(seap_msg->attrs);
                                        SEXP_free(attr_name);

                                        return (SEAP_EINVAL);
                                }

                                ++attr_i;
                        }

                        ++msg_n;
                } else {
                        seap_msg->attrs[attr_i].name  = SEXP_string_cstr (attr_name);
                        seap_msg->attrs[attr_i].value = NULL;

                        ++attr_i;
                }

                SEXP_free (attr_name);
        }

        _A(attr_i <= (SEXP_list_length (sexp_msg) - 4));
        _A(attr_i >= (SEXP_list_length (sexp_msg) - 4)/2);

        seap_msg->attrs_cnt = attr_i;
        void *new_attrs = realloc(seap_msg->attrs, sizeof(SEAP_attr_t) * seap_msg->attrs_cnt);
        if (new_attrs != NULL || seap_msg->attrs_cnt == 0)
                seap_msg->attrs = new_attrs;
        seap_msg->sexp = SEXP_list_last (sexp_msg);

        return (0);
}

static SEXP_t *SEAP_packet_msg2sexp (SEAP_msg_t *msg)
{
        SEXP_t *sexp;
        uint16_t i;

        SEXP_t *r0, *r1, *r2;

        _A(msg != NULL);

        sexp = SEXP_list_new (r0 = SEXP_string_new (SEAP_SYM_MSG, strlen (SEAP_SYM_MSG)),
                              r1 = SEXP_string_new (":id", 3),
#if SEAP_MSGID_BITS == 64
                              r2 = SEXP_number_newu_64 (msg->id),
#else
                              r2 = SEXP_number_newu_32 (msg->id),
#endif
                              NULL);

		SEXP_free(r0);
		SEXP_free(r1);
		SEXP_free(r2);

        /* Add message attributes */
        for (i = 0; i < msg->attrs_cnt; ++i) {
                if (msg->attrs[i].value != NULL) {
                        SEXP_list_add (sexp, r0 = SEXP_string_newf (":%s", msg->attrs[i].name));
                        SEXP_list_add (sexp, msg->attrs[i].value);
                } else {
                        SEXP_list_add (sexp, r0 = SEXP_string_new (msg->attrs[i].name,
                                                                   strlen (msg->attrs[i].name)));
                }

                SEXP_free (r0);
        }

        /* Add data */
        if (msg->sexp != NULL)
                SEXP_list_add (sexp, msg->sexp);
        else {
                SEXP_list_add (sexp, r0 = SEXP_list_new (NULL)); /* FIXME */
                SEXP_free(r0);
        }

	dD("MSG -> SEXP");
	dO(OSCAP_DEBUGOBJ_SEXP, sexp);
	dD("packet size: %zu", SEXP_sizeof(sexp));

        return (sexp);
}

static int SEAP_packet_sexp2cmd (SEXP_t *sexp_cmd, SEAP_cmd_t *seap_cmd)
{
        SEXP_t *item, *val;
        size_t i;
        int mattrs;

        memset (seap_cmd, 0, sizeof (SEAP_cmd_t));
        seap_cmd->class = SEAP_CMDCLASS_INT;
        seap_cmd->args  = NULL;

        mattrs = 0;
        i = 2;

        while ((item = SEXP_list_nth (sexp_cmd, i)) != NULL) {
                if (SEXP_strncmp (item, ":", 1) == 0) {
                        char *attr_name;

                        attr_name = SEXP_string_cstr (item);

                        switch (*(attr_name + 1)) {
                        case 'i':
                                if (strcmp (attr_name + 1, "id") == 0) {
                                        val = SEXP_list_nth (sexp_cmd, i + 1);

                                        if (val == NULL)
                                                break;

                                        if (SEXP_numberp (val)) {
                                                seap_cmd->id = SEXP_number_getu_16 (val);
                                                ++mattrs;
                                        }

                                        SEXP_free (val);
                                }
                                break;
                        case 'r':
                                if (strcmp (attr_name + 1, "reply_id") == 0) {
                                        val = SEXP_list_nth (sexp_cmd, i + 1);

                                        if (val == NULL)
                                                break;

                                        if (SEXP_numberp (val)) {
                                                seap_cmd->rid = SEXP_number_getu_16 (val);
                                                seap_cmd->flags |= SEAP_CMDFLAG_REPLY;
                                        }

                                        SEXP_free (val);
                                }
                                break;
                        case 'c':
                                if (strcmp (attr_name + 1, "class") == 0) {
                                        val = SEXP_list_nth (sexp_cmd, i + 1);

                                        if (val == NULL)
                                                break;

                                        if (SEXP_stringp (val)) {
                                                if (SEXP_strcmp (val, "usr") == 0) {
                                                        seap_cmd->class = SEAP_CMDCLASS_USR;
                                                }
                                        }

                                        SEXP_free (val);
                                }
                                break;
                        case 't':
                                if (strcmp (attr_name + 1, "type") == 0) {
                                        val = SEXP_list_nth (sexp_cmd, i + 1);

                                        if (val == NULL)
                                                break;

                                        if (SEXP_stringp (val)) {
                                                if (SEXP_strcmp (val, "sync") == 0)
                                                        seap_cmd->flags |= SEAP_CMDFLAG_SYNC;
                                        }

                                        SEXP_free (val);
                                }
                                break;
                        }

                        free (attr_name);
                        ++i; ++i;
                } else {
                        break;
                }

                SEXP_free (item);
        }

        if (item == NULL || mattrs < 1) {
                if (item != NULL)
                        SEXP_free(item);

                errno = EINVAL;
                return (-1);
        }

        if (SEXP_numberp (item)) {
                seap_cmd->code = SEXP_number_getu_16 (item);
                SEXP_free (item);
        } else {
                SEXP_free (item);
                errno = EINVAL;
                return (-1);
        }

        seap_cmd->args = SEXP_list_nth (sexp_cmd, ++i);

        return (0);
}

static SEXP_t *SEAP_packet_cmd2sexp (SEAP_cmd_t *cmd)
{
        SEXP_t *sexp;
        SEXP_t *r0, *r1, *r2;

        _A(cmd != NULL);

        sexp = SEXP_list_new (r0 = SEXP_string_new (SEAP_SYM_CMD, strlen (SEAP_SYM_CMD)),
                              r1 = SEXP_string_new (":id", 3),
                              r2 = SEXP_number_newu_16 (cmd->id),
                              NULL);

	SEXP_free(r0);
	SEXP_free(r1);
	SEXP_free(r2);

        if (cmd->flags & SEAP_CMDFLAG_REPLY) {
                SEXP_list_add (sexp,
                               r0 = SEXP_string_new (":reply_id", 9));
                SEXP_list_add (sexp,
                               r1 = SEXP_number_newu_16 (cmd->rid));

				SEXP_free(r0);
				SEXP_free(r1);
        }

        SEXP_list_add (sexp,
                       r0 = SEXP_string_new (":class", 6));
        SEXP_free (r0);

        switch (cmd->class) {
        case SEAP_CMDCLASS_USR:
                SEXP_list_add (sexp,
                               r0 = SEXP_string_new ("usr", 3));
                break;
        case SEAP_CMDCLASS_INT:
                SEXP_list_add (sexp,
                               r0 = SEXP_string_new ("int", 3));
                break;
        default:
                abort ();
        }


        SEXP_free (r0);

        SEXP_list_add (sexp,
                       r0 = SEXP_string_new (":type", 5));

        SEXP_list_add (sexp,
                       r1 = (cmd->flags & SEAP_CMDFLAG_SYNC ?
                             SEXP_string_new ("sync", 4) : SEXP_string_new ("async", 5)));

        SEXP_list_add (sexp,
                       r2 = SEXP_number_newu_16 (cmd->code));

		SEXP_free(r0);
		SEXP_free(r1);
		SEXP_free(r2);

        if (cmd->args != NULL)
                SEXP_list_add (sexp, cmd->args);

	dD("CMD -> SEXP");
	dO(OSCAP_DEBUGOBJ_SEXP, sexp);
	dD("packet size: %zu", SEXP_sizeof(sexp));

        SEXP_VALIDATE(sexp);
        return (sexp);
}

static int SEAP_packet_sexp2err (SEXP_t *sexp_err, SEAP_err_t *seap_err)
{
        SEXP_t  *member;
        uint32_t n;

        seap_err->id   = 0;
        seap_err->code = SEAP_EUNKNOWN;
        seap_err->type = SEAP_ETYPE_INT;
        seap_err->data = NULL;

        n = 2;

        while ((member = SEXP_list_nth (sexp_err, n)) != NULL) {
                switch (SEXP_typeof (member)) {
                case SEXP_TYPE_STRING:
                        if (SEXP_string_nth (member, 1) != ':') {
                                dD("Invalid string/attribute in packet");
                                SEXP_free (member);
                                errno = EINVAL;
                                return (-1);
                        }

                        if (SEXP_strcmp (member, ":orig_id") == 0) {
                                SEXP_t *val;

                                val = SEXP_list_nth (sexp_err, ++n);

                                if (!SEXP_numberp (val)) {
                                        dD("Invalid type of :orig_id value");
                                        SEXP_free (val);
                                        SEXP_free (member);
                                        errno = EINVAL;
                                        return (-1);
                                }

#if SEAP_MSGID_BITS == 64
                                seap_err->id = SEXP_number_getu_64 (val);
#else
                                seap_err->id = SEXP_number_getu_32 (val);
#endif
                                SEXP_free (val);
                        } else if (SEXP_strcmp (member, ":type") == 0) {
                                SEXP_t *val;

                                val = SEXP_list_nth (sexp_err, ++n);
				seap_err->type = SEXP_number_getu_32(val);

				if (!SEXP_numberp(val)) {
                                        dD("Invalid type of the :type attribute");
                                        SEXP_free (val);
                                        SEXP_free (member);
                                        errno = EINVAL;
                                        return (-1);
                                }

                                if (!(seap_err->type == SEAP_ETYPE_INT ||
				      seap_err->type == SEAP_ETYPE_USER)) {
					dD("Invalid value of the :type attribute");
					SEXP_free(val);
					SEXP_free(member);
					errno = EINVAL;
					return -1;
				}

                                SEXP_free (val);
                        } else {
                                dD("Unknown packet attribute");
                                SEXP_free (member);
                                errno = EINVAL;
                                return (-1);
                        }

                        break;
                case SEXP_TYPE_NUMBER:
                        seap_err->code = SEXP_number_getu_32 (member);
                        SEXP_free (member);

                        goto loop_exit;
                default:
                        dD("Unexpected type of packet member: list");
                        SEXP_free (member);
                        errno = EINVAL;
                        return (-1);
                }

                SEXP_free(member);
                ++n;
        }
loop_exit:
        seap_err->data = SEXP_list_nth (sexp_err, n + 1);

        return (0);
}

static SEXP_t *SEAP_packet_err2sexp (SEAP_err_t *err)
{
        SEXP_t *sexp;
        SEXP_t *r0, *r1, *r2, *r3, *r4, *r5;

        sexp = SEXP_list_new (r0 = SEXP_string_new (SEAP_SYM_ERR, strlen (SEAP_SYM_ERR)),
                              r1 = SEXP_string_new (":orig_id", 8),
#if SEAP_MSGID_BITS == 64
                              r2 = SEXP_number_newu_64 (err->id),
#else
                              r2 = SEXP_number_newu_32 (err->id),
#endif
                              r3 = SEXP_string_new (":type", 5),
                              r4 = SEXP_number_newu (err->type),
                              r5 = SEXP_number_newu (err->code),
                              NULL);

		SEXP_free(r0);
		SEXP_free(r1);
		SEXP_free(r2);
		SEXP_free(r3);
		SEXP_free(r4);
		SEXP_free(r5);

        if (err->data != NULL)
                SEXP_list_add (sexp, err->data);

	dD("ERR -> SEXP");
	dO(OSCAP_DEBUGOBJ_SEXP, sexp);
	dD("packet size: %zu", SEXP_sizeof(sexp));

        return (sexp);
}

static SEXP_t *SEAP_packet2sexp (SEAP_packet_t *packet)
{
        SEXP_t *sexp = NULL;

        switch (packet->type) {
        case SEAP_PACKET_MSG:
                sexp = SEAP_packet_msg2sexp (SEAP_packet_msg (packet));
                break;
        case SEAP_PACKET_CMD:
                sexp = SEAP_packet_cmd2sexp (SEAP_packet_cmd (packet));
                break;
        case SEAP_PACKET_ERR:
                sexp = SEAP_packet_err2sexp (SEAP_packet_err (packet));
                break;
        default:
                errno = EINVAL;
        }

        return (sexp);
}

int SEAP_packet_recv (SEAP_CTX_t *ctx, int sd, SEAP_packet_t **packet)
{
        SEAP_desc_t *dsc;
        SEXP_t      *sexp_buffer;
        SEXP_t      *sexp_packet;

        SEXP_t     *psym_sexp;
        char        psym_cstr_b[16+1];
        char       *psym_cstr;

	SEAP_packet_t *_packet;

        dsc = SEAP_desc_get (ctx->sd_table, sd);

        if (dsc == NULL) {
                errno = EFAULT;
                return (-1);
        }

	if (SEAP_packetq_get(&dsc->pck_queue, packet) != -1)
		return (0);

        /*
         * Event loop
         * The read mutex is not locked during the wait for an event.
         * This allows other threads to hijack the input stream and
         * to perform high priority tasks (e.g. synchronous commands).
         * If there is a read event and nobody (except this thread) has
         * locked the read mutex then the execution of this loop is
         * stopped, the read mutex gets locked and the receiving and
         * parsing of data can begin.
         */

        for (;;) {
                switch (DESC_TRYRLOCK (dsc)) {
                case  1:
                        goto eloop_exit;
                case  0:
                        switch (DESC_RLOCK (dsc)) {
                        case  1:
                                DESC_RUNLOCK(dsc);
                                continue;
                        case -1:
                                goto fail_rmutex;
                        default:
                                abort ();
                        }
                case -1:
                        fail_rmutex:

                        protect_errno {
                                dE("An error ocured while locking the read mutex: dsc=%p, errno=%u, %s.",
                                   dsc, errno, strerror (errno));
                        }

                        return (-1);
                default:
                        abort ();
                }
        }
eloop_exit:

	sexp_buffer = sch_queue_recvsexp(dsc);
	SEXP_VALIDATE(sexp_buffer);

	(*packet) = NULL;

        while((sexp_packet = SEXP_list_pop (sexp_buffer)) != NULL) {
		if (!SEXP_listp(sexp_packet)) {
			dD("Invalid SEAP packet received: %s.", "not a list");

			SEXP_free (sexp_packet);
			SEXP_free (sexp_buffer);

			errno = EINVAL;
			return (-1);
		} else if (SEXP_list_length (sexp_packet) < 2) {
			dD("Invalid SEAP packet received: %s.", "list length < 2");

			SEXP_free (sexp_packet);
			SEXP_free (sexp_buffer);

			errno = EINVAL;
			return (-1);
		}

		psym_sexp = SEXP_list_first (sexp_packet);

		if (!SEXP_stringp(psym_sexp)) {
			dD("Invalid SEAP packet received: %s.", "first list item is not a string");

			SEXP_free (psym_sexp);
			SEXP_free (sexp_packet);
			SEXP_free (sexp_buffer);

			errno = EINVAL;
			return (-1);
		} else if (SEXP_string_length (psym_sexp) != (strlen (SEAP_SYM_PREFIX) + 3)) {
			dD("Invalid SEAP packet received: %s.", "invalid packet type symbol length");

			SEXP_free (psym_sexp);
			SEXP_free (sexp_packet);
			SEXP_free (sexp_buffer);

			errno = EINVAL;
			return (-1);
		} else if (SEXP_strncmp (psym_sexp, SEAP_SYM_PREFIX, strlen (SEAP_SYM_PREFIX)) != 0) {
			dD("Invalid SEAP packet received: %s.", "invalid prefix");

			SEXP_free (psym_sexp);
			SEXP_free (sexp_packet);
			SEXP_free (sexp_buffer);

			errno = EINVAL;
			return (-1);
		}

		SEXP_string_cstr_r (psym_sexp, psym_cstr_b, sizeof psym_cstr_b);
		psym_cstr = psym_cstr_b + strlen (SEAP_SYM_PREFIX);
		SEXP_free (psym_sexp);

		switch (psym_cstr[0]) {
		case 'm':
			if (psym_cstr[1] == 's' &&
			    psym_cstr[2] == 'g')
			{
				_packet = SEAP_packet_new ();
				_packet->type = SEAP_PACKET_MSG;

				if (SEAP_packet_sexp2msg (sexp_packet, &(_packet->data.msg)) != 0) {
					/* error */
					dD("Invalid SEAP packet received: %s.", "can't translate to msg struct");

					SEXP_free (sexp_packet);
					SEAP_packet_free(_packet);
					SEXP_free (sexp_buffer);

					errno = EINVAL;
					return (-1);
				}
				break;
			}
			goto invalid;
		case 'c':
			if (psym_cstr[1] == 'm' &&
			    psym_cstr[2] == 'd')
			{
				_packet = SEAP_packet_new ();
				_packet->type = SEAP_PACKET_CMD;

				if (SEAP_packet_sexp2cmd (sexp_packet, &(_packet->data.cmd)) != 0) {
					/* error */
					dD("Invalid SEAP packet received: %s.", "can't translate to cmd struct");
					SEXP_free (sexp_packet);
					SEAP_packet_free(_packet);
					SEXP_free (sexp_buffer);

					errno = EINVAL;
					return (-1);
				}
				break;
			}
			goto invalid;
		case 'e':
			if (psym_cstr[1] == 'r' &&
			    psym_cstr[2] == 'r')
			{
				_packet = SEAP_packet_new ();
				_packet->type = SEAP_PACKET_ERR;

				if (SEAP_packet_sexp2err (sexp_packet, &(_packet->data.err)) != 0) {
					/* error */
					dD("Invalid SEAP packet received: %s.", "can't translate to err struct");
					SEXP_free (sexp_packet);
					SEAP_packet_free(_packet);
					SEXP_free (sexp_buffer);

					errno = EINVAL;
					return (-1);
				}
				break;
			}
			/* FALLTHROUGH */
		default:
		invalid:
			dD("Invalid SEAP packet received: %s.", "invalid packet type symbol");
			SEXP_free (sexp_packet);
			SEXP_free (sexp_buffer);
			errno = EINVAL;
			return (-1);
		}


		dD("Received packet");
		dO(OSCAP_DEBUGOBJ_SEXP, sexp_packet);
		dD("packet size: %zu", SEXP_sizeof(sexp_packet));

		SEXP_free(sexp_packet);

		if (*packet == NULL)
			(*packet) = _packet;
		else
			SEAP_packetq_put(&dsc->pck_queue, _packet);

		_packet = NULL;
	}

        SEXP_free(sexp_buffer);

        return (0);
}

int SEAP_packet_recv_bytype (SEAP_CTX_t *ctx, int sd, SEAP_packet_t **packet, uint8_t type)
{
        int ret;
        SEAP_packet_t *pck;
        SEAP_desc_t   *dsc;

        _A(ctx    != NULL);
        _A(packet != NULL);
        _A(type == SEAP_PACKET_CMD ||
           type == SEAP_PACKET_MSG ||
           type == SEAP_PACKET_ERR);

        dsc = SEAP_desc_get (ctx->sd_table, sd);

        if (dsc == NULL)
                return (-1);

        for (;;) {
                pck = NULL;
                ret = SEAP_packet_recv (ctx, sd, &pck);

                if (ret != 0)
                        return (ret);
                else if (pck->type == type)
                        break;
                else if (SEAP_packetq_put (&dsc->pck_queue, pck) == -1) /* XXX: infinite loop */
                        return (-1);
        }

        *packet = pck;

        return (ret);
}

int SEAP_packet_send (SEAP_CTX_t *ctx, int sd, SEAP_packet_t *packet)
{
        SEXP_t *packet_sexp;
        SEAP_desc_t *dsc;
        int ret;

        ret = -1;
        dsc = SEAP_desc_get (ctx->sd_table, sd);

        if (dsc == NULL)
                return (-1);

        packet_sexp = SEAP_packet2sexp (packet);

        if (packet_sexp == NULL) {
                dD("Can't convert S-exp to packet");
                return (-1);
        }

	if (DESC_WLOCK(dsc) == 1) {
                ret = 0;

		if (sch_queue_sendsexp(dsc, packet_sexp, 0) < 0) {
                        ret = -1;

                        protect_errno {
                                dD("FAIL: errno=%u, %s.", errno, strerror (errno));
                        }
                }

		if (DESC_WUNLOCK(dsc) != 1) {
			dE("DESC_WUNLOCK failed to unlock a mutex: %s", strerror(errno));
			ret = -1;
		}
	} else {
		dE("DESC_WLOCK failed to lock a mutex: %s", strerror(errno));
		ret = -1;
	}


        protect_errno {
                SEXP_free (packet_sexp);
        }

        return (ret);
}

int SEAP_packet_enqueue (SEAP_CTX_t *ctx, int sd, SEAP_packet_t *packet)
{
        return (-1);
}
