/*
 * "$Id: sidechannel.c 6649 2007-07-11 21:46:42Z mike $"
 *
 *   Side-channel API code for the Common UNIX Printing System (CUPS).
 *
 *   Copyright 2007 by Apple Inc.
 *   Copyright 2006 by Easy Software Products.
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Apple Inc. and are protected by Federal copyright
 *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
 *   which should have been included with this file.  If this file is
 *   file is missing or damaged, see the license at "http://www.cups.org/".
 *
 *   This file is subject to the Apple OS-Developed Software exception.
 *
 * Contents:
 *
 *   cupsSideChannelDoRequest() - Send a side-channel command to a backend
 *                                and wait for a response.
 *   cupsSideChannelRead()      - Read a side-channel message.
 *   cupsSideChannelWrite()     - Write a side-channel message.
 */

/*
 * Include necessary headers...
 */

#include "sidechannel.h"
#include "string.h"
#include <unistd.h>
#include <errno.h>
#ifdef __hpux
#  include <sys/time.h>
#else
#  include <sys/select.h>
#endif /* __hpux */
#ifndef WIN32
#  include <sys/time.h>
#endif /* !WIN32 */
#ifdef HAVE_POLL
#  include <sys/poll.h>
#endif /* HAVE_POLL */


/*
 * 'cupsSideChannelDoRequest()' - Send a side-channel command to a backend and wait for a response.
 *
 * This function is normally only called by filters, drivers, or port
 * monitors in order to communicate with the backend used by the current
 * printer.  Programs must be prepared to handle timeout or "not
 * implemented" status codes, which indicate that the backend or device
 * do not support the specified side-channel command.
 *
 * The "datalen" parameter must be initialized to the size of the buffer
 * pointed to by the "data" parameter.  cupsSideChannelDoRequest() will
 * update the value to contain the number of data bytes in the buffer.
 *
 * @since CUPS 1.3@
 */

cups_sc_status_t			/* O  - Status of command */
cupsSideChannelDoRequest(
    cups_sc_command_t command,		/* I  - Command to send */
    char              *data,		/* O  - Response data buffer pointer */
    int               *datalen,		/* IO - Size of data buffer on entry, number of bytes in buffer on return */
    double            timeout)		/* I  - Timeout in seconds */
{
  cups_sc_status_t	status;		/* Status of command */
  cups_sc_command_t	rcommand;	/* Response command */


  if (cupsSideChannelWrite(command, CUPS_SC_STATUS_NONE, NULL, 0, timeout))
    return (CUPS_SC_STATUS_TIMEOUT);

  if (cupsSideChannelRead(&rcommand, &status, data, datalen, timeout))
    return (CUPS_SC_STATUS_TIMEOUT);

  if (rcommand != command)
    return (CUPS_SC_STATUS_BAD_MESSAGE);

  return (status);
}


/*
 * 'cupsSideChannelRead()' - Read a side-channel message.
 *
 * This function is normally only called by backend programs to read
 * commands from a filter, driver, or port monitor program.  The
 * caller must be prepared to handle incomplete or invalid messages
 * and return the corresponding status codes.
 *
 * The "datalen" parameter must be initialized to the size of the buffer
 * pointed to by the "data" parameter.  cupsSideChannelDoRequest() will
 * update the value to contain the number of data bytes in the buffer.
 *
 * @since CUPS 1.3@
 */

int					/* O - 0 on success, -1 on error */
cupsSideChannelRead(
    cups_sc_command_t *command,		/* O - Command code */
    cups_sc_status_t  *status,		/* O - Status code */
    char              *data,		/* O - Data buffer pointer */
    int               *datalen,		/* IO - Size of data buffer on entry, number of bytes in buffer on return */
    double            timeout)		/* I  - Timeout in seconds */
{
  char		buffer[16388];		/* Message buffer */
  int		bytes;			/* Bytes read */
  int		templen;		/* Data length from message */
#ifdef HAVE_POLL
  struct pollfd	pfd;			/* Poll structure for poll() */
#else /* select() */
  fd_set	input_set;		/* Input set for select() */
  struct timeval stimeout;		/* Timeout value for select() */
#endif /* HAVE_POLL */


 /*
  * Range check input...
  */

  if (!command || !status)
    return (-1);

 /*
  * See if we have pending data on the side-channel socket...
  */

#ifdef HAVE_POLL
  pfd.fd     = CUPS_SC_FD;
  pfd.events = POLLIN;

  if (timeout < 0.0)
  {
    if (poll(&pfd, 1, -1) < 1)
      return (-1);
  }
  else if (poll(&pfd, 1, (long)(timeout * 1000)) < 1)
    return (-1);

#else /* select() */
  FD_ZERO(&input_set);
  FD_SET(CUPS_SC_FD, &input_set);

  if (timeout < 0.0)
  {
    if (select(CUPS_SC_FD + 1, &input_set, NULL, NULL, NULL) < 1)
      return (-1);
  }
  else
  {
    stimeout.tv_sec  = (int)timeout;
    stimeout.tv_usec = (int)(timeout * 1000000) % 1000000;

    if (select(CUPS_SC_FD + 1, &input_set, NULL, NULL, &stimeout) < 1)
      return (-1);
  }
#endif /* HAVE_POLL */

 /*
  * Read a side-channel message for the format:
  *
  * Byte(s)  Description
  * -------  -------------------------------------------
  * 0        Command code
  * 1        Status code
  * 2-3      Data length (network byte order) <= 16384
  * 4-N      Data
  */

  while ((bytes = read(CUPS_SC_FD, buffer, sizeof(buffer))) < 0)
    if (errno != EINTR && errno != EAGAIN)
      return (-1);

 /*
  * Validate the command code in the message...
  */

  if (buffer[0] < CUPS_SC_CMD_SOFT_RESET || buffer[0] > CUPS_SC_CMD_GET_STATE)
    return (-1);

  *command = (cups_sc_command_t)buffer[0];

 /*
  * Validate the data length in the message...
  */

  templen = ((buffer[2] & 255) << 8) | (buffer[3] & 255);

  if (templen > 0 && (!data || !datalen))
  {
   /*
    * Either the response is bigger than the provided buffer or the
    * response is bigger than we've read...
    */

    *status = CUPS_SC_STATUS_TOO_BIG;
  }
  else if (templen > *datalen || templen > (bytes - 4))
  {
   /*
    * Either the response is bigger than the provided buffer or the
    * response is bigger than we've read...
    */

    *status = CUPS_SC_STATUS_TOO_BIG;
  }
  else
  {
   /*
    * The response data will fit, copy it over and provide the actual
    * length...
    */

    *status  = (cups_sc_status_t)buffer[1];
    *datalen = templen;

    memcpy(data, buffer + 4, templen);
  }

  return (0);
}


/*
 * 'cupsSideChannelWrite()' - Write a side-channel message.
 *
 * This function is normally only called by backend programs to send
 * responses to a filter, driver, or port monitor program.
 *
 * @since CUPS 1.3@
 */

int					/* O - 0 on success, -1 on error */
cupsSideChannelWrite(
    cups_sc_command_t command,		/* I - Command code */
    cups_sc_status_t  status,		/* I - Status code */
    const char        *data,		/* I - Data buffer pointer */
    int               datalen,		/* I - Number of bytes of data */
    double            timeout)		/* I - Timeout in seconds */
{
  char		buffer[16388];		/* Message buffer */
  int		bytes;			/* Bytes written */
#ifdef HAVE_POLL
  struct pollfd	pfd;			/* Poll structure for poll() */
#else /* select() */
  fd_set	output_set;		/* Output set for select() */
  struct timeval stimeout;		/* Timeout value for select() */
#endif /* HAVE_POLL */


 /*
  * Range check input...
  */

  if (command < CUPS_SC_CMD_SOFT_RESET || command > CUPS_SC_CMD_GET_STATE ||
      datalen < 0 || datalen > 16384 || (datalen > 0 && !data))
    return (-1);

 /*
  * See if we can safely write to the side-channel socket...
  */

#ifdef HAVE_POLL
  pfd.fd     = CUPS_SC_FD;
  pfd.events = POLLOUT;

  if (timeout < 0.0)
  {
    if (poll(&pfd, 1, -1) < 1)
      return (-1);
  }
  else if (poll(&pfd, 1, (long)(timeout * 1000)) < 1)
    return (-1);

#else /* select() */
  FD_ZERO(&output_set);
  FD_SET(CUPS_SC_FD, &output_set);

  if (timeout < 0.0)
  {
    if (select(CUPS_SC_FD + 1, NULL, &output_set, NULL, NULL) < 1)
      return (-1);
  }
  else
  {
    stimeout.tv_sec  = (int)timeout;
    stimeout.tv_usec = (int)(timeout * 1000000) % 1000000;

    if (select(CUPS_SC_FD + 1, NULL, &output_set, NULL, &stimeout) < 1)
      return (-1);
  }
#endif /* HAVE_POLL */

 /*
  * Write a side-channel message in the format:
  *
  * Byte(s)  Description
  * -------  -------------------------------------------
  * 0        Command code
  * 1        Status code
  * 2-3      Data length (network byte order) <= 16384
  * 4-N      Data
  */

  buffer[0] = command;
  buffer[1] = status;
  buffer[2] = datalen >> 8;
  buffer[3] = datalen & 255;

  bytes = 4;

  if (datalen > 0)
  {
    memcpy(buffer + 4, data, datalen);
    bytes += datalen;
  }

  while (write(CUPS_SC_FD, buffer, bytes) < 0)
    if (errno != EINTR && errno != EAGAIN)
      return (-1);

  return (0);
}


/*
 * End of "$Id: sidechannel.c 6649 2007-07-11 21:46:42Z mike $".
 */
