#include "pamc_private.h"

/*
 * Copyright (c) 1998 Andrew G. Morgan <morgan@linux.kernel.org>
 * All rights reserved.
 *
 * The license for this source code should accompany this file.  Its
 * md5 checksum is: cac41ac118b52c96cf248baec7237381
 */

/*
 * delete packet
 */

void pamc_delete_packet(pamc_packet_t *datum_pp)
{
    D(("called"));
    if (datum_pp != NULL && *datum_pp != NULL) {
	D(("scrubbing"));
	pamc_scrub((void **) datum_pp, 4+pamc_packet_length(*datum_pp));
    }
    D(("done"));
}

/*
 * new packet (will erase old packet if datum_pp is currently used
 */

void pamc_new_packet(pamc_packet_t *datum_pp, int length)
{
    D(("called [%p, %u]", datum_pp, length));
    if (datum_pp == NULL) {
	D(("no packet"));
	return;
    }

    if (*datum_pp != NULL) {
	D(("purging old packet"));
	pamc_delete_packet(datum_pp);
    }

    *datum_pp = (pamc_packet_t) malloc(4+length);
    if (*datum_pp != NULL) {
	D(("copying length to new packet"));
	pamc_write__u32(*datum_pp, length);
    }
    D(("done"));
}

/*
 * This is where we exchange data between the client and the agent
 */

int pamc_exch_packet(pamc_handle_t pch, unsigned int control,
		     pamc_packet_t *datum_pp)
{
    unsigned char prefix[4];
    unsigned int length;

    D(("called"));
    /* is pch valid? */
    if (pch == NULL) {
	D(("bad handler"));
	return PAMC_CONTROL_FAIL;
    }

    /* is datum_pp valid? */
    if (datum_pp == NULL || *datum_pp == NULL) {
	D(("bad packet [%p]", datum_pp));
	return PAMC_CONTROL_FAIL;            /* no request? */
    }

    /* read length */
    length = pamc_packet_length(*datum_pp);

    /* is the packet a request for an agent? */
    if (control == PAMC_CONTROL_SELECT) {
	/* the packet should be the name of the agent */
	char *id = (char *) malloc(1+length);

	D(("request for new agent"));
	if (id == NULL) {
	    D(("no memory"));
	    goto abort_exchange;
	}
	memcpy(id, pamc_packet_data(*datum_pp), length);
	id[length] = '\0';
	D(("request for [%s]", id));

	/* identify agent */
	if (pamc_set_agent(pch, id) != PAMC_CONTROL_OK) {
	    D(("failed to start agent"));
	    pamc_delete_packet(datum_pp);
	    pamc_scrub((void **)&id, length);
	    return PAMC_CONTROL_FAIL;
	}

	D(("agent primed and ready for dialogue"));
    }

    /* do we have an agent waiting to receive? */
    if (pch->current == NULL || pch->current->status != PAMC_CONTROL_BUSY) {
	D(("no agent ready to receive data"));
	pamc_delete_packet(datum_pp);
	return PAMC_CONTROL_FAIL;
    }

    /* correct length with the 4 characters of the control */
    length += 4;
    pamc_write__u32(prefix, length);

    /* agent should be silent here */
    if (pamc_silent_fd(pch->current->from_agent) != PAMC_CONTROL_OK) {
	D(("agent is not silent?"));
	goto abort_exchange;
    }

    /** WRITE CONTROL+PACKET **/

    /* write length */
    if (pamc_push_data(pch->current->to_agent, prefix, 4) != PAMC_CONTROL_OK) {
	D(("failed to send new packet length"));
	goto abort_exchange;
    }

    /* write control */
    D(("write the control packet"));
    pamc_write__u32(prefix, control);
    if (pamc_push_data(pch->current->to_agent, prefix, 4) != PAMC_CONTROL_OK) {
	D(("failed to send control"));
	goto abort_exchange;
    }

    /* write packet */
    D(("push the data down the pipe"));
    if (pamc_push_data(pch->current->to_agent, pamc_packet_data(*datum_pp)
		       , length-4) != PAMC_CONTROL_OK) {
	D(("failed to send packet data"));
	goto abort_exchange;
    }

    /** READ CONTROL+PACKET **/

    /* read head */
    D(("wait for the reply"));
    if (pamc_pull_data(pch->current->from_agent, prefix, 4)
	!= PAMC_CONTROL_OK) {
	D(("failed to read length"));
	goto abort_exchange;
    }

    /* deduce length of data (4 less that head indicates) */
    D(("consider what was returned"));
    length = pamc_packet_length(prefix);
    if (length < 4) {
	D(("length is too short (%u)", length));
	goto abort_exchange;
    }
    length -= 4;

    /* prepare a packet to receive it */
    pamc_new_packet(datum_pp, length);
    if (*datum_pp == NULL) {
	D(("failed to prepare packet"));
	goto abort_exchange;
    }

    /* read control */
    if (pamc_pull_data(pch->current->from_agent, prefix, 4)
	!= PAMC_CONTROL_OK) {
	D(("failed to read control"));
	goto abort_exchange;
    }
    control = pamc_read__u32(prefix);

    /* read packet data */
    if (pamc_pull_data(pch->current->from_agent, pamc_packet_data(*datum_pp),
		       length) != PAMC_CONTROL_OK) {
	D(("failed to read packet"));
	goto abort_exchange;
    }    

    /* agent should be silent again */
    if (pamc_silent_fd(pch->current->from_agent) == PAMC_CONTROL_OK
	&& control != PAMC_CONTROL_ABORT) {
	D(("got packet!"));
	memset(prefix, 0, 4);
	return control;
    }

abort_exchange:

    D(("exchange aborted"));
    memset(prefix, 0, 4);
    pamc_delete_packet(datum_pp);

    pch->current->status = PAMC_CONTROL_FAIL;
    return PAMC_CONTROL_ABORT;
}
