/* doscan - Denial Of Service Capable Auditing of Networks       -*- C++ -*-
 * Copyright (C) 2003 Florian Weimer
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"
#include "half_duplex.h"
#include "opt.h"

#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <string>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif

half_duplex_handler::half_duplex_handler(event_queue& q, int fd,
                                         bool first_read)
  : fd_handler (q, fd, first_read ? watch_read : watch_write),
    state(0), receive_goal(0)
{
}

half_duplex_handler::~half_duplex_handler()
{
  int old_fd = fd();
  if (old_fd >= 0) {
    unwatch();
    close(old_fd);
  }
}

bool
half_duplex_handler::on_timeout(ticks_t)
{
  error(ETIMEDOUT);
  return false;
}

bool
half_duplex_handler::on_activity(activity act)
{
  switch (act) {
  case activity_read:
    return on_activity_read();

  case activity_write:
    return on_activity_write();

  case activity_error:
    error(get_error());
    return false;

  case activity_read_write:
    fprintf(stderr, "%s: half-duplex connection on %d is ready for "
            "reading and writing\n", opt_program, fd());
    exit(EXIT_FAILURE);
  };

  abort();
}

bool
half_duplex_handler::on_activity_read()
{
  unsigned current_size = receive_buffer.size();
  unsigned to_read;

  // Determine the number of bytes to read.

  if (receive_goal > 0) {
    to_read = receive_goal - current_size;
  } else {
    int pending;
    int result = ioctl(fd(), FIONREAD, &pending);
    if (result == -1) {
      error(errno);
      return false;
    }
    to_read = pending;
  }

  // Grow the receive buffer as necessary and fill it with available
  // data.

  receive_buffer.resize(current_size + to_read);
  int result = read(fd(), &receive_buffer[current_size], to_read);

  // Now process the data.

  if (result > 0) {
    if (static_cast<unsigned>(result) == to_read) {
      // We got all the data we wanted.
      return invoke_ready();

    } else {
      // We received less data than expected.  We have to shrink the
      // string.
      receive_buffer.resize(current_size + result);
      if (!receive_goal) {
        // There was less data than exepcted, but it's still enough if
        // we weren't given an exact number of bytes.

        return invoke_ready();

      } else {
        // We have to wait for more data.
        return true;
      }
    }

  } else if (result == 0) {
    // Peer closed the connection.
    error(0);
    return false;

  } else {                      // result == -1
    // We encountered an error.
    int err = errno;
    switch (err) {
    case EWOULDBLOCK:
    case EINTR:
      // Non-permanent errors, ignore them.
      return true;

    default:
      error(err);
      return false;
    }
  }

  abort();
}

bool
half_duplex_handler::on_activity_write()
{
  unsigned to_write = send_buffer.size() - send_offset;
  int result = write(fd(), &send_buffer[send_offset], to_write);
  if (result > 0) {
    send_offset += result;
    if (static_cast<unsigned>(result) == to_write) {
      // The send operation is complete.
      return invoke_ready();
    } else {
      // There still is more data to send.
      return true;
    }

  } else if (result == 0) {
    // Peer closed the connection.
    error(0);
    return false;

  } else {
    // We encountered an error.
    int err = errno;
    switch (err) {
    case EWOULDBLOCK:
    case EINTR:
      // Non-permanent errors, ignore them.
      return true;

    default:
      error(err);
      return false;
    }
  }

  abort();
}

void
half_duplex_handler::request_data(unsigned count)
{
  receive_buffer.clear();
  receive_goal = count;
  watch(watch_read);
  stop = false;
}

int
half_duplex_handler::get_error()
{
  int error = 0;
  socklen_t len = sizeof(error);

  errno = 0;
  if (getsockopt(fd(), SOL_SOCKET, SO_ERROR, &error, &len) >= 0) {
    // Non-Solaris case: error is overwritten with the error code,
    // Nothing to do.
  } else {
    // Solaris case: errno has the error code.
    error = errno;
  }

  return error;
}
