Package erjang.driver.tcp_inet

Source Code of erjang.driver.tcp_inet.TCPINet$AsyncOp

/**
* This file is part of Erjang - A JVM-based Erlang VM
*
* Copyright (c) 2010 by Trifork
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/

package erjang.driver.tcp_inet;

/**
* TODO
*
* - add locks
* - sock_select
*
*/

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import kilim.Pausable;
import kilim.RingQueue;
import erjang.EAtom;
import erjang.EBinList;
import erjang.EBinary;
import erjang.EHandle;
import erjang.EInternalPID;
import erjang.EInternalPort;
import erjang.EObject;
import erjang.EPID;
import erjang.EPort;
import erjang.ERT;
import erjang.ERef;
import erjang.EString;
import erjang.ETuple;
import erjang.ETuple2;
import erjang.ErlangError;
import erjang.NotImplemented;
import erjang.driver.EAsync;
import erjang.driver.EDriverInstance;
import erjang.driver.EDriverTask;
import erjang.driver.IO;
import erjang.driver.NIOSelector;
import erjang.driver.SelectMode;
import erjang.driver.efile.Posix;
import erjang.net.InetSocket;
import erjang.net.Protocol;
import erjang.net.ProtocolFamily;
import erjang.net.ProtocolType;

public class TCPINet extends EDriverInstance implements java.lang.Cloneable {
  static Logger log = Logger.getLogger("erjang.driver.tcp_inet");
  static Logger portlog = Logger.getLogger("erjang.port");

  public class AsyncMultiOp {

    public AsyncOp op;
    public MultiTimerData timeout;
    public AsyncMultiOp next;

  }

  static class MultiTimerData implements Comparable<MultiTimerData>{

    public long when;
    public EPID caller;

    @Override
    public int compareTo(MultiTimerData o) {
      if ( this.when < o.when )
        return -1;
      if ( this.when > o.when )
        return 1;
      return 0;
    }

  }

  static class AsyncOp {

    public AsyncOp(short id, EPID caller, int req, int timeout,
        ERef monitor) {
      this.id = id;
      this.caller = caller;
      this.req = req;
      this.monitor = monitor;
      this.timeout = timeout;
    }

    /** id used to identify reply */
    final short id;

    /** recipient of async reply */
    final EPID caller;

    /* Request id (CONNECT/ACCEPT/RECV) */
    final int req;

    final ERef monitor;

    final public int timeout;
  }

  static enum SockType {
    SOCK_STREAM, SOCK_DGRAM, SOCK_SEQPACKET;
  }

  static enum ActiveType {
    PASSIVE(0), ACTIVE(1), ACTIVE_ONCE(2);
    final int value;

    ActiveType(int val) {
      this.value = val;
    }

    public static ActiveType valueOf(int ival) {
      switch (ival) {
      case 0:
        return PASSIVE;
      case 1:
        return ACTIVE;
      case 2:
        return ACTIVE_ONCE;
      }

      throw new erjang.NotImplemented();
    }
  }

  /* general address encode/decode tag */
  public static final byte INET_AF_INET = 1;
  public static final byte INET_AF_INET6 = 2;
  public static final byte INET_AF_ANY = 3; /* INADDR_ANY or IN6ADDR_ANY_INIT */
  public static final byte INET_AF_LOOPBACK = 4; /*
                           * INADDR_LOOPBACK or
                           * IN6ADDR_LOOPBACK_INIT
                           */

  /* INET_REQ_GETTYPE enumeration */
  public static final int INET_TYPE_STREAM = 1;
  public static final int INET_TYPE_DGRAM = 2;
  public static final int INET_TYPE_SEQPACKET = 3;

  /* INET_LOPT_MODE options */
  public static final int INET_MODE_LIST = 0;
  public static final int INET_MODE_BINARY = 1;

  /* INET_LOPT_DELIVER options */
  public static final int INET_DELIVER_PORT = 0;
  public static final int INET_DELIVER_TERM = 1;

  /* INET_LOPT_ACTIVE options */
  public static final int INET_PASSIVE = 0; /* false */
  public static final int INET_ACTIVE = 1; /* true */
  public static final int INET_ONCE = 2; /* true; active once then passive */

  /* INET_REQ_GETSTATUS enumeration */
  public static final int INET_F_OPEN = 0x0001;
  public static final int INET_F_BOUND = 0x0002;
  public static final int INET_F_ACTIVE = 0x0004;
  public static final int INET_F_LISTEN = 0x0008;
  public static final int INET_F_CON = 0x0010;
  public static final int INET_F_ACC = 0x0020;
  public static final int INET_F_LST = 0x0040;
  public static final int INET_F_BUSY = 0x0080;
  public static final int INET_F_MULTI_CLIENT = 0x0100; /*
                             * Multiple clients for
                             * one descriptor, i.e.
                             * multi-accept
                             */

  /*
   * One numberspace for *_REC_* so if an e.g UDP request is issued* for a TCP
   * socket, the driver can protest.
   */
  public static final int INET_REQ_OPEN = 1;
  public static final int INET_REQ_CLOSE = 2;
  public static final int INET_REQ_CONNECT = 3;
  public static final int INET_REQ_PEER = 4;
  public static final int INET_REQ_NAME = 5;
  public static final int INET_REQ_BIND = 6;
  public static final int INET_REQ_SETOPTS = 7;
  public static final int INET_REQ_GETOPTS = 8;
  /* public static final int INET_REQ_GETIX 9 NOT USED ANY MORE */
  /* public static final int INET_REQ_GETIF 10 REPLACE BY NEW STUFF */
  public static final int INET_REQ_GETSTAT = 11;
  public static final int INET_REQ_GETHOSTNAME = 12;
  public static final int INET_REQ_FDOPEN = 13;
  public static final int INET_REQ_GETFD = 14;
  public static final int INET_REQ_GETTYPE = 15;
  public static final int INET_REQ_GETSTATUS = 16;
  public static final int INET_REQ_GETSERVBYNAME = 17;
  public static final int INET_REQ_GETSERVBYPORT = 18;
  public static final int INET_REQ_SETNAME = 19;
  public static final int INET_REQ_SETPEER = 20;
  public static final int INET_REQ_GETIFLIST = 21;
  public static final int INET_REQ_IFGET = 22;
  public static final int INET_REQ_IFSET = 23;
  public static final int INET_REQ_SUBSCRIBE = 24;
  public static final int INET_REQ_ACCEPT = 26;
  public static final int INET_REQ_LISTEN = 27;
  /* TCP requests */
  public static final int TCP_REQ_ACCEPT = 40;
  public static final int TCP_REQ_LISTEN = 41;
  public static final int TCP_REQ_RECV = 42;
  public static final int TCP_REQ_UNRECV = 43;
  public static final int TCP_REQ_SHUTDOWN = 44;
  public static final int TCP_REQ_MULTI_OP = 45;
  /* UDP and SCTP requests */
  public static final int PACKET_REQ_RECV = 60; /* Common for UDP and SCTP */
  public static final int SCTP_REQ_LISTEN = 61; /*
                         * Different from TCP; not for
                         * UDP
                         */
  public static final int SCTP_REQ_BINDX = 62; /* Multi-home SCTP bind */

  /* INET_REQ_SUBSCRIBE sub-requests */
  public static final byte INET_SUBS_EMPTY_OUT_Q = 1;

  /* TCP additional flags */
  public static final byte TCP_ADDF_DELAY_SEND = 1;
  public static final byte TCP_ADDF_CLOSE_SENT = 2; /*
                           * Close sent (active mode
                           * only)
                           */
  public static final byte TCP_ADDF_DELAYED_CLOSE_RECV = 4; /*
                               * If receive fails,
                               * report
                               * {error,closed}
                               * (passive mode)
                               */
  public static final byte TCP_ADDF_DELAYED_CLOSE_SEND = 8; /*
                               * If send fails,
                               * report
                               * {error,closed}
                               * (passive mode)
                               */

  /*                     *_REQ_* replies */
  public static final byte INET_REP_ERROR = 0;
  public static final byte INET_REP_OK = 1;
  public static final byte INET_REP_SCTP = 2;

  public static final int INET_STATE_CLOSED = 0;
  public static final int INET_STATE_OPEN = (INET_F_OPEN);
  public static final int INET_STATE_BOUND = (INET_STATE_OPEN | INET_F_BOUND);
  public static final int INET_STATE_CONNECTED = (INET_STATE_BOUND | INET_F_ACTIVE);

  public static final int TCP_STATE_CLOSED = INET_STATE_CLOSED;
  public static final int TCP_STATE_OPEN = (INET_F_OPEN);
  public static final int TCP_STATE_BOUND = (TCP_STATE_OPEN | INET_F_BOUND);
  public static final int TCP_STATE_CONNECTED = (TCP_STATE_BOUND | INET_F_ACTIVE);
  public static final int TCP_STATE_LISTEN = (TCP_STATE_BOUND | INET_F_LISTEN);
  public static final int TCP_STATE_CONNECTING = (TCP_STATE_BOUND | INET_F_CON);
  public static final int TCP_STATE_ACCEPTING = (TCP_STATE_LISTEN | INET_F_ACC);
  public static final int TCP_STATE_MULTI_ACCEPTING = (TCP_STATE_ACCEPTING | INET_F_MULTI_CLIENT);

  // options
  public static final byte INET_OPT_REUSEADDR = 0;
  public static final byte INET_OPT_KEEPALIVE = 1;
  public static final byte INET_OPT_DONTROUTE = 2;
  public static final byte INET_OPT_LINGER = 3;
  public static final byte INET_OPT_BROADCAST = 4;
  public static final byte INET_OPT_OOBINLINE = 5;
  public static final byte INET_OPT_SNDBUF = 6;
  public static final byte INET_OPT_RCVBUF = 7;
  public static final byte INET_OPT_PRIORITY = 8;
  public static final byte INET_OPT_TOS = 9;
  public static final byte TCP_OPT_NODELAY = 10;
  public static final byte UDP_OPT_MULTICAST_IF = 11;
  public static final byte UDP_OPT_MULTICAST_TTL = 12;
  public static final byte UDP_OPT_MULTICAST_LOOP = 13;
  public static final byte UDP_OPT_ADD_MEMBERSHIP = 14;
  public static final byte UDP_OPT_DROP_MEMBERSHIP = 15;
  // "Local" options: codes start from 20:
  public static final byte INET_LOPT_BUFFER = 20;
  public static final byte INET_LOPT_HEADER = 21;
  public static final byte INET_LOPT_ACTIVE = 22;
  public static final byte INET_LOPT_PACKET = 23;
  public static final byte INET_LOPT_MODE = 24;
  public static final byte INET_LOPT_DELIVER = 25;
  public static final byte INET_LOPT_EXITONCLOSE = 26;
  public static final byte INET_LOPT_TCP_HIWTRMRK = 27;
  public static final byte INET_LOPT_TCP_LOWTRMRK = 28;
  public static final byte INET_LOPT_BIT8 = 29;
  public static final byte INET_LOPT_TCP_SEND_TIMEOUT = 30;
  public static final byte INET_LOPT_TCP_DELAY_SEND = 31;
  public static final byte INET_LOPT_PACKET_SIZE = 32;
  public static final byte INET_LOPT_READ_PACKETS = 33;
  public static final byte INET_OPT_RAW = 34;
  public static final byte INET_LOPT_TCP_SEND_TIMEOUT_CLOSE = 35;
  // Specific SCTP options: separate range:
  public static final byte SCTP_OPT_RTOINFO = 100;
  public static final byte SCTP_OPT_ASSOCINFO = 101;
  public static final byte SCTP_OPT_INITMSG = 102;
  public static final byte SCTP_OPT_AUTOCLOSE = 103;
  public static final byte SCTP_OPT_NODELAY = 104;
  public static final byte SCTP_OPT_DISABLE_FRAGMENTS = 105;
  public static final byte SCTP_OPT_I_WANT_MAPPED_V4_ADDR = 106;
  public static final byte SCTP_OPT_MAXSEG = 107;
  public static final byte SCTP_OPT_SET_PEER_PRIMARY_ADDR = 108;
  public static final byte SCTP_OPT_PRIMARY_ADDR = 109;
  public static final byte SCTP_OPT_ADAPTATION_LAYER = 110;
  public static final byte SCTP_OPT_PEER_ADDR_PARAMS = 111;
  public static final byte SCTP_OPT_DEFAULT_SEND_PARAM = 112;
  public static final byte SCTP_OPT_EVENTS = 113;
  public static final byte SCTP_OPT_DELAYED_ACK_TIME = 114;
  public static final byte SCTP_OPT_STATUS = 115;
  public static final byte SCTP_OPT_GET_PEER_ADDR_INFO = 116;

  /* INET_REQ_IFGET and INET_REQ_IFSET options */
  public static final int INET_IFOPT_ADDR = 1;
  public static final int INET_IFOPT_BROADADDR = 2;
  public static final int INET_IFOPT_DSTADDR = 3;
  public static final int INET_IFOPT_MTU = 4;
  public static final int INET_IFOPT_NETMASK = 5;
  public static final int INET_IFOPT_FLAGS = 6;
  public static final int INET_IFOPT_HWADDR = 7;

  /* INET_LOPT_BIT8 options */
  public static final int INET_BIT8_CLEAR = 0;
  public static final int INET_BIT8_SET = 1;
  public static final int INET_BIT8_ON = 2;
  public static final int INET_BIT8_OFF = 3;

  /* INET_REQ_GETSTAT enumeration */
  public static final int INET_STAT_RECV_CNT = 1;
  public static final int INET_STAT_RECV_MAX = 2;
  public static final int INET_STAT_RECV_AVG = 3;
  public static final int INET_STAT_RECV_DVI = 4;
  public static final int INET_STAT_SEND_CNT = 5;
  public static final int INET_STAT_SEND_MAX = 6;
  public static final int INET_STAT_SEND_AVG = 7;
  public static final int INET_STAT_SEND_PND = 8;
  public static final int INET_STAT_RECV_OCT = 9; /* received octets */
  public static final int INET_STAT_SEND_OCT = 10; /* sent octets */

  /* INET_IFOPT_FLAGS enumeration */
  public static final int INET_IFF_UP = 0x0001;
  public static final int INET_IFF_BROADCAST = 0x0002;
  public static final int INET_IFF_LOOPBACK = 0x0004;
  public static final int INET_IFF_POINTTOPOINT = 0x0008;
  public static final int INET_IFF_RUNNING = 0x0010;
  public static final int INET_IFF_MULTICAST = 0x0020;
  /* Complement flags for turning them off */
  public static final int INET_IFF_DOWN = 0x0100;
  public static final int INET_IFF_NBROADCAST = 0x0200;
  /* public static final int INET_IFF_NLOOPBACK =0x0400; */
  public static final int INET_IFF_NPOINTTOPOINT = 0x0800;
  /* public static final int INET_IFF_NRUNNING =0x1000; */
  /* public static final int INET_IFF_NMULTICAST =0x2000; */

  /*
   * Flags for "sctp_sndrcvinfo". Used in a bitmask -- must be powers of 2:*
   * INET_REQ_SETOPTS:SCTP_OPT_DEFAULT_SEND_PARAM
   */
  public static final int SCTP_FLAG_UNORDERED = (1 /* am_unordered */);
  public static final int SCTP_FLAG_ADDR_OVER = (2 /* am_addr_over */);
  public static final int SCTP_FLAG_ABORT = (4 /* am_abort */);
  public static final int SCTP_FLAG_EOF = (8 /* am_eof */);
  public static final int SCTP_FLAG_SNDALL = (16 /*
                           * am_sndall, NOT YET
                           * IMPLEMENTED
                           */);

  /*
   * Flags for "sctp_set_opts" (actually for SCTP_OPT_PEER_ADDR_PARAMS).*
   * These flags are also used in a bitmask, so they must be powers of 2:
   */
  public static final int SCTP_FLAG_HB_ENABLE = (1 /* am_hb_enable */);
  public static final int SCTP_FLAG_HB_DISABLE = (2 /* am_hb_disable */);
  public static final int SCTP_FLAG_HB_DEMAND = (4 /* am_hb_demand */);
  public static final int SCTP_FLAG_PMTUD_ENABLE = (8 /* am_pmtud_enable */);
  public static final int SCTP_FLAG_PMTUD_DISABLE = (16 /* am_pmtud_disable */);
  public static final int SCTP_FLAG_SACDELAY_ENABLE = (32 /* am_sackdelay_enable */);
  public static final int SCTP_FLAG_SACDELAY_DISABLE = (64 /* am_sackdelay_disable */);

  public static final int INET_DEF_BUFFER = 1460; /* default buffer size */
  public static final int INET_MIN_BUFFER = 1; /* internal min buffer */
  public static final int INET_MAX_BUFFER = (1024 * 64); /*
                               * internal max
                               * buffer
                               */

  public static final byte[] EXBADPORT = "exbadport".getBytes(Charset
      .forName("ASCII"));
  public static final byte[] EXBADSEQ = "exbadseq".getBytes(Charset
      .forName("ASCII"));

  public static final int INET_HIGH_WATERMARK =(1024*8); /* 8k pending high => busy  */
  /* Note: INET_LOW_WATERMARK MUST be less than INET_MAX_BUFFER and
  ** less than INET_HIGH_WATERMARK
  */
  public static final int INET_LOW_WATERMARK  =(1024*4); /* 4k pending => allow more */

  public static final int INET_INFINITY = 0xffffffff;
  private static final EAtom am_inet_async = EAtom.intern("inet_async");
  private static final EAtom am_inet_reply = EAtom.intern("inet_reply");
  private static final int INVALID_EVENT = 0xffff0000;
  private static final int SOL_SOCKET = 0xffff;
  private static final EAtom am_closed = EAtom.intern("closed");
  private static final EAtom am_empty_out_q = EAtom.intern("empty_out_q");
  private static final EAtom am_tcp = EAtom.intern("tcp");
  private static final EAtom am_tcp_closed = EAtom.intern("tcp_closed");
  private static final EAtom am_timeout = EAtom.intern("timeout");
  private static final EAtom am_tcp_error = EAtom.intern("tcp_error");
  private static final byte[] NOPROC = new byte[] { 'n', 'o', 'p', 'r', 'o', 'c'} ;
  private static final PacketCallbacks<TCPINet> INET_CALLBACKS = new TCPINetCallbacks();

  private int state = INET_STATE_CLOSED;
  private InetSocket fd;
  private List<EHandle> empty_out_q_subs = new ArrayList<EHandle>(1);
  private InetSocketAddress remote;
  ActiveType active = ActiveType.PASSIVE;
  private RingQueue<AsyncOp> opt = new RingQueue<AsyncOp>(1);
  private boolean busy_on_send;
  private EHandle caller;
  private EHandle busy_caller;
  private int i_remain;
  private boolean send_timeout_close;

  private AsyncMultiOp multi_last;
  private AsyncMultiOp multi_first;
  private PriorityQueue<MultiTimerData> mtd_queue;
  private boolean prebound;
  private int event_mask;
  PacketParseType htype = PacketParseType.TCP_PB_RAW;
  private int hsz;
  private int mode = INET_MODE_LIST;
  private int deliver = INET_DELIVER_TERM;
  private int bufsz = 1200;
  private boolean exitf = true;
  private int psize;
  private boolean bit8f = false;
  private boolean bit8 = false;
  private int low = INET_LOW_WATERMARK;
  private int high = INET_HIGH_WATERMARK;
  private int send_timeout = INET_INFINITY;
  private int tcp_add_flags;
  private int read_packets;

  private Protocol protocol;
  private ProtocolType stype;
  private ProtocolFamily sfamily;
  private ByteBuffer i_buf;
  private int i_ptr_start;
  private IntCell http_state = new IntCell();
  private PacketCallbacks<TCPINet> packet_callbacks = INET_CALLBACKS;

  private int recv_cnt;
  private int recv_max;
  private double recv_avg;
  private double recv_dvi;
  private int send_cnt;
  private int send_max;
  private double send_avg;
  private long recv_oct;
  private long send_oct;

  // private int i_ptr;
  // private int i_bufsz;

  public TCPINet(Protocol protocol, Driver driver) {
    super(driver);
    this.protocol = protocol;
    this.bufsz = INET_DEF_BUFFER;
  }

  public TCPINet copy(EPID caller, InetSocket sock) {

    TCPINet copy;
    try {
      copy = (TCPINet) this.clone();
    } catch (CloneNotSupportedException e) {
      throw new InternalError("cannot clone");
    }

    copy.fd = sock;
    copy.caller = caller;
    copy.opt = new RingQueue<AsyncOp>(2);
    copy.empty_out_q_subs = new ArrayList<EHandle>();
    copy.active = ActiveType.PASSIVE;

    final EDriverTask this_task = port().task();
    EDriverTask driver = new EDriverTask(caller, copy) {
      public EObject getName() {
        return this_task.getName();
      }
    };

    // two-way link to new owner
    EInternalPID caller_pid = caller.testInternalPID();
    if (caller_pid != null) {
      caller_pid.task().link_oneway(driver.self_handle());
      driver.link_oneway(caller_pid);
    }
    ERT.run(driver);

    return copy;
  }

  @Override
  protected void flush() throws Pausable {
    throw new erjang.NotImplemented();

  }

  /*
  ** command:
  **   output on a socket only !
  **   a reply code will be sent to connected (caller later)
  **   {inet_reply, S, Status}
  ** NOTE! normal sockets use the the tcp_inet_commandv
  ** but distribution still uses the tcp_inet_command!!
  */

  @Override
  protected void output(EHandle caller, ByteBuffer buf) throws IOException, Pausable {
   
    this.caller = caller;
    if (!is_connected()) {
      inet_reply_error(Posix.ENOTCONN);
    } else if (tcp_sendv(new ByteBuffer[]{buf}) == 0) {
      inet_reply_ok(caller);
    }
   
    //log.finer("OUTPUT!!");
    throw new erjang.NotImplemented();

  }

  @Override
  protected void outputv(EHandle caller, ByteBuffer[] ev)
      throws IOException, Pausable {

    this.caller = caller;

    if (log.isLoggable(Level.FINEST)) {
      dump_buffer(log, "TCPIP::outputv", ev);
    }

    if (!is_connected()) {
      if ((tcp_add_flags & TCP_ADDF_DELAYED_CLOSE_SEND) != 0) {
        tcp_add_flags &= ~TCP_ADDF_DELAYED_CLOSE_SEND;
        inet_reply_error(am_closed);
      } else {
        inet_reply_error(Posix.ENOTCONN);
      }
    } else if (tcp_sendv(ev) == 0) {
      inet_reply_ok(caller);
    } else {
      log.fine("bad output");
    }

  }

  private int tcp_sendv(ByteBuffer[] ev) throws Pausable {
    int sz;
    EPort ix = port();
    long len = remaining(ev);
    ByteBuffer hbuf = null;

    switch (htype) {
    case TCP_PB_1:
      hbuf = ByteBuffer.allocate(1);
      hbuf.put(0, (byte) (len & 0xff));
      break;
    case TCP_PB_2:
      hbuf = ByteBuffer.allocate(2);
      hbuf.putShort(0, (short) (len & 0xffff));
      break;
    case TCP_PB_4:
      hbuf = ByteBuffer.allocate(4);
      hbuf.putInt(0, (int) len);
      break;
    default:
      if (len == 0) {
        return 0;
      }
    }

    inet_output_count(len+ (hbuf==null ? 0 : hbuf.limit()));
   
    if (hbuf != null) {
      len += hbuf.remaining();
      // insert hbuf ahead of ev
      if (ev.length > 0 && ev[0].remaining() == 0) {
        ev[0] = hbuf;
      } else {
        ByteBuffer[] ev2 = new ByteBuffer[ev.length + 1];
        ev2[0] = hbuf;
        System.arraycopy(ev, 0, ev2, 1, ev.length);
        ev = ev2;
      }
    }

    if ((sz = driver_sizeq()) > 0) {
     
      driver_enqv(ev);
      sock_select(ERL_DRV_WRITE, SelectMode.SET);

      //dump_buffer(log, "enqued output [1]!", ev);
      //dump_buffer(log, "queue is now", driver_peekq());

      if (sz + len >= high) {

        // TODO: we somehow fail in this case.
        // The data is put on the queue, but never written?
       
        state |= INET_F_BUSY; /* mark for low-watermark */
        busy_caller = caller;
        set_busy_port(port(), true);
        if (this.send_timeout != INET_INFINITY) {
          busy_on_send = true;
          driver_set_timer(send_timeout);
        }
        return 1;
      }

    } else {
      int vsize = ev.length;
      long n;

      if ((tcp_add_flags & TCP_ADDF_DELAY_SEND) != 0) {
        if (log.isLoggable(Level.FINE))
          log.fine("tcp_delay_send!");
        n = 0;
      } else {
        GatheringByteChannel gbc = (GatheringByteChannel) fd.channel();

        try {

          // dump_buffer(log, null, ev);

          n = gbc.write(ev);

          // log.finer("sent " + n + " bytes to " + gbc);

        } catch (IOException e) {
          int sock_errno = IO.exception_to_posix_code(e);
          if ((sock_errno != Posix.EAGAIN)
              && (sock_errno != Posix.EINTR)) {
            tcp_send_error(sock_errno);
            return sock_errno;
          }
          // TODO: handle partial writes, async
          n = 0;
        }

      }

      if (n == len) {
        // we sent everything!
        assert empty_out_q_subs.isEmpty() : "no subscribers";
        return 0;
      }

     
      //dump_buffer(log, "enqueing output [2]! n="+n, ev);

      driver_enqv(ev);
      sock_select(ERL_DRV_WRITE | 0, SelectMode.SET);

    }
    return 0;

  }

  private void inet_output_count(long len) {
    this.send_oct += len;
    this.send_cnt += 1;
    if (len > send_max)
      send_max = (int) len;
   
    // TODO: Implement avg and dvi
  }

  private long remaining(ByteBuffer[] ev) {
    long res = 0;
    for (int i = 0; i < ev.length; i++) {
      if (ev[i] != null) {
        res += ev[i].remaining();
      }
    }
    return res;
  }

  @Override
  public void processExit(ERef monitor) throws Pausable {

    EHandle who = driver_get_monitored_process(monitor);
    int state = this.state;

    if ((state & TCP_STATE_MULTI_ACCEPTING) == TCP_STATE_MULTI_ACCEPTING) {
      AsyncMultiOp op;
      if ((op = remove_multi_op(who))  == null){
        return;
      }
      if (op.timeout != null) {
        remove_multi_timer(op.timeout);
      }
      if (this.multi_first == null) {
        sock_select(ERL_DRV_ACCEPT, SelectMode.CLEAR);
        this.state = TCP_STATE_LISTEN;
      }

    } else if ((state & TCP_STATE_ACCEPTING) == TCP_STATE_ACCEPTING) {
      deq_async();
      driver_cancel_timer();
      sock_select(ERL_DRV_ACCEPT, SelectMode.CLEAR);
      this.state = TCP_STATE_LISTEN;
    }

  }

  private void sock_select(int ops, SelectMode onOff) {
    if (onOff == SelectMode.SET) {
      this.event_mask |= ops;
    } else {
      this.event_mask &= ~ops;
    }

    if (log.isLoggable(Level.FINE))
    log.fine("sock_select " + this + " ops="
        + Integer.toBinaryString(ops) + "; mode=" + onOff);

    super.select(this.fd.channel(), ops, onOff);
  }

  private void remove_multi_timer(MultiTimerData p) {
    boolean is_first = mtd_queue.peek() == p;
   
    mtd_queue.remove(p);
   
    if (is_first) {
      driver_cancel_timer();
     
      MultiTimerData first = mtd_queue.peek();
      if (first != null) {
        long timeout = relative_timeout( first.when );
        driver_set_timer(timeout);
      }     
    }
   
  }

  private long relative_timeout(long abs_time) {
    long now = System.currentTimeMillis();
    if (abs_time < now)
      return 0;
    else
      return abs_time-now;
  }

  @Override
  protected void readyAsync(EAsync data) throws Pausable {
    throw new erjang.NotImplemented();

  }

  @Override
  protected void readyInput(SelectableChannel ch) throws Pausable {

    if (fd == null || fd.channel() == null)
      return;

    if (!is_open())
      return;

    if (ch.isOpen())
      select(ch, ERL_DRV_READ, SelectMode.SET);

    if (log.isLoggable(Level.FINE))
    log.fine("readyInput " + this + " @ " + ch);

    if (is_connected()) {
      int rcv = tcp_recv(0);
      if (log.isLoggable(Level.FINE))
      log.fine("tcp_recv[async] =>" + rcv);

    } else {
      if (log.isLoggable(Level.FINE))
      log.fine("received input while not connected?");
    }

  }

  private int tcp_recv(int request_len) throws Pausable {
    if (log.isLoggable(Level.FINER))
    log.finer("tcp_recv len="+request_len);
   
    int n, len, nread;

    if (i_buf == null) {
      if (log.isLoggable(Level.FINER))
        log.finer("tcp_recv no ibuf");
      /* allocate a read buffer */
      int sz = (request_len > 0) ? request_len : this.bufsz;

      try {
        i_buf = ByteBuffer.allocate(sz);
      } catch (OutOfMemoryError e) {
        return -1;
      }

      i_ptr_start = 0;
      nread = sz;
      if (request_len > 0) {
        i_remain = request_len;
      } else {
        i_remain = 0;
      }

    } else if (request_len > 0) {
      n = i_buf.position() - i_ptr_start;
      if (log.isLoggable(Level.FINER))
        log.finer("tcp_recv has "+n+" bytes");
      if (n >= request_len) {
        return tcp_deliver(request_len);
      } else if (tcp_expand_buffer(request_len) < 0) {
        return tcp_recv_error(Posix.ENOMEM);
      } else {
        i_remain = nread = request_len - n;
      }

    } else if (i_remain == 0) {
      if (log.isLoggable(Level.FINER))
        log.finer("tcp_recv i_remain == 0");
      int[] lenp = new int[1];
      if ((nread = tcp_remain(lenp)) < 0) {
        return tcp_recv_error(Posix.EMSGSIZE);
      } else if (nread == 0) {
        return tcp_deliver(lenp[0]);
      } else if (lenp[0] > 0) {
        i_remain = lenp[0];
      }

    } else {
      if (log.isLoggable(Level.FINER))
        log.finer("tcp_recv i_remain == "+i_remain);
      nread = i_remain;
    }

    try {
      ReadableByteChannel rbc = (ReadableByteChannel) fd.channel();
     
      if (rbc instanceof SocketChannel) {
        SocketChannel sc = (SocketChannel) rbc;
        if (sc.isBlocking()) {
          log.fine("SOCKET IS BLOCKING! "+sc);
        }
      } else {
        log.warning("NOT A SOCKET! "+rbc);
      }

      // only read up to nread bytes
      i_buf.limit( Math.min (i_buf.position() + nread, i_buf.capacity()) );
     
      n = rbc.read(i_buf);
      if (log.isLoggable(Level.FINER))
        log.finer("did read " + n + " bytes (ibuf.size="+i_buf.remaining()+")");
      if (n == 0)
        return 0;
     
    } catch (ClosedChannelException e) {
      return tcp_recv_closed();
    } catch (IOException e) {
      int code = IO.exception_to_posix_code(e);
      if (code == Posix.ENOTCONN || !fd.isOpen())
        return tcp_recv_closed();
      else
        return tcp_recv_error(IO.exception_to_posix_code(e));
    }

    if (n < 0) {
      try {
        fd.channel().close();
      } catch (IOException e) {
        // ignore
      }
      return tcp_recv_closed();
    }

    if (i_remain > 0) {
      i_remain -= n;
      if (i_remain == 0) {
        return tcp_deliver(i_buf.position() - i_ptr_start);
      }
    } else {
      int[] lenp = new int[1];
      nread = tcp_remain(lenp);

      if ((nread) < 0) {
        return tcp_recv_error(Posix.EMSGSIZE);
      } else if (nread == 0) {
        return tcp_deliver(lenp[0]);
      } else if (lenp[0] > 0) {
        i_remain = lenp[0];
      }
    }

    return 0;
  }

  private int tcp_recv_closed() throws Pausable {
    if (is_busy()) {
      caller = busy_caller;
      tcp_clear_output();
      if (busy_on_send) {
        driver_cancel_timer();
        busy_on_send = false;
      }
      state &= ~INET_F_BUSY;
      set_busy_port(false);
      inet_reply_error(am_closed);
    }

    if (active == ActiveType.PASSIVE) {
      driver_cancel_timer();
      tcp_clear_input();
      if (exitf) {
        tcp_clear_output();
        desc_close();
      } else {
        desc_close_read();
      }
      async_error_all(am_closed);
    } else {
      tcp_clear_input();
      tcp_closed_message();
      if (exitf) {
        driver_exit(0);
      } else {
        desc_close_read();
      }
    }
    return -1;
  }

  private void tcp_clear_output() throws Pausable {
    int qsz = driver_sizeq();

    // clear queue
    ByteBuffer[] q = driver_peekq();
    if (q != null) {
      for (int i = 0; i < q.length; i++) {
        if (q[i] != null) {
          q[i].position(q[i].limit());
        }
      }
    }

    driver_deq(qsz);
    send_empty_out_q_msgs();

  }

  private int tcp_recv_error(int err) throws Pausable {

    if (err == Posix.EAGAIN) {
      return 0;
    }

    if (is_busy()) {
      /* A send is blocked */
      caller = busy_caller;
      tcp_clear_output();
      if (busy_on_send) {
        driver_cancel_timer();
        busy_on_send = false;
      }
      state &= ~INET_F_BUSY;
      set_busy_port(false);
      inet_reply_error(am_closed);
    }
    if (active == ActiveType.PASSIVE) {
      /* We must cancel any timer here ! */
      driver_cancel_timer();
      tcp_clear_input();
      if (exitf) {
        desc_close();
      } else {
        desc_close_read();
      }
      async_error_all(EAtom.intern(Posix.errno_id(err)));
    } else {
      tcp_clear_input();
      tcp_error_message(err); /* first error */
      tcp_closed_message(); /* then closed */
      if (exitf)
        driver_exit(err);
      else
        desc_close();
    }
    return -1;

  }

  private void tcp_error_message(int err) throws Pausable {
    ETuple spec = ETuple.make(am_tcp_error, port(), EAtom.intern(Posix
        .errno_id(err)));
    driver_output_term(spec);
  }

  @Override
  protected void readyOutput(SelectableChannel evtthrows Pausable {


    EInternalPort ix = port();

    if (is_connected()) {
      ByteBuffer[] iov;
      if ((iov = driver_peekq()) == null) {
        select(evt, ERL_DRV_WRITE, SelectMode.CLEAR);
        send_empty_out_q_msgs();
        return;
      }

      GatheringByteChannel gbc = (GatheringByteChannel) fd.channel();
      long n;
      try {
        // dump_buffer(log, null, iov);
        n = gbc.write(iov);
       
        //dump_buffer(log, "delayed write!", iov);


      } catch (IOException e) {
        tcp_send_error(IO.exception_to_posix_code(e));
        return;
      }
     
      driver_deq(n);
     
      int dsq = driver_sizeq();
      if (dsq != 0) {
        select(evt, ERL_DRV_WRITE, SelectMode.SET);
      }
     
      if (dsq <= low) {
        if (is_busy()) {
          this.caller = busy_caller;
          state &= ~INET_F_BUSY;
          set_busy_port(port(), false);
          if (busy_on_send) {
            driver_cancel_timer();
            busy_on_send = false;
          }
          inet_reply_ok(caller);
        }
      }
    }

  }

  private void set_busy_port(EInternalPort port, boolean on) {
    if (on) {
      task.status |= EDriverTask.ERTS_PORT_SFLG_PORT_BUSY;
    } else {
      task.status &= ~EDriverTask.ERTS_PORT_SFLG_PORT_BUSY;
    }
  }

  private void inet_reply_ok(EHandle caller2) throws Pausable {
    ETuple msg = ETuple.make(am_inet_reply, port(), ERT.am_ok);
    EHandle caller = this.caller;
    this.caller = null;

    if (log.isLoggable(Level.FINER) && caller != null) {
      log.finer("sending to " + caller + " ! " + msg);
    }

    driver_send_term(caller, msg);
  }

  private void tcp_send_error(int err) throws Pausable {

    if (is_busy()) {
      caller = busy_caller;
      tcp_clear_output();
      if (busy_on_send) {
        driver_cancel_timer();
        busy_on_send = false;
      }
      state &= ~INET_F_BUSY;
      set_busy_port(false);
    }

    if (active != ActiveType.PASSIVE) {
      tcp_closed_message();
      inet_reply_error(am_closed);
      if (exitf) {
        driver_exit(0);
      } else {
        try {
          fd.close();
        } catch (IOException e) {
          // ignore //
        }
      }
    } else {
      tcp_clear_output();
      tcp_clear_input();
      tcp_close_check();
      erl_inet_close();

      if (caller != null) {
        inet_reply_error(am_closed);
      } else {
        /*
         * No blocking send op to reply to right now. If next op is a
         * send, make sure it returns {error,closed} rather than
         * {error,enotconn}.
         */
        tcp_add_flags |= TCP_ADDF_DELAYED_CLOSE_SEND;
      }

      tcp_add_flags |= TCP_ADDF_DELAYED_CLOSE_RECV;
    }

  }

  private void tcp_close_check() throws Pausable {
    if (state == TCP_STATE_ACCEPTING) {
      AsyncOp op = opt.peek();
      sock_select(ERL_DRV_ACCEPT, SelectMode.CLEAR);
      state = TCP_STATE_LISTEN;
      if (op != null) {
        driver_demonitor_process(op.monitor);
      }
      async_error(am_closed);

    } else if ((state&TCP_STATE_MULTI_ACCEPTING) == TCP_STATE_MULTI_ACCEPTING) {
      sock_select(ERL_DRV_ACCEPT, SelectMode.CLEAR);
      state = TCP_STATE_LISTEN;

      for (AsyncMultiOp op = deq_multi_op(); op != null; op = deq_multi_op()) {
        driver_demonitor_process(op.op.monitor);
        send_async_error(op.op.id, op.op.caller, am_closed);
      }
     
      // clean_multi_timers ?

    } else if ((state&TCP_STATE_CONNECTING) == TCP_STATE_CONNECTING) {
      async_error(am_closed);

    } else if ((state&TCP_STATE_CONNECTED) == TCP_STATE_CONNECTED) {
      async_error_all(am_closed);
    }
  }

  private void async_error_all(EAtom reason) throws Pausable {

    for (AsyncOp op = deq_async(); op != null; op = deq_async()) {
      send_async_error(op.id, op.caller, reason);
    }

  }

  private void send_empty_out_q_msgs() throws Pausable {

    if (empty_out_q_subs.isEmpty())
      return;

    ETuple2 msg = new ETuple2(am_empty_out_q, port());
    for (EHandle h : empty_out_q_subs) {
      h.send(port(), msg);
    }
  }

  @Override
  public void readyAccept(SelectableChannel ch) throws Pausable {
    if (log.isLoggable(Level.FINE))
      log.fine("readyAccept " + this);

    if (state == TCP_STATE_ACCEPTING) {
      InetSocket sock;
      try {
        sock = fd.accept();
      } catch (IOException e) {
        sock = null;
        // return //
      }

      // we got a connection
      sock_select(ERL_DRV_ACCEPT, SelectMode.CLEAR);
      state = TCP_STATE_LISTEN;

      AsyncOp peek = opt.peek();
      if (peek != null) {
        driver_demonitor_process(peek.monitor);
      }

      driver_cancel_timer();

      if (sock == null) {
        // return //
      } else {

        if (peek == null) {
          sock_close(fd.channel());
          async_error(Posix.EINVAL);
          return;
        }

        TCPINet accept_desc = this.copy(peek.caller, sock);
        accept_desc.state = TCP_STATE_CONNECTED;

        try {
          sock.setNonBlocking();
        } catch (IOException e) {
          e.printStackTrace();
        }
        // accept_desc.select(sock.channel(), ERL_DRV_READ,
        // SelectMode.SET);

        async_ok_port(peek.caller, accept_desc.port());
      }
    } else if (state == TCP_STATE_MULTI_ACCEPTING) {
     
      while (state == TCP_STATE_MULTI_ACCEPTING) {
        int err = 0;
        InetSocket sock;
        try {
          sock = fd.accept();
          if (sock == null) {
            sock_select(ERL_DRV_ACCEPT, SelectMode.SET);
            return;
          }
        } catch (IOException e) {
          sock = null;
          err = IO.exception_to_posix_code(e);
        }
 
        AsyncMultiOp multi = deq_multi_op();
        if (multi == null) {
          return; /* -1; close? */
        }

        if (this.multi_first == null) {
          // we got a connection, and there are no more multi's waiting
          sock_select(ERL_DRV_ACCEPT, SelectMode.CLEAR);
          state = TCP_STATE_LISTEN;
        }
       
        if (multi.timeout != null) {
          remove_multi_timer(multi.timeout);
        }
       
        driver_demonitor_process(multi.op.monitor);

        if (sock == null) {
          send_async_error(multi.op.id, multi.op.caller,
              EAtom.intern(Posix.errno_id(err).toLowerCase()));
          // return //
        } else {
 
          TCPINet accept_desc = this.copy(multi.op.caller, sock);
          accept_desc.state = TCP_STATE_CONNECTED;
 
          try {
            sock.setNonBlocking();
          } catch (IOException e) {
            e.printStackTrace();
          }
          // accept_desc.select(sock.channel(), ERL_DRV_READ,
          // SelectMode.SET);
 
          send_async_ok_port(multi.op.id, multi.op.caller, accept_desc.port());
        }
      }
    }
  }

  @Override
  public void readyConnect(SelectableChannel evt) throws Pausable {

    if (log.isLoggable(Level.FINE))
      log.fine("readyConnect " + this);

    if (state == TCP_STATE_CONNECTING) {
      // clear select state
      sock_select(ERL_DRV_CONNECT, SelectMode.CLEAR);

      // cancel the timer
      driver_cancel_timer();

      try {
        if (!fd.finishConnect()) {
          async_error(Posix.EIO);
          return;
        } else {
          state = TCP_STATE_CONNECTED;

        }
      } catch (IOException e) {
        // e.printStackTrace();
        async_error(IO.exception_to_posix_code(e));
        return;
      }

      if (active != ActiveType.PASSIVE) {
        sock_select(ERL_DRV_READ, SelectMode.SET);
      }

      async_ok();

    } else {
      sock_select(ERL_DRV_CONNECT, SelectMode.CLEAR);

    }

  }

  private boolean async_error(int eio) throws Pausable {

    return async_error(EAtom.intern(Posix.errno_id(eio).toLowerCase()));
  }

  @Override
  /* Handling of timeout in driver */
  protected void timeout() throws Pausable {

    if (log.isLoggable(Level.FINE)) {
      log.fine("timeout "+this);
    }
   
    if ((state & INET_F_MULTI_CLIENT) != 0) {
      fire_multi_timers();

    } else if ((state & TCP_STATE_CONNECTED) == TCP_STATE_CONNECTED) {

      if (busy_on_send) {
        assert is_busy();

        caller = busy_caller;
        state &= ~INET_F_BUSY;
        busy_on_send = false;
        set_busy_port(false);
        inet_reply_error(ERT.am_timeout);
        if (send_timeout_close) {
          erl_inet_close();
        }
      } else {
        /* assume recv timeout */
        assert active != ActiveType.PASSIVE;
        sock_select(ERL_DRV_READ | ERL_DRV_WRITE, SelectMode.CLEAR);
        i_remain = 0;
        async_error(ERT.am_timeout);
      }

    } else if ((state & TCP_STATE_CONNECTING) == TCP_STATE_CONNECTING) {

      erl_inet_close();
      async_error(ERT.am_timeout);

    } else if ((state & TCP_STATE_ACCEPTING) == TCP_STATE_ACCEPTING) {
      AsyncOp this_op = opt.peek();

      /* timer is set on accept */
      sock_select(ERL_DRV_ACCEPT, SelectMode.CLEAR);
      if (this_op != null) {
        driver_demonitor_process(this_op.monitor);
      }
      state = TCP_STATE_LISTEN;
      async_error(ERT.am_timeout);

    }
  }

  private boolean is_busy() {
    return (state & INET_F_BUSY) == INET_F_BUSY;
  }

  private void fire_multi_timers() throws Pausable {
    long next_timeout = 0;
    if (this.mtd_queue == null || this.mtd_queue.isEmpty()) {
      throw new InternalError();
    }
   
    do {
      MultiTimerData save = mtd_queue.remove();
      tcp_inet_multi_timeout(save.caller);
     
      if (mtd_queue.isEmpty())
        return;
     
      next_timeout = mtd_queue.peek().when;
     
    } while (next_timeout == 0);
    driver_set_timer(next_timeout);
  }

  private void tcp_inet_multi_timeout(EPID caller) throws Pausable {

    AsyncMultiOp multi;
    if ((multi = remove_multi_op(caller)) == null) {
      return;
    }
   
    driver_demonitor_process(multi.op.monitor);
   
    if (multi_first == null) {
      sock_select(ERL_DRV_ACCEPT, SelectMode.CLEAR);
      this.state = TCP_STATE_LISTEN;
    }
   
    send_async_error(multi.op.id, caller, am_timeout);
  }

  private boolean async_error(EAtom reason) throws Pausable {
    AsyncOp op = deq_async();
    if (op == null) {
      return false;
    }
    return send_async_error(op.id, op.caller, reason);
  }

  private void erl_inet_close() {

    free_subscribers(empty_out_q_subs);
    if (!prebound && ((state & INET_F_OPEN) == INET_F_OPEN)) {
      desc_close();
      state = INET_STATE_CLOSED;
    } else if (prebound && (fd != null)) {
      sock_select(ERL_DRV_READ | ERL_DRV_WRITE, SelectMode.CLEAR);
      event_mask = 0;
    }
  }

  private void desc_close() {

    sock_select(ERL_DRV_USE, SelectMode.CLEAR);
    fd = null;
    event_mask = 0;

    // when no more users of socket, the callback
    // stopSelect is called
  }

  @Override
  protected void stopSelect(SelectableChannel ch) throws Pausable {
    sock_close(ch);
  }
 
  @Override
  protected void stop(EObject reason) throws Pausable {
    super.stop(reason);
    tcp_close_check();
    InetSocket ff = fd;
    if (ff != null) {
      SelectableChannel ch = ff.channel();
      if (ch != null) {
      sock_close(ch);
      }
    }
  }

  private void sock_close(SelectableChannel ch) {
    try {
      ch.close();
    } catch (IOException e) {
      if (log.isLoggable(Level.WARNING))
        log.log(Level.WARNING, "failed to close socket", e);
    }
  }

  private void free_subscribers(List<EHandle> emptyOutQSubs) {
    emptyOutQSubs.clear();
  }

  @Override
  protected ByteBuffer control(EPID caller, int command,
      ByteBuffer buf) throws Pausable {

    switch (command) {
    case INET_REQ_OPEN:
      return inet_open(buf);

    case INET_REQ_PEER:
      return inet_peer(buf);

    case INET_REQ_SUBSCRIBE:
      return inet_subscribe(caller, buf);

    case INET_REQ_BIND:
      return inet_bind(buf);

    case INET_REQ_NAME:
      return inet_name(buf);

    case INET_REQ_LISTEN:
      return tcp_listen(buf);

    case TCP_REQ_LISTEN:
      return tcp_listen(buf);

    case INET_REQ_ACCEPT:
    case TCP_REQ_ACCEPT:
      return tcp_accept(caller, buf);

    case TCP_REQ_RECV:
      return tcp_recv(caller, buf);

    case INET_REQ_CONNECT:
      return inet_connect(caller, buf);

    case INET_REQ_GETOPTS:
      return inet_get_opts(buf);

    case INET_REQ_SETOPTS:
      switch (inet_set_opts(buf)) {
      case -1:
        return ctl_error(Posix.EINVAL);
      case 0:
        return ctl_reply(INET_REP_OK, new byte[0]);
      default: /* active/passive change!! */
        /*
         * Let's hope that the descriptor really is a tcp_descriptor
         * here.
         */
        tcp_deliver(0);
        return ctl_reply(INET_REP_OK, new byte[0]);
      }

    case INET_REQ_GETSTAT:
      return inet_getstat(buf);

    }

    throw new erjang.NotImplemented("tcp_inet control(cmd=" + command + ")");

  }

  private ByteBuffer inet_getstat(ByteBuffer buf) {
    int outsize = 1 + (buf.remaining() + 2) * 5;
    ByteBuffer dst = ByteBuffer.allocate(outsize);

    dst.put(INET_REP_OK);

    while (buf.hasRemaining()) {
      byte op = buf.get();
      dst.put(op);
      int val;

      switch (op) {
      case INET_STAT_RECV_CNT:
        val = this.recv_cnt;
        break;
      case INET_STAT_RECV_MAX:
        val = this.recv_max;
        break;
      case INET_STAT_RECV_AVG:
        val = (int) this.recv_avg;
        break;
      case INET_STAT_RECV_DVI:
        val = (int) Math.abs(this.recv_dvi);
        break;
      case INET_STAT_SEND_CNT:
        val = this.send_cnt;
        break;
      case INET_STAT_SEND_MAX:
        val = this.send_max;
        break;
      case INET_STAT_SEND_AVG:
        val = (int) this.send_avg;
        break;
      case INET_STAT_SEND_PND:
        val = driver_sizeq();
        break;
      case INET_STAT_RECV_OCT:
        dst.putLong(this.recv_oct);
        continue;
      case INET_STAT_SEND_OCT:
        dst.putLong(this.send_oct);
        continue;
      default:
        return ctl_error(Posix.EINVAL);
      }

      dst.putInt(val); /* write 32bit value */

    }

    return dst;

  }

  private ByteBuffer inet_peer(ByteBuffer buf) {

    if ((state & INET_F_ACTIVE) == 0) {
      log.fine("peer -> not connected");
     
      return ctl_error(Posix.ENOTCONN);
    }

    InetSocketAddress addr = fd.getRemoteAddress();
    InetAddress a = addr.getAddress();

    if (log.isLoggable(Level.FINE)) log.fine("peer -> "+addr);

    byte[] bytes = a.getAddress();
    int port = addr.getPort();

    byte[] data = new byte[1 + 2 + bytes.length];
    data[0] = (byte) sfamily.code;
    data[1] = (byte) ((port >> 8) & 0xff);
    data[2] = (byte) (port & 0xff);
    System.arraycopy(bytes, 0, data, 3, bytes.length);

    return ctl_reply(INET_REP_OK, data);

  }

  private ByteBuffer tcp_recv(EPID caller2, ByteBuffer buf) throws Pausable {
    /* INPUT: Timeout(4), Length(4) */

    if (!is_connected()) {
      if ((tcp_add_flags & TCP_ADDF_DELAYED_CLOSE_RECV) != 0) {
        tcp_add_flags &= ~(TCP_ADDF_DELAYED_CLOSE_RECV | TCP_ADDF_DELAYED_CLOSE_SEND);
        return ctl_reply(INET_REP_ERROR, "closed");
      } else {
        return ctl_error(Posix.ENOTCONN);
      }
    }

    if (active != ActiveType.PASSIVE || buf.remaining() != 8) {
      return ctl_error(Posix.EINVAL);
    }

    int timeout = buf.getInt();
    int n = buf.getInt();

    if (htype != PacketParseType.TCP_PB_RAW && n != 0)
      return ctl_error(Posix.EINVAL);

    if (n > 0x4000000)
      return ctl_error(Posix.ENOMEM);

    ByteBuffer tbuf = ByteBuffer.allocate(2);

    if (enq_async(caller2, tbuf, TCP_REQ_RECV) < 0)
      return ctl_error(Posix.EALREADY);

    if (log.isLoggable(Level.FINER))
      log.finer("enq " + caller2 + "::" + tbuf.getShort(0) +"; timeout="+timeout);

    int rep;
    if ((rep = tcp_recv(n)) == 0) {
      if (timeout == 0) {
        async_error(am_timeout);
      } else {
        if (timeout != INET_INFINITY) {
          driver_set_timer(timeout);
        }
        sock_select(ERL_DRV_READ, SelectMode.SET);
      }
    } else {
      if (log.isLoggable(Level.FINER))
        log.finer("tcp_recv[sync] => " + rep);
    }

    return ctl_reply(INET_REP_OK, tbuf.array());
  }

  private ByteBuffer inet_get_opts(ByteBuffer buf) {
    ByteArrayOutputStream barr = new ByteArrayOutputStream();
    DataOutputStream ptr = new DataOutputStream(barr);

    try {
      barr.write(INET_REP_OK);

      while (buf.hasRemaining()) {
        byte opt = buf.get();

        switch (opt) {
        case INET_LOPT_BUFFER:
          ptr.write(opt);
          ptr.writeInt(bufsz);
          continue;
        case INET_LOPT_HEADER:
          ptr.write(opt);
          ptr.writeInt(hsz);
          continue;
        case INET_LOPT_MODE:
          ptr.write(opt);
          ptr.writeInt(mode);
          continue;
        case INET_LOPT_DELIVER:
          ptr.write(opt);
          ptr.writeInt(deliver);
          continue;
        case INET_LOPT_ACTIVE:
          ptr.write(opt);
          ptr.writeInt(active == ActiveType.PASSIVE ? 0
              : active == ActiveType.ACTIVE ? 1 : 2);
          continue;
        case INET_LOPT_PACKET:
          ptr.write(opt);
          ptr.writeInt(htype.code);
          continue;
        case INET_LOPT_PACKET_SIZE:
          ptr.write(opt);
          ptr.writeInt(psize);
          continue;
        case INET_LOPT_EXITONCLOSE:
          ptr.write(opt);
          ptr.writeInt(exitf ? 1 : 0);
          continue;

        case INET_LOPT_BIT8:
          ptr.write(opt);
          ptr.writeInt(bit8f ? (bit8 ? 1 : 0) : INET_BIT8_OFF);
          continue;

        case INET_LOPT_TCP_HIWTRMRK:
          if (stype == ProtocolType.STREAM) {
            ptr.write(opt);
            ptr.writeInt(high);
          }
          continue;

        case INET_LOPT_TCP_LOWTRMRK:
          if (stype == ProtocolType.STREAM) {
            ptr.write(opt);
            ptr.writeInt(low);
          }
          continue;

        case INET_LOPT_TCP_SEND_TIMEOUT:
          if (stype == ProtocolType.STREAM) {
            ptr.write(opt);
            ptr.writeInt(send_timeout);
          }
          continue;

        case INET_LOPT_TCP_SEND_TIMEOUT_CLOSE:
          if (stype == ProtocolType.STREAM) {
            ptr.write(opt);
            ptr.writeInt(send_timeout_close ? 1 : 0);
          }
          continue;

        case INET_LOPT_TCP_DELAY_SEND:
          if (stype == ProtocolType.STREAM) {
            ptr.write(opt);
            ptr
                .writeInt(((tcp_add_flags & TCP_ADDF_DELAY_SEND) != 0) ? 1
                    : 0);
          }
          continue;

        case INET_LOPT_READ_PACKETS:
          if (stype == ProtocolType.STREAM) {
            ptr.write(opt);
            ptr.writeInt(read_packets);
          }
          continue;

        case INET_OPT_RAW:
          /* ignore (in Java7 we can do some...) */
          continue;

        case INET_OPT_PRIORITY:
          ptr.write(opt);
          ptr.writeInt(0);
          continue;

        case INET_OPT_TOS:
          ptr.write(opt);
          ptr.writeInt(0);
          continue;

        case INET_OPT_REUSEADDR:
          try {
            boolean val = fd.getReuseAddress();
            ptr.write(opt);
            ptr.writeInt(val ? 1 : 0);
            continue;
          } catch (IOException e) {
            // ignore //
            continue;
          }

        case INET_OPT_KEEPALIVE:
          try {
            boolean val = fd.getKeepAlive();
            ptr.write(opt);
            ptr.writeInt(val ? 1 : 0);
            continue;
          } catch (IOException e) {
            // ignore //
            continue;
          }

        case INET_OPT_SNDBUF:
          try {
            int val = fd.getSendBufferSize();
            ptr.write(opt);
            ptr.writeInt(val);
            continue;
          } catch (IOException e) {
            // ignore //
            continue;
          }
        case INET_OPT_RCVBUF:
          try {
            int val = fd.getReceiveBufferSize();
            ptr.write(opt);
            ptr.writeInt(val);
            continue;
          } catch (IOException e) {
            // ignore //
            continue;
          }
        case TCP_OPT_NODELAY:
          try {
            boolean val = fd.getNoDelay();
            ptr.write(opt);
            ptr.writeInt(val ? 1 : 0);
            continue;
          } catch (IOException e) {
            // ignore //
            continue;
          }
        default:
          throw new NotImplemented("getopt " + ((int) opt));
        }
      }

      ptr.close();
      byte[] b = barr.toByteArray();
      ByteBuffer res = ByteBuffer.wrap(b);
      res.position(b.length);
      return res;

    } catch (IOException e) {
      e.printStackTrace();
      return ctl_error(IO.exception_to_posix_code(e));
    }

  }

  private ByteBuffer tcp_accept(EPID caller, ByteBuffer buf) throws Pausable {

    if ((state != TCP_STATE_LISTEN && state != TCP_STATE_ACCEPTING && state != TCP_STATE_MULTI_ACCEPTING)
        || buf.remaining() != 4) {
      return ctl_error(Posix.EINVAL);
    }

    int timeout = buf.getInt();

    switch (state) {
    case TCP_STATE_ACCEPTING: {
      long time_left;
        MultiTimerData mtd = null, omtd = null;
        ERef monitor;

        if ((monitor = driver_monitor_process(caller)) == null) {
          return ctl_xerror(NOPROC);
        }
       
        AsyncOp op = deq_async_w_tmo();
        if (op.timeout != INET_INFINITY) {
          time_left = driver_read_timer();
          driver_cancel_timer();
         
          if (time_left <= 0) {
            time_left = 1;
          }
          omtd = add_multi_timer(op.caller, time_left);
        }
        enq_old_multi_op(op, omtd);
        if (timeout != INET_INFINITY) {
          mtd = add_multi_timer(caller, timeout);
        }
        short id = enq_multi_op(TCP_REQ_ACCEPT, caller, mtd, monitor);
        byte[] data = new byte[2];
        data[0] = (byte) (id >>> 8);
        data[1] = (byte) (id & 0xff);
        state = TCP_STATE_MULTI_ACCEPTING;
        return ctl_reply(INET_REP_OK, data);
    }
      // break;

    case TCP_STATE_MULTI_ACCEPTING:
    {
        MultiTimerData mtd = null, omtd = null;
        ERef monitor;

        if ((monitor = driver_monitor_process(caller)) == null) {
          return ctl_xerror(NOPROC);
        }
       
        if (timeout != INET_INFINITY) {
          mtd = add_multi_timer(caller, timeout);
        }
       
        short id = enq_multi_op(TCP_REQ_ACCEPT, caller, mtd, monitor);
        byte[] data = new byte[2];
        data[0] = (byte) (id >>> 8);
        data[1] = (byte) (id & 0xff);
        return ctl_reply(INET_REP_OK, data);

    }
      // break;

    case TCP_STATE_LISTEN:
      ByteBuffer reply = ByteBuffer.allocate(2);
      InetSocket sock;
      try {
        sock = fd.accept();
      } catch (IOException e) {
        return ctl_error(IO.exception_to_posix_code(e));
      }
      if (sock == null) {
        // async ...
        ERef monitor = driver_monitor_process(caller);
        if (monitor == null) {
          return ctl_xerror(NOPROC);
        }
        enq_async_w_tmo(caller, reply, TCP_REQ_ACCEPT, timeout, monitor);
        state = TCP_STATE_ACCEPTING;
        sock_select(ERL_DRV_ACCEPT, SelectMode.SET);
        if (timeout != INET_INFINITY) {
          driver_set_timer(timeout);
        }
      } else {
        // we got a connection

        try {
          sock.setNonBlocking();
        } catch (IOException e) {
          return ctl_error(IO.exception_to_posix_code(e));
        }
        TCPINet accept_desc = this.copy(caller, sock);
        accept_desc.state = TCP_STATE_CONNECTED;
        enq_async(caller, reply, TCP_REQ_ACCEPT);
        async_ok_port(caller, accept_desc.port());
      }

      return ctl_reply(INET_REP_OK, reply.array());

    default:
      throw new InternalError();
    }
  }
 
  private MultiTimerData add_multi_timer(EPID caller, long timeout) {

    MultiTimerData mtd = new MultiTimerData();
    mtd.when = System.currentTimeMillis() + timeout;
    mtd.caller = caller;

    if (this.mtd_queue == null) {
      this.mtd_queue = new PriorityQueue<MultiTimerData>();
    }
   
    this.mtd_queue.add(mtd);
   
    if (this.mtd_queue.peek() == mtd) {     
      // are we in front?
     
      if (this.mtd_queue.size() > 1) {
        // we're not alone, so there must already be a timer set
        driver_cancel_timer();
      }
     
      // then set our timer.
      driver_set_timer(timeout);     
    }
   
    return mtd;
  }


  private AsyncMultiOp remove_multi_op(EHandle caller) {
   
    AsyncMultiOp opp, slap = null;
   
    for (opp = multi_first;
       opp != null && opp.op.caller != caller;
       slap = opp, opp = opp.next
    ) {
      /* skip */
    }
   
    if (opp == null) {
      return null;
    }
   
    if (slap == null) {
      multi_first = opp.next;
    } else {
      slap.next = opp.next;
    }
   
    if (multi_last == opp) {
      multi_last = slap;
    }
   
    opp.next = null;
    return opp;
  }

  private short enq_multi_op(int req, EPID caller,
      MultiTimerData timeout, ERef monitor) {

    short id = (short) (aid.incrementAndGet() & 0xffff);
   
    AsyncOp op = new AsyncOp(id, caller, req, -1, monitor);
    enq_old_multi_op(op, timeout);
   
    return id;
  }
 
  AsyncMultiOp deq_multi_op() {
    AsyncMultiOp first = multi_first;
    if (first == null) {
      return null;
    }
   
    multi_first = first.next;
    if (multi_first == null) {
      multi_last = null;
    }
    first.next = null;
    return first;
  }

  private void enq_old_multi_op(AsyncOp op, MultiTimerData timeout) {
    AsyncMultiOp opp = new AsyncMultiOp();
    opp.op = op;
    opp.timeout = timeout;
   
    if (this.multi_first == null) {
      multi_first = opp;
    } else {
      multi_last.next = opp;
    }
    multi_last = opp;
  }

  private boolean async_ok_port(EPID caller, EInternalPort port2) throws Pausable {
    AsyncOp op = deq_async();
    if (op == null) {
      return false;
    }
    return send_async_ok_port(op.id, caller, port2);
  }

  private ByteBuffer inet_name(ByteBuffer buf) {

    if (!is_bound()) {
      return ctl_error(Posix.EINVAL);
    }

    InetSocketAddress addr = (InetSocketAddress) fd.getLocalSocketAddress();
    int port = addr.getPort();
    byte[] ip = addr.getAddress().getAddress();

    ByteBuffer out = ByteBuffer.allocate(4 + ip.length);

    out.put(INET_REP_OK);

    out.put(encode_proto_family(this.sfamily));
    out.putShort((short) port);
    out.put(ip);

    return out;
  }

  private ByteBuffer tcp_listen(ByteBuffer buf) {
    if (state == TCP_STATE_CLOSED || !is_open()) {
      return ctl_xerror(EXBADPORT);
    } else if (!is_bound()) {
      return ctl_xerror(EXBADSEQ);
    } else if (buf.remaining() != 2) {
      return ctl_error(Posix.EINVAL);
    }

    int backlog = buf.getShort() & 0xffff;

    try {
      this.fd.listen(backlog);
    } catch (IOException e) {
      return ctl_error(IO.exception_to_posix_code(e));
    }

    state = TCP_STATE_LISTEN;
    return ctl_reply(INET_REP_OK);
  }

  /*
   * set socket options:* return -1 on error* 0 if ok* 1 if ok force deliver
   * of queued data
   */

  private int inet_set_opts(ByteBuffer buf) throws Pausable {

    PacketParseType old_htype = this.htype;
    ActiveType old_active = this.active;
    boolean propagate = false;/*
                 * Set to true if failure to set this option
                 * should be propagated to erlang (not all
                 * errors can be propagated for BC reasons)
                 */

    int res = 0;

    while (buf.remaining() >= 5) {
      byte opt = buf.get();
      int ival = buf.getInt();

      switch (opt) {
      case INET_LOPT_HEADER:
        this.hsz = ival;
        continue;

      case INET_LOPT_MODE:
        this.mode = ival;
        continue;

      case INET_LOPT_DELIVER:
        this.deliver = ival;
        continue;

      case INET_LOPT_BUFFER:
        if (ival > INET_MAX_BUFFER)
          ival = INET_MAX_BUFFER;
        else if (ival < INET_MIN_BUFFER)
          ival = INET_MIN_BUFFER;
        this.bufsz = ival;
        continue;

      case INET_LOPT_ACTIVE:
        this.active = ActiveType.valueOf(ival);
        if ((stype == ProtocolType.STREAM)
            && (active != ActiveType.PASSIVE)
            && (state == INET_STATE_CLOSED)) {
          tcp_closed_message();
          if (this.exitf) {
            driver_exit(0);
            return 0; /* Give up on this socket, descriptor lost */
          } else {
            desc_close_read();
          }
        }
        res = 1;
        continue;

      case INET_LOPT_PACKET:
        this.htype = PacketParseType.valueOf(ival);
        continue;

      case INET_LOPT_PACKET_SIZE:
        this.psize = (int) ival;
        continue;

      case INET_LOPT_EXITONCLOSE:
        log.fine("setting exitf="+(ival != 0)+" on "+this);
        this.exitf = (ival == 0) ? false : true;
        continue;

      case INET_LOPT_BIT8:
        switch (ival) {
        case INET_BIT8_ON:
          this.bit8f = true;
          this.bit8 = false;
          break;
        case INET_BIT8_OFF:
          this.bit8f = false;
          this.bit8 = false;
          break;
        case INET_BIT8_CLEAR:
          this.bit8f = true;
          this.bit8 = false;
          break;
        case INET_BIT8_SET:
          this.bit8f = true;
          this.bit8 = true;
          break;
        }
        continue;

      case INET_LOPT_TCP_HIWTRMRK:
        if (this.stype == ProtocolType.STREAM) {
          if (ival < 0)
            ival = 0;
          else if (ival > INET_MAX_BUFFER * 2)
            ival = INET_MAX_BUFFER * 2;
          if (this.low > ival)
            this.low = ival;
          this.high = ival;
        }
        continue;

      case INET_LOPT_TCP_LOWTRMRK:
        if (this.stype == ProtocolType.STREAM) {
          if (ival < 0)
            ival = 0;
          else if (ival > INET_MAX_BUFFER)
            ival = INET_MAX_BUFFER;
          if (this.high < ival)
            this.high = ival;
          this.high = ival;
        }
        continue;

      case INET_LOPT_TCP_SEND_TIMEOUT:
        if (this.stype == ProtocolType.STREAM) {
          this.send_timeout = ival;
        }
        continue;

      case INET_LOPT_TCP_SEND_TIMEOUT_CLOSE:
        if (this.stype == ProtocolType.STREAM) {
          this.send_timeout_close = ival == 0 ? false : true;
        }
        continue;

      case INET_LOPT_TCP_DELAY_SEND:
        if (this.stype == ProtocolType.STREAM) {
          if (ival != 0)
            this.tcp_add_flags |= TCP_ADDF_DELAY_SEND;
          else
            this.tcp_add_flags &= ~TCP_ADDF_DELAY_SEND;
        }
        continue;

      case INET_LOPT_READ_PACKETS:
        if (this.stype == ProtocolType.STREAM) {
          if (ival <= 0)
            return -1;
          this.read_packets = ival;
        }
        continue;

      case INET_OPT_REUSEADDR:
        try {
          fd.setReuseAddress(ival != 0);
        } catch (IOException e) {
          if (propagate)
            return -1;
        }
        continue;

      case INET_OPT_KEEPALIVE:
        try {
          fd.setKeepAlive(ival != 0);
        } catch (IOException e) {
          if (propagate)
            return -1;
        }
        break;

      case INET_OPT_DONTROUTE:
        // TODO: WARN?
        continue;

      case INET_OPT_BROADCAST:
        try {
          fd.setBroadcast(ival != 0);
        } catch (IOException e) {
          if (propagate)
            return -1;
        }
        continue;

      case INET_OPT_OOBINLINE:
        try {
          fd.setOOBInline(ival != 0);
        } catch (IOException e) {
          if (propagate)
            return -1;
        }
        continue;

      case INET_OPT_SNDBUF:
        try {
          fd.setSendBufferSize(ival);
        } catch (IOException e) {
          if (propagate)
            return -1;
        }
        continue;

      case INET_OPT_RCVBUF:
        try {
          fd.setReceiveBufferSize(ival);
          if (ival > this.bufsz) {
            this.bufsz = ival;
          }
        } catch (IOException e) {
          if (propagate)
            return -1;
        }
        continue;

      case INET_OPT_LINGER:
        if (buf.remaining() < 4)
          return -1;
        int linger = buf.getInt();
        try {
          fd.setLinger(ival != 0, linger);
        } catch (IOException e) {
          if (propagate)
            return -1;
        }
        continue;

      case INET_OPT_PRIORITY:
        // TODO: WARN?
        continue;

      case INET_OPT_TOS:
        try {
          fd.setTrafficClass(ival);
        } catch (IOException e) {
          if (propagate)
            return -1;
        }
        continue;

      case TCP_OPT_NODELAY:
        try {
          fd.setTcpNoDelay(ival != 0);
        } catch (IOException e) {
          if (propagate)
            return -1;
        }
        continue;

        /** NO MULTICAST SUPPORT **/
        /**
         * case UDP_OPT_MULTICAST_TTL: try { fd.setTimeToLive(ival); }
         * catch (IOException e) { if (propagate) return -1; } continue;
         *
         * case UDP_OPT_MULTICAST_LOOP: try { fd.setLoopbackMode(ival ==
         * 0); } catch (SocketException e) { if (propagate) return -1; }
         * continue;
         *
         * case UDP_OPT_MULTICAST_IF: try { byte[] ip = int_to_ip(ival);
         * InetAddress addr = InetAddress.getByAddress(ip);
         * fd.setInterface(addr); } catch (IOException e) { if
         * (propagate) return -1; } continue;
         *
         * case UDP_OPT_ADD_MEMBERSHIP: try { // TODO: figure out which
         * port to use? // in the erlang call sequence, we have not yet
         * bound // the socket; so how can we know which port int port =
         * 0; if (fd.isBound()) { port = fd.getLocalPort(); } byte[] ip
         * = int_to_ip(ival); InetAddress mcast =
         * InetAddress.getByAddress(ip); SocketAddress mcastaddr = new
         * InetSocketAddress(mcast, port); InetAddress iaddr =
         * InetAddress.getByAddress(int_to_ip(buf .getInt()));
         * NetworkInterface netIf = NetworkInterface
         * .getByInetAddress(iaddr); fd.joinGroup(mcastaddr, netIf); }
         * catch (IOException e) { if (propagate) return -1; } continue;
         *
         * case UDP_OPT_DROP_MEMBERSHIP: try { // TODO: figure out which
         * port to use? // in the erlang call sequence, we have not yet
         * bound // the socket; so how can we know which port int port =
         * 0; if (fd.isBound()) { port = fd.getLocalPort(); } byte[] ip
         * = int_to_ip(ival); InetAddress mcast =
         * InetAddress.getByAddress(ip); SocketAddress mcastaddr = new
         * InetSocketAddress(mcast, port); InetAddress iaddr =
         * InetAddress.getByAddress(int_to_ip(buf .getInt()));
         * NetworkInterface netIf = NetworkInterface
         * .getByInetAddress(iaddr); fd.leaveGroup(mcastaddr, netIf); }
         * catch (IOException e) { if (propagate) return -1; } continue;
         **/
      default:
        return -1;

      }

    }

    if (((stype == ProtocolType.STREAM) && is_connected())
        || ((stype == ProtocolType.DGRAM) && is_open())) {

      if (active != old_active)
        sock_select(ERL_DRV_READ,
            active == ActiveType.PASSIVE ? SelectMode.CLEAR
                : SelectMode.SET);

      if ((stype == ProtocolType.STREAM) && active != ActiveType.PASSIVE) {
        if (old_active == ActiveType.PASSIVE || (htype != old_htype)) {
          /*
           * passive => active change OR header type change in active
           * mode
           */
          return 1;
        }
        return 0;
      }
    }
    return 0;
  }

  /** decode 0x12345678 into byte[]{ 0x12, 0x34, 0x56, 0x78 } */
  private byte[] int_to_ip(int ival) {
    return new byte[] { (byte) ((ival >>> 24) & 0xff),
        (byte) ((ival >>> 16) & 0xff), (byte) ((ival >>> 8) & 0xff),
        (byte) ((ival) & 0xff), };
  }

  private void desc_close_read() {
    sock_select(ERL_DRV_READ|ERL_DRV_USE, SelectMode.CLEAR);
  }

  private void tcp_closed_message() throws Pausable {
    if ((tcp_add_flags & TCP_ADDF_CLOSE_SENT) == 0) {
      tcp_add_flags |= TCP_ADDF_CLOSE_SENT;
      driver_output_term(new ETuple2(am_tcp_closed, port()));
    }
  }

  /**
   * deliver len bytes (from i_ptr_start...)
   *
   * @return number of packets delivered
   * @throws Pausable
   */
  private int tcp_deliver(int len) throws Pausable {

    if (log.isLoggable(Level.FINER))
      log.finer("tcp_deliver " + i_ptr_start + ":" + len);

    int count = 0;
    int n;
    int[] lenp = new int[] { len };

    if (len == 0) {

      if (i_buf == null || i_remain > 0) {
        return count;
      }

      n = tcp_remain(lenp);
      len = lenp[0];
      if (n != 0) {
        if (n < 0) {

          if (log.isLoggable(Level.WARNING))
            log.warning("tcp_deliver::packet_error " + n);

         
          /* packet error */
          return n;
        }
        if (len > 0) /* more data pending */
          i_remain = len;
        return count;
      }
    }

    while (len > 0) {
      if (log.isLoggable(Level.FINER))
        log.finer("deliver.2");
      int code = 0;

      inet_input_count(len);
     
      if (len * 4 >= i_buf.capacity() * 3) { /* >=75% */
        if (log.isLoggable(Level.FINER))
            log.finer("deliver.2.1");
        /* something after? */
        if (i_ptr_start + len == i_buf.position()) { /* no */
          code = tcp_reply_data(i_buf.array(), i_buf
              .arrayOffset()
              + i_ptr_start, len);
          if (code >= 0) tcp_clear_input();
        } else { /* move trail to beginning of a new buffer */
          ByteBuffer bin = ByteBuffer.allocate(i_buf.capacity());
          bin.put(i_buf.array(), i_buf.arrayOffset() + i_ptr_start
              + len, i_buf.position() - len - i_ptr_start);

          code = tcp_reply_data(i_buf.array(), i_buf
              .arrayOffset()
              + i_ptr_start, len);
          if (code >= 0) {
          i_buf = bin;
          i_ptr_start = 0;
          i_remain = 0;
          }
        }
      } else {
        if (log.isLoggable(Level.FINER))
          log.finer("deliver.2.2");
        // are we sending all we've got?
        boolean share_ok = i_ptr_start + len == i_buf.position();
        if (share_ok) {
          code = tcp_reply_data(i_buf.array(), i_buf.arrayOffset()
              + i_ptr_start, len);
          if (code >= 0)
          tcp_clear_input();
        } else {
          byte[] data = new byte[len];
          System.arraycopy(i_buf.array(), i_buf.arrayOffset()
              + i_ptr_start, data, 0, len);
          code = tcp_reply_data(data, 0, len);
          if (code >= 0) {
          i_ptr_start += len;
          i_remain = 0;
          }
        }
      }

      if (code < 0) {
        if (log.isLoggable(Level.WARNING))
          log.warning("tcp_deliver::error(code) " + code);

      return code;
      }
     
      count++;
      len = 0;

      if (active == ActiveType.PASSIVE) {
        driver_cancel_timer();
        sock_select(ERL_DRV_READ | 0, SelectMode.CLEAR);
        if (i_buf != null) {
          tcp_restart_input();
        }
      } else if (i_buf != null) {
        lenp[0] = len;
        n = tcp_remain(lenp);
        len = lenp[0];
        if (n != 0) {
          if (n < 0) /* packet error */
            return n;
          tcp_restart_input();
          if (len > 0)
            i_remain = len;
          len = 0;
        }
      }
    }
    return count;
  }

  /*
   * * active=TRUE:* (NOTE! distribution MUST use active=TRUE, deliver=PORT)*
   * deliver=PORT {S, {data, [H1,..Hsz | Data]}}* deliver=TERM {tcp, S,
   * [H1..Hsz | Data]}** active=FALSE:* {async, S, Ref, {ok,[H1,...Hsz |
   * Data]}}
   */

  private void inet_input_count(int len) {
    recv_cnt += 1;
    recv_oct += len;
   
    double avg = recv_avg;
    double dvi = recv_dvi;
   
    recv_avg = avg + (len - avg) / recv_cnt;
    if (len > recv_max)
      recv_max = len;
  }

  private void tcp_restart_input() {
    if (i_ptr_start != 0) {
      int n = i_buf.position() - i_ptr_start;
      System.arraycopy(i_buf.array(), i_buf.arrayOffset() + i_ptr_start,
          i_buf.array(), i_buf.arrayOffset(), n);
      i_ptr_start = 0;
      i_buf.position(n);
    }
  }

  private int tcp_reply_data(byte[] ib, int start, int len) throws Pausable {

    if (log.isLoggable(Level.FINER))
      log.finer("tcp_reply_data len="+len);
   
    ByteBuffer out = ByteBuffer.wrap(ib, start, len);
    Packet.get_body(htype, out);
    start = out.position();
    int bodylen = out.remaining();

    scanbit8(out);

    out = out.slice();
    out.position(out.limit());

    int code;
    if (deliver == INET_DELIVER_PORT) {
      code = inet_port_data(out);
    } else if ((code = Packet.parse(htype, ib, start, len, http_state,
        packet_callbacks, this)) == 0) {
      if (active == ActiveType.PASSIVE) {
        return inet_async_data(0, out);
      } else {
        code = tcp_message(out);
      }
    }

    if (code < 0)
      return code;

    if (active == ActiveType.ACTIVE_ONCE) {
      active = ActiveType.PASSIVE;
    }

    return code;
  }

  /** data is at 0 .. out.position()
   * @throws Pausable */
  private int tcp_message(ByteBuffer out) throws Pausable {
    int hsz = this.hsz;

    EObject msg;
    EObject data;

    if (mode == INET_MODE_LIST) {
      out.flip();
      data = EString.make(out);
    } else {
      out.position(hsz);
      ByteBuffer tail = out.slice();

      out.flip();
      ByteBuffer header = out;

      data = new EBinList(header, EBinary.make(tail));
    }

    msg = ETuple.make(am_tcp, port(), data);

    // System.out.println("sending "+msg);

    driver_output_term(msg);

    return 0;
  }

  /*
   * * passive mode reply:* {inet_async, S, Ref, {ok,[H1,...Hsz | Data]}}* NB:
   * this is for TCP only;* UDP and SCTP use inet_async_binary_data .
   */
  private int inet_async_data(int i, ByteBuffer out) {

    AsyncOp op = deq_async();
    if (op == null)
      return -1;

    if (log.isLoggable(Level.FINER))
      log.finer("deq " + op.caller + "::" + op.id);

    EObject data;
    if (mode == INET_MODE_LIST) {
      out.flip();
      data = EString.make(out);
    } else {
      // TODO: FIGURE OUT IF THIS IS RIGHT
      out.position(hsz);
      ByteBuffer tail = out.slice();
      out.flip();
      data = EBinary.make(tail);
    }

    ETuple res = ETuple.make(am_inet_async, port(), ERT.box(op.id),
        new ETuple2(ERT.am_ok, data));

    if (log.isLoggable(Level.FINER)) {
      log.finer("sending to " + op.caller + " ! " + res);
    }

    op.caller.sendb(res);

    return 0;
  }

  /** out has data from 0..out.position()
   * @throws Pausable */
  private int inet_port_data(ByteBuffer out) throws Pausable {

    int hsz = this.hsz;
    if (mode == INET_MODE_LIST || (hsz > out.remaining())) {
      driver_output(out);
      return 0;
    } else if (hsz > 0) {
      out.limit(out.position());
      out.position(hsz);
      ByteBuffer tail = out.slice();
      tail.position(tail.limit());
      out.limit(hsz);

      driver_output2(out, tail);
      return 0;
    } else {
      driver_output(out);
      return 0;
    }

  }

  private int tcp_reply_binary_data(byte[] ib, int start, int len) throws Pausable {

    ByteBuffer out = ByteBuffer.wrap(ib, start, len);
    Packet.get_body(htype, out);
    start = out.position();
    int bodylen = out.remaining();

    scanbit8(out);

    int code;
    if (deliver == INET_DELIVER_PORT) {
      code = inet_port_binary_data(out);
    } else if ((code = Packet.parse(htype, ib, start, len, http_state,
        packet_callbacks, this)) == 0) {
      if (active == ActiveType.PASSIVE) {
        return inet_async_binary_data(0, out);
      } else {
        code = tcp_binary_message(out);
      }
    }

    if (code < 0)
      return code;

    if (active == ActiveType.ACTIVE_ONCE) {
      active = ActiveType.PASSIVE;
    }

    return code;
  }

  private int tcp_binary_message(ByteBuffer out) {
    throw new erjang.NotImplemented();

  }

  private int inet_async_binary_data(int i, ByteBuffer out) {
    throw new erjang.NotImplemented();

  }

  private int inet_port_binary_data(ByteBuffer out) {
    throw new erjang.NotImplemented();

  }

  private void scanbit8(ByteBuffer out) {
    if (!bit8f || bit8)
      return;

    char c = 0;
    int rem = out.remaining();
    for (int i = 0; i < rem; i++) {
      c |= out.get(i);
    }
    bit8 = ((c & 0x80) != 0);

  }

  private void tcp_clear_input() {
    i_buf = null;
    i_ptr_start = 0;
    i_remain = 0;
  }

  private int tcp_expand_buffer(int len) {

    int used = i_ptr_start;
    int ulen = used + len;

    if (i_buf.limit() >= ulen) {
      /* packet will fit (within limit) */
      return 0;
    } else if (i_buf.capacity() >= ulen) {
      /* capacity is large enough to grow limit */
      i_buf.limit(ulen);
      return 0;
    }

    try {
      // allocate a new buffer of size "ulen"
      ByteBuffer bin = ByteBuffer.allocate(ulen);

      // copy the old buffer into this new buffer
      i_buf.flip();
      bin.put(i_buf);

      // and make the new buffer be the real thing
      i_buf = bin;
      return 0;

    } catch (OutOfMemoryError e) {
      return -1;
    }
  }

  /**
   * i_buf is a ByteBuffer, which has position() at the end of read input. The
   * variable i_ptr_start is an index into 0..position() which marks the
   * position of the first byte that has not been delivered to the client.
   *
   * i_ptr === i_buf.position() i_bufsz === i_buf.limit(). ibuf->orig_size ===
   * i_buf.capacity().
   * */

  private final int i_ptr() {
    return i_buf.position();
  }

  private final int i_bufsz() {
    return i_buf.limit();
  }

  private final void i_bufsz(int pos) {
    i_buf.limit(pos);
  }

  private final int i_buf_origsz() {
    return i_buf.capacity();
  }

  /** push data in front of the input buffer (unget) */
  private int tcp_push_buffer(byte[] data, int off, int len) {
    if (i_buf == null) {
      i_buf = ByteBuffer.allocate(len);
      i_buf.put(data, off, len);
      i_ptr_start = 0;
    } else {
      int sz_before = i_ptr_start;
      int sz_filled = i_buf.position() - i_ptr_start;

      if (len <= sz_before) {
        i_buf.position(sz_before - len);
        i_buf.put(data, off, len);
        i_ptr_start -= len;
      } else {
        ByteBuffer bin = ByteBuffer.allocate(len + i_buf.limit());
        bin.put(data, off, len);

        // semi-flip
        i_buf.position(i_ptr_start);
        i_buf.limit(sz_filled);
        bin.put(i_buf.array(), i_buf.arrayOffset() + i_ptr_start,
            sz_filled);
        i_buf = bin;
        i_ptr_start = 0;
      }
    }

    i_remain = 0;
    return 0;

  }

  private int tcp_remain(int[] lenp) {

    int nfill = i_buf.position();
    int nsz = i_buf.remaining();
    int n = nfill - i_ptr_start;

    int tlen;

    tlen = Packet.get_length(htype, i_buf.array(), i_buf.arrayOffset()
        + i_ptr_start, n, this.psize, i_bufsz(), http_state);

    if (tlen > 0) {
      if (tlen <= n) {
        // got a packet
        lenp[0] = tlen;
        return 0;
      } else {
        if (tcp_expand_buffer(tlen) < 0) {
          return -1;
        }
        lenp[0] = (tlen - n);
        return lenp[0];
      }
    } else if (tlen == 0) {
      lenp[0] = 0;
      if (nsz == 0) {
        if (nfill == n) {
          return -1;
        } else {
          return nfill - n;
        }
      } else {
        return nsz;
      }
    }

    return -1;
  }

  private ByteBuffer inet_open(ByteBuffer cmd) {
    if (cmd.remaining() == 2) {
      byte family = cmd.get();
      byte type = cmd.get();

      if ((family == INET_AF_INET || family == INET_AF_INET6) && type == INET_TYPE_STREAM) {
        return inet_ctl_open(decode_proto_family(family),
            ProtocolType.STREAM);
      }
    }

    return ctl_error(Posix.EINVAL);
  }

  private ByteBuffer inet_connect(EPID caller, ByteBuffer cmd) throws Pausable {
    /* INPUT: Timeout(4), Port(2), Address(N) */

    if (!is_open()) {
      return ctl_xerror(EXBADPORT);
    } else if (is_connected()) {
      return ctl_error(Posix.EISCONN);
    } else if (!is_bound()) {
      return ctl_xerror(EXBADSEQ);
    } else if (is_connecting()) {
      return ctl_error(Posix.EINVAL);
    }

    int timeout = cmd.getInt();

    remote = inet_set_address(sfamily, cmd);
    if (remote == null) {
      return ctl_error(Posix.EINVAL);
    }

    try {
      short aid;
      ByteBuffer reply = ByteBuffer.allocate(3);
      reply.put(INET_REP_OK);

      if (this.fd.connect(remote)) {
        // established

        state = TCP_STATE_CONNECTED;
        if (active != ActiveType.PASSIVE)
          sock_select(ERL_DRV_READ, SelectMode.SET);
        aid = enq_async(caller, reply, INET_REQ_CONNECT);
        async_ok();

      } else {
        // async

        state = TCP_STATE_CONNECTING;
        if (timeout != INET_INFINITY)
          driver_set_timer(timeout);

        sock_select(ERL_DRV_CONNECT, SelectMode.SET);

        aid = enq_async(caller, reply, INET_REQ_CONNECT);
      }

      return reply;
    } catch (IOException e) {
      e.printStackTrace();
      return ctl_error(IO.exception_to_posix_code(e));
    }
  }

  private ByteBuffer inet_bind(ByteBuffer cmd) {
    if (cmd.remaining() < 2)
      return ctl_error(Posix.EINVAL);

    if (state != INET_STATE_OPEN)
      return ctl_xerror(EXBADSEQ);
   
    ProtocolFamily family = ProtocolFamily.fromOrdinal(cmd.get());

    InetSocketAddress addr = inet_set_address(sfamily, cmd);
    if (addr == null)
      return ctl_error(Posix.EINVAL);

    try {
      fd.bind(addr);
    } catch (IOException e1) {
      e1.printStackTrace();
      return ctl_error(IO.exception_to_posix_code(e1));
    }

    state = INET_STATE_BOUND;

    int port = addr.getPort();
    if (port == 0) {
      port = fd.getLocalPort();
    }

    ByteBuffer reply = ByteBuffer.allocate(3);
    reply.order(ByteOrder.nativeOrder());
    reply.put(INET_REP_OK);
    reply.putShort((short) (port & 0xFFFF));

    return reply;
  }

  private boolean async_ok() throws Pausable {
    AsyncOp op = deq_async();
    if (op == null) {
      return false;
    }
    return send_async_ok(op.id, op.caller);
  }

  AsyncOp deq_async() {
    return deq_async_w_tmo();
  }

  private AsyncOp deq_async_w_tmo() {
    if (opt.size() == 0)
      return null;
    return opt.get();
  }

  private boolean inet_reply_error(EObject reason) throws Pausable {
    /*
     * send message:* {inet_reply, Port, Ref, {error,Reason}}
     */
    EHandle caller = this.caller;
    this.caller = null;
    ETuple msg = ETuple.make(am_inet_reply, port(), new ETuple2(
        ERT.am_error, reason));
    if (portlog.isLoggable(Level.FINER)) {
      portlog.finer("sending to " + caller + " ! " + msg);
    }
   
    driver_send_term(caller, msg);
    return true;
  }

  private boolean inet_reply_error(int reason) throws Pausable {
    return inet_reply_error(EAtom.intern(Posix.errno_id(reason)));
  }

  private boolean send_async_error(short id, EPID caller, EObject reason) throws Pausable {
    /*
     * send message:* {inet_async, Port, Ref, {error,Reason}}
     */

    ETuple msg = ETuple.make(am_inet_async, port(), ERT.box(id),
        new ETuple2(ERT.am_error, reason));
    if (portlog.isLoggable(Level.FINER)) {
      portlog.finer("sending to " + caller + " ! " + msg);
    }
    caller.send(port(), msg);
    return  true;
  }

  private boolean send_async_ok(int id, EPID caller) throws Pausable {
    ETuple msg = ETuple.make(am_inet_async, port(), ERT.box(id), ERT.am_ok);
    if (portlog.isLoggable(Level.FINER)) {
      portlog.finer("sending to " + caller + " ! " + msg);
    }
    caller.send(port(), msg);
    return true;
  }

  private boolean send_async_ok_port(int id, EPID caller, EPort port2) throws Pausable {
    ETuple msg = ETuple.make(am_inet_async, port(), ERT.box(id),
        new ETuple2(ERT.am_ok, port2));
    if (portlog.isLoggable(Level.FINER)) {
      log.finer("sending to " + caller + " ! " + msg);
    }
    caller.send(port(), msg);
    return true;
  }

  private short enq_async(EPID caller, ByteBuffer reply, int req) {
    return enq_async_w_tmo(caller, reply, req, INET_INFINITY, (ERef) null);
  }

  static AtomicInteger aid = new AtomicInteger(0);

  private short enq_async_w_tmo(EPID caller, ByteBuffer reply, int req,
      int timeout, ERef monitor) {

    short id = (short) (aid.incrementAndGet() & 0x7fff);
    AsyncOp op = new AsyncOp(id, caller, req, timeout, monitor);

    if (reply != null) {
      reply.putShort(op.id);
    }

    opt.put(op);

    return op.id;
  }

  private InetSocketAddress inet_set_address(ProtocolFamily sfamily2,
      ByteBuffer cmd) {

    int port = cmd.getShort() & 0xffff;

    byte[] bytes;
    if (sfamily2 == ProtocolFamily.INET) {
      bytes = new byte[4];
    } else if (sfamily2 == ProtocolFamily.INET6) {
      bytes = new byte[16];
    } else {
      return null;
    }
    InetAddress addr;
    try {
      if (cmd.remaining() == 0) {
        addr = InetAddress.getLocalHost();
      } else {

        cmd.get(bytes);

        addr = InetAddress.getByAddress(bytes);
      }
    } catch (UnknownHostException e) {
      return null;
    }

    InetSocketAddress res = new InetSocketAddress(addr, port);

    return res;
  }

  private boolean is_connecting() {
    return (state & INET_F_CON) == INET_F_CON;
  }

  private boolean is_bound() {
    return (state & INET_F_BOUND) == INET_F_BOUND;
  }

  private boolean is_connected() {
    return (state & INET_STATE_CONNECTED) == INET_STATE_CONNECTED;
  }

  private boolean is_open() {
    return (state & INET_F_OPEN) == INET_F_OPEN;
  }

  private ByteBuffer inet_subscribe(EPID caller, ByteBuffer cmd) {
    ByteBuffer reply = ByteBuffer.allocate(1 + cmd.remaining() * 5);
    reply.put(INET_REP_OK);

    while (cmd.hasRemaining()) {
      byte op = cmd.get();
      if (op == INET_SUBS_EMPTY_OUT_Q) {

        reply.put(INET_SUBS_EMPTY_OUT_Q);

        int val = driver_sizeq();
        if (val > 0) {
          save_subscriber(empty_out_q_subs, caller);
        }

        reply.putInt(val);

      } else {
        throw new ErlangError(EAtom.intern("einval"));
      }
    }

    return reply;
  }

  private boolean save_subscriber(List<EHandle> subs, EPID pid) {

    subs.add(pid);

    return true;
  }

  private ByteBuffer inet_ctl_open(ProtocolFamily domain, ProtocolType type) {
    if (state != INET_STATE_CLOSED) {
      return ctl_xerror(EXBADSEQ);
    }

    // since we don't know if we will be connecting or listening we
    // need to delay that decision; for now, create a (client) socket.

    try {
      this.fd = InetSocket.open(domain, type, protocol);
      fd.configureBlocking(false);

    } catch (IOException e) {
      int code = IO.exception_to_posix_code(e);
      return ctl_error(code);
    }

    this.state = INET_STATE_OPEN;
    this.stype = type;
    this.sfamily = domain;

    return ctl_reply(INET_REP_OK);
  }

  private ProtocolFamily decode_proto_family(byte domain) {
    switch (domain) {
    case INET_AF_INET:
      return ProtocolFamily.INET;
    case INET_AF_INET6:
      return ProtocolFamily.INET6;
    case INET_AF_ANY:
      return ProtocolFamily.ANY;
    case INET_AF_LOOPBACK:
      return ProtocolFamily.LOOPBACK;
    default:
      throw new NotImplemented();
    }
  }

  private byte encode_proto_family(ProtocolFamily domain) {
    switch (domain) {
    case INET:
      return INET_AF_INET;
    case INET6:
      return INET_AF_INET6;
    case ANY:
      return INET_AF_ANY;
    case LOOPBACK:
      return INET_AF_LOOPBACK;
    default:
      throw new NotImplemented();
    }
  }

  private ByteBuffer ctl_reply(int code) {
    ByteBuffer buf = ByteBuffer.allocate(1);
    buf.put((byte) code);
    return buf;
  }

  private ByteBuffer ctl_xerror(byte[] exbadseq2) {
    return ctl_reply(INET_REP_ERROR, exbadseq2);
  }

  private ByteBuffer ctl_reply(int code, byte[] data) {
    ByteBuffer buf = ByteBuffer.allocate(1 + data.length);
    buf.put((byte) code);
    buf.put(data);
    return buf;
  }

  private ByteBuffer ctl_reply(int code, String data) {
    ByteBuffer buf = ByteBuffer.allocate(1 + data.length());
    buf.put((byte) code);
    for (int i = 0; i < data.length(); i++) {
      buf.put((byte) data.charAt(i));
    }
    return buf;
  }

  private ByteBuffer ctl_error(int err) {
    String id = Posix.errno_id(err);
    return ctl_reply(INET_REP_ERROR, id);
  }

  void state(StringBuffer sb) {
    if ((state) == TCP_STATE_ACCEPTING) sb.append("Ac");
    if ((state) == TCP_STATE_BOUND) sb.append("Bo");
    if ((state) == TCP_STATE_CLOSED) sb.append("Clo");
    if ((state) == TCP_STATE_CONNECTED) sb.append("Con");
    if ((state) == TCP_STATE_CONNECTING) sb.append("Cog");
    if ((state) == TCP_STATE_LISTEN) sb.append("Li");
    if ((state) == TCP_STATE_MULTI_ACCEPTING) sb.append("Mu");
    if ((state) == TCP_STATE_OPEN) sb.append("Op");
  }
 
  @Override
  public String toString() {
    StringBuffer sb = new StringBuffer("TCPIP@");
    sb.append(System.identityHashCode(this));
    sb.append('[');

    sb.append("sock=").append(fd);

    sb.append("; state="); state(sb);
   
    sb.append("; active=").append(active);

    sb.append("; deliver=").append(deliver);

    sb.append("; select=").append(Integer.toBinaryString(event_mask));

    if (fd != null && fd.channel() != null) {
      SelectionKey sk = NIOSelector.interest(fd.channel());
      if (sk == null) {
        sb.append("; nointrest");
      } else if (!sk.isValid()) {

        sb.append("; cancelled");
      } else {

        try {
          sb.append("; ready="
              + Integer.toBinaryString(sk.readyOps()));
          sb.append("; interest="
              + Integer.toBinaryString(sk.interestOps()));
        } catch (CancelledKeyException e) {
          sb.append("; cancelled");
        }
      }
    }

    sb.append(']');

    return sb.toString();

  }

}
TOP

Related Classes of erjang.driver.tcp_inet.TCPINet$AsyncOp

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.