/**
* 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 evt) throws 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();
}
}