/* 
 * $Id: packet.c,v 1.15 91/05/02 22:26:20 qjb Exp $
 * $Source: /afs/athena.mit.edu/astaff/project/qrpc/src/RCS/packet.c,v $
 * $Author: qjb $
 *
 * This file contains the code that actually deals with sending and 
 * receiving packets.
 */

#if !defined(lint) && !defined(SABER) && !defined(RCS_HDRS)
static char *rcsid = "Id:$";
#endif /* lint || SABER || RCS_HDRS */

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>

#include "qrpc.h"
#include "qrpc_private.h"

typedef struct {
    bit16 raw_opcode;
    bit32 raw_length;
} raw_pkt_header;

typedef struct {
    qrpc_opcode_t opcode;
    qrpc_length_t length;
} pkt_header;


#ifdef __STDC__
static qrpc_error_t qrpci_net_write(qrpc_t qrpc, char *data, int length)
#else
static qrpc_error_t qrpci_net_write(qrpc, data, length)
  qrpc_t qrpc;
  char *data;
  int length;
#endif /* __STDC__ */
{
    if (qrpc->connected) {
	int len;
	qrpc_error_t status = QRPC_SUCCESS;

	/*
	 * If non-blocking IO is ever supported, then
	 * this check will be incorrect.  Instead, this routine
	 * would have to keep writing until all the data were sent.
	 * This isn't coded since there is no reason for a client
	 * that uses non-blocking I/O to use this method of sending
	 * packets.
	 */
	if ((len = write(qrpc->out, data, length)) != length) {
	    if (len == -1) {
		if ((errno == EPIPE) || (errno = ECONNRESET))
		    status = QRPC_ERR_NOPEER;
		else
		    status = QRPC_ERR_WRITE;		    
		sprintf(qrpc->errmsg, "Error writing to network: %s", 
			sys_errlist[errno]);
	    }
	    else {
		status = QRPC_ERR_WRITE;
		sprintf(qrpc->errmsg, 
			"write: %d bytes written; %d bytes actually sent", 
			length, len);
	    }
	    return(status);
	}

	return (QRPC_SUCCESS);
    }
    else
	return (QRPC_ERR_NOTCONN);
}


#ifdef __STDC__
static qrpc_error_t qrpci_net_read(qrpc_t qrpc, char *data, int length)
#else
static qrpc_error_t qrpci_net_read(qrpc, data, length)
  qrpc_t qrpc;
  char *data;
  int length;
#endif /* __STDC__ */
{
    qrpc_error_t status;

    if (qrpc->connected) {
	int len;
	int bytes_read = 0;

	while (bytes_read < length) {
	    if ((len = read(qrpc->in, data + bytes_read, 
			    length - bytes_read)) < 0) {
		if (errno == ECONNRESET)
		    status = QRPC_ERR_NOPEER;
		else
		    status = QRPC_ERR_READ;
		sprintf(qrpc->errmsg, "Error reading from network: %s",
			sys_errlist[errno]);
		return(status);
	    }
	    else if (len == 0) {
		/* 
		 * An end of file means one of the following:
		 *   The connection has been closed.
		 *   There is a bug in this library resulting in a
		 *     discrepency between length sent and length
		 *     marked in packet.
		 *   There is an application protocol error in which
		 *     one side is breaking qrpc's abstraction barrier
		 *     and the other side isn't.
		 * 
		 * Since case I is the one that we should optomize for
		 * since it is the normal case, the error message string
		 * will be truthful but the error code will be NOPEER.
		 * This way, applications can check this error code
		 * to determine that the connection has been dropped.
		 */
		sprintf(qrpc->errmsg, "EOF detected during network read");
		return (QRPC_ERR_NOPEER);
	    }
	    
	    bytes_read += len;
	}
	
	return (QRPC_SUCCESS);
    }
    else
	return (QRPC_ERR_NOTCONN);
}


#ifdef __STDC__
static qrpc_error_t qrpci_opcode_to_string(qrpc_t qrpc, qrpc_opcode_t opcode,
					   char **name)
#else
static qrpc_error_t qrpci_opcode_to_string(qrpc, opcode, name)
  qrpc_t qrpc;
  qrpc_opcode_t opcode;
  char **name;
#endif /* __STDC__ */
{
    if (opcode < QRPC_APP_OPCODE_OFFSET) 
	/* This is an internal opcode */
	*name = qrpci_valid_opcodes[opcode].name;
    else {
	opcode -= QRPC_APP_OPCODE_OFFSET;
	if (opcode <= qrpc->max_valid_opcode) 
	    *name = qrpc->valid_opcodes[opcode].name;
	else {
	    *name = "**UNKNOWN OPCODE**";
	    return (QRPC_ERR_ILLEGAL_OPCODE);
	}
    }

    return (QRPC_SUCCESS);
}


#ifdef __STDC__
qrpc_error_t qrpci_send_packet(qrpc_t qrpc, qrpc_opcode_t opcode,
			       qrpc_data_t data, qrpc_length_t length)
#else
qrpc_error_t qrpci_send_packet(qrpc, opcode, data, length)
  qrpc_t qrpc;
  qrpc_opcode_t opcode;
  qrpc_data_t data;
  qrpc_length_t length;
#endif /* __STDC__ */
{
    qrpc_error_t status = QRPC_SUCCESS;
    raw_pkt_header header;

    if (! IS32BIT(length)) {
	sprintf(qrpc->errmsg, "Length must be a thirty-two-bit quantity.");
	return (QRPC_ERR_ILLEGAL_LENGTH);
    }

    if (! IS16BIT(opcode)) {
	sprintf(qrpc->errmsg, "Opcode must be a sixteen-bit quantity");
	return (QRPC_ERR_ILLEGAL_OPCODE);
    }

    /* Set up packet header */
    SBCLEAR(header);
    (void) qrpci_ushort_to_bit16(opcode, header.raw_opcode);
    (void) qrpci_ulong_to_bit32(length, header.raw_length);

    status = qrpci_net_write(qrpc, (char *)&header, sizeof(header));
	
    /* Send packet data */
    if ((status == QRPC_SUCCESS) && (length > 0))
	status = qrpci_net_write(qrpc, (char *)data, (int)length);

    return(status);
}


#ifdef __STDC__ 
static qrpc_error_t qrpci_recv_pkt_header(qrpc_t qrpc, pkt_header *header)
#else
static qrpc_error_t qrpci_recv_pkt_header(qrpc, header)
  qrpc_t qrpc;
  pkt_header *header;
#endif /* __STDC__ */
{
    raw_pkt_header raw_header;
    qrpc_error_t status = QRPC_SUCCESS;

    switch (qrpc->state) {
      case QRPC_ST_EXP_HEADER:
	break;
      case QRPC_ST_EXP_DATA:
	sprintf(qrpc->errmsg, 
		"qrpci_recv_pkt_header called while expecting data");
	status = QRPC_ERR_INTERNAL;
	break;
      default:
	sprintf(qrpc->errmsg,
		"qrpci_recv_pkt_header detects illegal qrpc->state (%d)",
		qrpc->state);
	status = QRPC_ERR_INTERNAL;
    }

    if (status)
	return (status);

    SBCLEAR(raw_header);

    /* Try to read packet header */
    
    qrpci_dprint(qrpc, "Trying to read header.");
    status = qrpci_net_read(qrpc, (char *)&raw_header, sizeof(raw_header));
    qrpci_dprint(qrpc, "Read header");
    
    if (status == QRPC_SUCCESS) {
	(void) qrpci_bit16_to_ushort(raw_header.raw_opcode, 
				     &(header->opcode));
	(void) qrpci_bit32_to_ulong(raw_header.raw_length, 
				    &(header->length));
    }

    return (status);
}


#ifdef __STDC__
qrpc_error_t qrpci_recv_packet(qrpc_t qrpc, qrpc_opcode_t opcode,
			       qrpc_data_t data, qrpc_length_t *lengthp)
  /*
   * Requires:
   *   When procedure is called, data must point to at least *lengthp
   *   bytes.
   * Modifies:
   *   data
   *   *lengthp
   * Effects:
   *   Attempts to read a packet of opcode "opcode" containing no more
   *   than "*lengthp" bytes of data.  "*lengthp" bytes of "data" are
   *   cleared before any data are read.  If the data are read
   *   succesfully, "*lengthp" will be set to the number of bytes
   *   actually read.  If the opcode read is "QRPC_OP_STATUS" and the
   *   incoming length is greater than zero, the incoming message is
   *   stored as an error message from the other end of the
   *   connection.  Otherwise, a mismatched opcode, a possible
   *   overflow, or an error in one of the routines called by this
   *   one, *lengthp is left unmodified and an error code is returned.
   */
#else
qrpc_error_t qrpci_recv_packet(qrpc, opcode, data, lengthp)
  qrpc_t qrpc;
  qrpc_opcode_t opcode;
  qrpc_data_t data;
  qrpc_length_t *lengthp;
#endif /* __STDC__ */
{
    qrpc_error_t status = QRPC_SUCCESS;
    qrpc_bool remote_error = FALSE;

    pkt_header header;
    int length = *lengthp;
    
    switch (qrpc->state) {
      case QRPC_ST_EXP_HEADER:
	break;
      case QRPC_ST_EXP_DATA:
	sprintf(qrpc->errmsg, 
		"qrpci_recv_packet called while expecting data");
	status = QRPC_ERR_INTERNAL;
	break;
      default:
	sprintf(qrpc->errmsg,
		"qrpci_recv_packet detects illegal qrpc->state (%d)",
		qrpc->state);
	status = QRPC_ERR_INTERNAL;
    }

    if (status)
	return(status);

    qrpci_dprint(qrpc, "About to clear data in recv_packet");
    bzero((char *)data, length);
    qrpci_dprint(qrpc, "Cleared data in recv_packet");
    
    /* Try to read packet header */
    status = qrpci_recv_pkt_header(qrpc, &header);
    
    /* Check opcode */
    if (status == QRPC_SUCCESS) {
	if (opcode == header.opcode)
	    status = QRPC_SUCCESS;
	else if ((header.opcode == QRPC_OP_STATUS) &&
		 (header.length > 0)) {
	    BCLEAR(qrpc->errmsg);
	    length = (qrpc_length_t) sizeof(qrpc->errmsg);
	    data = (qrpc_data_t) qrpc->errmsg;
	    remote_error = TRUE;
	}
	else {
	    char *exp_name = NULL;
	    char *act_name = NULL;
	    
	    qrpci_opcode_to_string(qrpc, opcode, &exp_name);
	    qrpci_opcode_to_string(qrpc, header.opcode, &act_name);
	    sprintf(qrpc->errmsg, 
		    "Expected packet opcode of %s (%d); got %s (%d)",
		    exp_name, opcode, act_name, header.opcode);
	    status = QRPC_ERR_PACKET;
	}
    }
    
    /* Check length */
    if (status == QRPC_SUCCESS) {
	if (header.length > length) {
	    sprintf(qrpc->errmsg, "%s %d %s %d",
		    "expected to receive", header.length,
		    "bytes, but only had room for", length);
	    status = QRPC_ERR_PACKET;
	}
    }
    
    /* Read data */
    
    *lengthp = header.length;
    qrpci_dprint(qrpc, "About to read data");
    if ((status == QRPC_SUCCESS) && (header.length > 0))
	status = qrpci_net_read(qrpc, (char *)data, (int)header.length);
    else
	qrpci_dprint(qrpc, "Not reading because zero length expected");

    qrpci_dprint(qrpc, "Leaving recv_packet");

    if (status)
	return(status);
    else if (remote_error)
	return(QRPC_ERR_REMOTE);
    else
	return(QRPC_SUCCESS);
}


#ifdef __STDC__
qrpc_error_t qrpc_send(qrpc_t qrpc, qrpc_opcode_t opcode, 
		       qrpc_data_t data, qrpc_length_t length)
#else
qrpc_error_t qrpc_send(qrpc, opcode, data, length)
  qrpc_t qrpc;
  qrpc_opcode_t opcode;
  qrpc_data_t data;
  qrpc_length_t length;
#endif /* __STDC__ */
{
    if (opcode > qrpc->max_valid_opcode) {
	sprintf(qrpc->errmsg, "Application opcode %d out of range", opcode);
	return(QRPC_ERR_ILLEGAL_OPCODE);
    }

    return(qrpci_send_packet(qrpc, opcode + QRPC_APP_OPCODE_OFFSET,
			     data, length));
}


#ifdef __STDC__
qrpc_error_t qrpc_recv_header(qrpc_t qrpc, qrpc_opcode_t *opcode, 
			      qrpc_length_t *length)
#else
qrpc_error_t qrpc_recv_header(qrpc, opcode, length)
  qrpc_t qrpc;
  qrpc_opcode_t *opcode;
  qrpc_length_t *length;
#endif /* __STDC__ */
{
    qrpc_error_t status = QRPC_SUCCESS;
    
    pkt_header header;
    
    switch (qrpc->state) {
      case QRPC_ST_EXP_HEADER:
	break;
      case QRPC_ST_EXP_DATA:
	sprintf(qrpc->errmsg, 
		"qrpc_recv_header called while expecting data");
	status = QRPC_ERR_APP;
	break;
      default:
	sprintf(qrpc->errmsg,
		"qrpc_recv_header detects illegal qrpc->state (%d)",
		qrpc->state);
	status = QRPC_ERR_INTERNAL;
    }
    
    if (status)
	return(status);
    
    /* Try to read packet header */
    status = qrpci_recv_pkt_header(qrpc, &header);

    if (status)
	return(status);

    if ((header.opcode == QRPC_OP_STATUS) &&
	(header.length > 0)) {
	/* We have a remote error; handle it. */
	if (header.length >= sizeof(qrpc->errmsg)) {
	    sprintf(qrpc->errmsg, "%d%s %d %s", header.length,
		    "-byte error message being sent; can only receive",
		    sizeof(qrpc->errmsg) - 1, "bytes");
	    return(QRPC_ERR_PACKET);
	}
	qrpc->state = QRPC_ST_EXP_DATA;
	qrpc->expected_length = header.length;
	BCLEAR(qrpc->errmsg);
	if (status = qrpc_recv_data(qrpc, (qrpc_data_t) qrpc->errmsg))
	    return(status);
	else
	    return(QRPC_ERR_REMOTE);
    }
    else if (header.opcode < QRPC_APP_OPCODE_OFFSET) {
	sprintf(qrpc->errmsg, "%s %s",
		"Expected application opcode; received qrpc internal opcode:",
		qrpci_valid_opcodes[header.opcode].name);
	return(QRPC_ERR_PACKET);
    }

    qrpc->state = QRPC_ST_EXP_DATA;
    qrpc->expected_length = header.length;
    *length = header.length;
    *opcode = header.opcode - QRPC_APP_OPCODE_OFFSET;

    return(QRPC_SUCCESS);
}


#ifdef __STDC__
qrpc_error_t qrpc_recv_some_data(qrpc_t qrpc, qrpc_data_t data, 
				 qrpc_length_t length)
#else
qrpc_error_t qrpc_recv_some_data(qrpc, data, length)
  qrpc_t qrpc;
  qrpc_data_t data;
  qrpc_length_t length;
#endif /* __STDC__ */
{
    qrpc_error_t status = QRPC_SUCCESS;

    switch (qrpc->state) {
      case QRPC_ST_EXP_DATA:
	break;
      case QRPC_ST_EXP_HEADER:
	sprintf(qrpc->errmsg, 
		"qrpc_recv_some_data called while expecting header");
	status = QRPC_ERR_APP;
	break;
      default:
	sprintf(qrpc->errmsg,
		"qrpc_recv_some_data detects illegal qrpc->state (%d)", 
		qrpc->state);
	status = QRPC_ERR_INTERNAL;
    }
    
    if (status)
	return(status);

    if (qrpc->expected_length < length) {
	sprintf(qrpc->errmsg,
		"qrpc_recv_some_data asked to receive %d %s %d %s", length,
		"bytes when only", qrpc->expected_length, 
		"are available");
	return (QRPC_ERR_APP);
    }

    if (length > 0) {
	bzero((char *)data, length);
	status = qrpci_net_read(qrpc, (char *)data, (int)length);
    }
    
    if (status == QRPC_SUCCESS)
	qrpc->expected_length -= length;

    return (QRPC_SUCCESS);
}


#ifdef __STDC__
qrpc_error_t qrpc_recv_data(qrpc_t qrpc, qrpc_data_t data)
#else
qrpc_error_t qrpc_recv_data(qrpc, data)
  qrpc_t qrpc;
  qrpc_data_t data;
#endif /* __STDC__ */
{
    qrpc_error_t status = QRPC_SUCCESS;

    switch (qrpc->state) {
      case QRPC_ST_EXP_DATA:
	break;
      case QRPC_ST_EXP_HEADER:
	sprintf(qrpc->errmsg, 
		"qrpc_recv_data called while expecting header");
	status = QRPC_ERR_APP;
	break;
      default:
	sprintf(qrpc->errmsg,
		"qrpc_recv_data detects illegal qrpc->state (%d)", 
		qrpc->state);
	status = QRPC_ERR_INTERNAL;
    }
    
    if (status)
	return(status);

    if (qrpc->expected_length > 0) {
	bzero((char *)data, qrpc->expected_length);
	status = qrpci_net_read(qrpc, (char *)data, 
				(int)qrpc->expected_length);
    }
    
    if (status == QRPC_SUCCESS) {
	qrpc->state = QRPC_ST_EXP_HEADER;
	qrpc->expected_length = 0;
    }

    return (QRPC_SUCCESS);
}


#ifdef __STDC__
qrpc_error_t qrpc_recv(qrpc_t qrpc, qrpc_opcode_t opcode,
		       qrpc_data_t data, qrpc_length_t *length)
#else
qrpc_error_t qrpc_recv(qrpc, opcode, data, length)
  qrpc_t qrpc;
  qrpc_opcode_t opcode;
  qrpc_data_t data;
  qrpc_length_t *length;
#endif /* __STDC__ */
{
    if (opcode > qrpc->max_valid_opcode) {
	sprintf(qrpc->errmsg, "Application opcode %d out of range", opcode);
	return(QRPC_ERR_ILLEGAL_OPCODE);
    }

    return(qrpci_recv_packet(qrpc, opcode + QRPC_APP_OPCODE_OFFSET,
			     data, length));
}
