package org.codesharp.traffic.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import org.codesharp.traffic.Asserter;
import org.codesharp.traffic.Commands;
import org.codesharp.traffic.MessageHandle;
import org.codesharp.traffic.Status;
/***
*
* https://github.com/wsky/top-traffic/issues/2#issuecomment-66725466
*
* Internal message protocol
*
* +-----+--------+-----+-----+------------+--------------+----------+-------------+----------+------+
* | CMD | Status | Len | Dst | Header-Len | Headers | Path-Len | Path | Body-Len | Body |
* | | | | | |--------------| |-------------| | |
* | | | | | | Type | Value | | Flag | Path | | |
* +-----+--------+-----+-----+------------+------+-------+----------+------+------+----------+------+
* | 1 | 1 | 4 | 8 | 1 | 1 | N | 1 | 1 | 8 | 4 | N |
* +-----+--------+-----+-----+------------+------+-------+----------+------+------+----------+------+
* | 0/1 | byte | int | long| byte | 1/2/ | | byte | 0/1 | long | int | |
* +-----+--------+-----+-----+------------+------+-------+----------+------+------+----------+------+
*/
public class MessageHandleImpl implements MessageHandle {
public final static int LEN = 1 + 1;
public final static int DST = LEN + 4;
public final static int HEADER_LEN = DST + 8;
public final static int PATH_SIZE = 1 + 8;
public final static byte PATH_UNSET = 0;
public final static byte PATH_SET = 1;
protected ByteBufAllocator allocator;
public MessageHandleImpl(ByteBufAllocator allocator) {
this.allocator = allocator;
}
public String toString(Object msg) {
ByteBuf buf = (ByteBuf) msg;
ByteBuf body = this.getMessageBody(msg);
byte[] bytes = new byte[body.readableBytes()];
body.readBytes(bytes);
return String.format(
"cmd=%s, status=%s, len=%s, dst=%s, header-len=%s, body=%s",
this.getCommand(buf),
this.getStatus(buf),
this.getLen(buf),
this.getDestination(buf),
this.getHeaderLen(buf),
new String(bytes));
}
public ByteBuf newMessage(byte cmd, byte status, long dst, int pathCount, byte[] body, Object... headers) {
ByteBuf buf = this.allocator.buffer();
buf.resetWriterIndex();
buf.writeByte(cmd);
buf.writeByte(status);
int len = 0;
buf.writeInt(len);
buf.writeLong(dst);
len += 8;
byte headerLen = 0;
buf.writeByte(headerLen);
len += 1;
for (Object o : headers) {
if (o instanceof Byte) {
buf.writeByte(1).writeByte((Byte) o);
headerLen += (1 + 1);
} else if (o instanceof Short) {
buf.writeByte(2).writeShort((Short) o);
headerLen += (1 + 2);
} else if (o instanceof Integer) {
buf.writeByte(3).writeInt((Integer) o);
headerLen += (1 + 4);
} else if (o instanceof Long) {
buf.writeByte(4).writeLong((Long) o);
headerLen += (1 + 8);
} else
Asserter.throwUnsupportedHeader(o != null ? o.getClass() : o);
}
buf.setByte(HEADER_LEN, headerLen);
len += headerLen;
byte pathLen = (byte) (pathCount * PATH_SIZE);
buf.writeByte(pathLen);
len += (1 + pathLen);
for (int i = 0; i < pathCount; i++) {
buf.writeByte(PATH_UNSET);
buf.writeLong(0);
}
buf.writeInt(body.length);
buf.writeBytes(body);
len += (4 + body.length);
this.setLen(buf, len);
return buf;
}
public byte getCommand(Object msg) {
return ((ByteBuf) msg).getByte(0);
}
public byte getStatus(Object msg) {
return ((ByteBuf) msg).getByte(1);
}
public Object getDestination(Object msg) {
return ((ByteBuf) msg).getLong(DST);
}
public Object getHeader(Object msg, int index) {
ByteBuf buf = (ByteBuf) msg;
int headerLen = this.getHeaderLen(buf);
int read = HEADER_LEN + 1;
int end = HEADER_LEN + headerLen;
int i = 0;
while (read < end) {
byte t = buf.getByte(read++);
Object v = null;
if (t == 1) {
v = buf.getByte(read);
read += 1;
} else if (t == 2) {
v = buf.getShort(read);
read += 2;
} else if (t == 3) {
v = buf.getInt(read);
read += 4;
} else if (t == 4) {
v = buf.getLong(read);
read += 8;
} else
Asserter.throwUnsupportedHeader(t);
if (i++ == index)
return v;
}
throw Asserter.overflow("header", index);
}
public Object getNext(Object msg) {
ByteBuf buf = (ByteBuf) msg;
int headerLen = this.getHeaderLen(buf);
int pathLen = buf.getByte(HEADER_LEN + 1 + headerLen);
int pathBegin = HEADER_LEN + 1 + headerLen + 1;
int pathCount = pathLen / PATH_SIZE;
while (pathCount-- > 0) {
int pathFlag = pathBegin + pathCount * PATH_SIZE;
if (buf.getByte(pathFlag) == PATH_UNSET)
continue;
buf.setByte(pathFlag, PATH_UNSET);
return buf.getLong(pathFlag + 1);
}
return null;
}
public Object append(Object msg, Object from) {
ByteBuf buf = (ByteBuf) msg;
int headerLen = this.getHeaderLen(buf);
int pathLen = buf.getByte(HEADER_LEN + 1 + headerLen);
int pathBegin = HEADER_LEN + 1 + headerLen + 1;
int pathCount = pathLen / PATH_SIZE;
for (int i = 0; i < pathCount; i++) {
int pathFlag = pathBegin + i * PATH_SIZE;
if (buf.getByte(pathFlag) == PATH_SET)
continue;
buf.setByte(pathFlag, PATH_SET);
buf.setLong(pathFlag + 1, (Long) from);
return msg;
}
throw Asserter.overflow("path", pathCount);
}
public ByteBuf getMessageBody(Object msg) {
ByteBuf buf = (ByteBuf) msg;
int headerLen = this.getHeaderLen(buf);
int pathLen = buf.getByte(HEADER_LEN + 1 + headerLen);
int bodyLen = buf.getInt(HEADER_LEN + 1 + headerLen + 1 + pathLen);
int bodyBegin = HEADER_LEN + 1 + headerLen + 1 + pathLen + 4;
buf.setIndex(bodyBegin, bodyBegin + bodyLen);
return buf;
}
public Object unknownDestination(Object msg) {
ByteBuf buf = (ByteBuf) msg;
this.setCommand(buf, Commands.ACK);
this.setStatus(buf, Status.UNKNOWN_DESTINATION);
return msg;
}
protected void setCommand(ByteBuf buf, byte cmd) {
buf.setByte(0, cmd);
}
protected void setStatus(ByteBuf buf, byte status) {
buf.setByte(1, status);
}
protected void setBody(ByteBuf buf, byte[] body) {
int len = this.getLen(buf);
int headerLen = this.getHeaderLen(buf);
int pathLen = buf.getByte(HEADER_LEN + 1 + headerLen);
int bodyLen = buf.getInt(HEADER_LEN + 1 + headerLen + 1 + pathLen);
int bodyBegin = HEADER_LEN + 1 + headerLen + 1 + pathLen + 4;
// reset len
this.setLen(buf, len + (body.length - bodyLen));
// reset body-len
buf.setInt(HEADER_LEN + 1 + headerLen + 1 + pathLen, body.length);
buf.setIndex(0, bodyBegin);
buf.writeBytes(body);
}
protected int getLen(ByteBuf buf) {
return buf.getInt(LEN);
}
protected void setLen(ByteBuf buf, int len) {
buf.setInt(LEN, len);
}
protected int getHeaderLen(ByteBuf buf) {
return buf.getByte(HEADER_LEN);
}
}