forked from I2P_Developers/i2p.i2p
NTCP2: Data phase payload generation and parsing
This commit is contained in:
@@ -445,6 +445,37 @@ public abstract class I2NPMessageImpl extends DataStructureImpl implements I2NPM
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the message with a short 9-byte header.
|
||||
* THe header consists of a one-byte type, 4-byte ID, and a 4-byte expiration in seconds only.
|
||||
* Used by NTCP2 only!
|
||||
* @since 0.9.35
|
||||
*/
|
||||
public static I2NPMessage fromRawByteArrayNTCP2(I2PAppContext ctx, byte buffer[], int offset,
|
||||
int len, I2NPMessageHandler handler) throws I2NPMessageException {
|
||||
int type = buffer[offset] & 0xff;
|
||||
offset++;
|
||||
I2NPMessage msg = createMessage(ctx, type);
|
||||
if (msg == null)
|
||||
throw new I2NPMessageException("Unknown message type: " + type);
|
||||
|
||||
try {
|
||||
long id = DataHelper.fromLong(buffer, offset, 4);
|
||||
offset += 4;
|
||||
// January 19 2038? No, unsigned, good until Feb. 7 2106
|
||||
// in seconds, round up so we don't lose time every hop
|
||||
long expiration = (DataHelper.fromLong(buffer, offset, 4) * 1000) + 500;
|
||||
offset += 4;
|
||||
int dataSize = len - 9;
|
||||
msg.readMessage(buffer, offset, dataSize, type, handler);
|
||||
msg.setUniqueId(id);
|
||||
msg.setMessageExpiration(expiration);
|
||||
return msg;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new I2NPMessageException("Corrupt message (negative expiration)", iae);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Yes, this is fairly ugly, but its the only place it ever happens.
|
||||
*
|
||||
|
||||
229
router/java/src/net/i2p/router/transport/ntcp/NTCP2Payload.java
Normal file
229
router/java/src/net/i2p/router/transport/ntcp/NTCP2Payload.java
Normal file
@@ -0,0 +1,229 @@
|
||||
package net.i2p.router.transport.ntcp;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.i2np.I2NPMessage;
|
||||
import net.i2p.data.i2np.I2NPMessageException;
|
||||
import net.i2p.data.i2np.I2NPMessageImpl;
|
||||
import net.i2p.data.router.RouterInfo;
|
||||
|
||||
/**
|
||||
*
|
||||
* NTCP2 Payload generation and parsing
|
||||
*
|
||||
* @since 0.9.35
|
||||
*/
|
||||
class NTCP2Payload {
|
||||
|
||||
private static final int BLOCK_DATETIME = 0;
|
||||
private static final int BLOCK_OPTIONS = 1;
|
||||
private static final int BLOCK_ROUTERINFO = 2;
|
||||
private static final int BLOCK_I2NP = 3;
|
||||
private static final int BLOCK_TERMINATION = 4;
|
||||
private static final int BLOCK_PADDING = 254;
|
||||
|
||||
public interface PayloadCallback {
|
||||
public void gotDateTime(long time);
|
||||
public void gotI2NP(I2NPMessage msg);
|
||||
public void gotOptions(byte[] options, boolean isHandshake);
|
||||
public void gotRI(RouterInfo ri, boolean isHandshake);
|
||||
public void gotTermination(int reason);
|
||||
public void gotUnknown(int type, int len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Incoming payload. Calls the callback for each received block.
|
||||
*
|
||||
* @return number of blocks processed
|
||||
* @throws IOException on major errors
|
||||
* @throws DataFormatException on parsing of individual blocks
|
||||
* @throws I2NPMessageException on parsing of I2NP block
|
||||
*/
|
||||
public static int processPayload(I2PAppContext ctx, PayloadCallback cb,
|
||||
byte[] payload, int length, boolean isHandshake)
|
||||
throws IOException, DataFormatException, I2NPMessageException {
|
||||
int blocks = 0;
|
||||
boolean gotRI = false;
|
||||
boolean gotPadding = false;
|
||||
int i = 0;
|
||||
while (i < length) {
|
||||
int type = payload[i++] & 0xff;
|
||||
if (gotPadding)
|
||||
throw new IOException("Illegal block after padding: " + type);
|
||||
int len = (int) DataHelper.fromLong(payload, i, 2);
|
||||
i += 2;
|
||||
if (i + len > length)
|
||||
throw new IOException("Block runs over frame");
|
||||
switch (type) {
|
||||
case BLOCK_DATETIME:
|
||||
if (isHandshake)
|
||||
throw new IOException("Illegal block in handshake: " + type);
|
||||
if (len != 4)
|
||||
throw new IOException("Bad length for DATETIME: " + len);
|
||||
long time = DataHelper.fromLong(payload, i, 4) * 1000;
|
||||
cb.gotDateTime(time);
|
||||
break;
|
||||
|
||||
case BLOCK_OPTIONS:
|
||||
byte[] options = null;
|
||||
cb.gotOptions(options, isHandshake);
|
||||
break;
|
||||
|
||||
case BLOCK_ROUTERINFO:
|
||||
int flag = payload[i] & 0xff;
|
||||
RouterInfo alice = new RouterInfo();
|
||||
// TODO limit
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(payload, i + 1, len - 1);
|
||||
alice.readBytes(bais, true);
|
||||
// TODO validate Alice static key, pass back somehow
|
||||
cb.gotRI(alice, isHandshake);
|
||||
gotRI = true;
|
||||
break;
|
||||
|
||||
case BLOCK_I2NP:
|
||||
if (isHandshake)
|
||||
throw new IOException("Illegal block in handshake: " + type);
|
||||
I2NPMessage msg = I2NPMessageImpl.fromRawByteArrayNTCP2(ctx, payload, i, len, null);
|
||||
cb.gotI2NP(msg);
|
||||
break;
|
||||
|
||||
case BLOCK_TERMINATION:
|
||||
if (isHandshake)
|
||||
throw new IOException("Illegal block in handshake: " + type);
|
||||
if (len != 9)
|
||||
throw new IOException("Bad length for TERMINATION: " + len);
|
||||
int rsn = payload[i] & 0xff;
|
||||
cb.gotTermination(rsn);
|
||||
break;
|
||||
|
||||
case BLOCK_PADDING:
|
||||
gotPadding = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (isHandshake)
|
||||
throw new IOException("Illegal block in handshake: " + type);
|
||||
cb.gotUnknown(type, len);
|
||||
break;
|
||||
|
||||
}
|
||||
i += len;
|
||||
blocks++;
|
||||
}
|
||||
if (isHandshake && !gotRI)
|
||||
throw new IOException("No RI block in handshake");
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param payload writes to it starting at off
|
||||
* @return the new offset
|
||||
*/
|
||||
public int writePayload(byte[] payload, int off, List<Block> blocks) {
|
||||
for (Block block : blocks) {
|
||||
off = block.write(payload, off);
|
||||
}
|
||||
return off;
|
||||
}
|
||||
|
||||
public static abstract class Block {
|
||||
private final int type;
|
||||
|
||||
public Block(int ttype) {
|
||||
type = ttype;
|
||||
}
|
||||
|
||||
public int write(byte[] tgt, int off) {
|
||||
tgt[off++] = (byte) type;
|
||||
DataHelper.toLong(tgt, off, 2, getDataLength());
|
||||
return writeData(tgt, off + 2);
|
||||
}
|
||||
|
||||
public abstract int getDataLength();
|
||||
|
||||
public abstract int writeData(byte[] tgt, int off);
|
||||
}
|
||||
|
||||
public static class RIBlock extends Block {
|
||||
private final byte[] data;
|
||||
|
||||
public RIBlock(RouterInfo ri) {
|
||||
super(BLOCK_ROUTERINFO);
|
||||
data = ri.toByteArray();
|
||||
}
|
||||
|
||||
public int getDataLength() {
|
||||
return 1 + data.length;
|
||||
}
|
||||
|
||||
public int writeData(byte[] tgt, int off) {
|
||||
tgt[off++] = 0; // flag
|
||||
System.arraycopy(data, 0, tgt, off, data.length);
|
||||
return off + data.length;
|
||||
}
|
||||
}
|
||||
|
||||
public static class I2NPBlock extends Block {
|
||||
private final byte[] data;
|
||||
|
||||
public I2NPBlock(I2NPMessage msg) {
|
||||
super(BLOCK_I2NP);
|
||||
data = msg.toByteArray();
|
||||
}
|
||||
|
||||
public int getDataLength() {
|
||||
return data.length - 7;
|
||||
}
|
||||
|
||||
public int writeData(byte[] tgt, int off) {
|
||||
// type, ID, first 4 bytes of exp
|
||||
System.arraycopy(data, 0, tgt, off, 9);
|
||||
// skip last 4 bytes of exp, sz, checksum
|
||||
System.arraycopy(data, 16, tgt, off + 9, data.length - 16);
|
||||
return off + data.length - 7;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PaddingBlock extends Block {
|
||||
private final byte[] data;
|
||||
|
||||
public PaddingBlock(I2PAppContext ctx, int size) {
|
||||
super(BLOCK_PADDING);
|
||||
data = new byte[size];
|
||||
if (size > 0)
|
||||
ctx.random().nextBytes(data);
|
||||
}
|
||||
|
||||
public int getDataLength() {
|
||||
return data.length;
|
||||
}
|
||||
|
||||
public int writeData(byte[] tgt, int off) {
|
||||
System.arraycopy(tgt, off, data, 0, data.length);
|
||||
return off + data.length;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DateTimeBlock extends Block {
|
||||
private final long now;
|
||||
|
||||
public DateTimeBlock(I2PAppContext ctx) {
|
||||
super(BLOCK_DATETIME);
|
||||
now = ctx.clock().now();
|
||||
}
|
||||
|
||||
public int getDataLength() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
public int writeData(byte[] tgt, int off) {
|
||||
DataHelper.toLong(tgt, off, 4, now / 1000);
|
||||
return off + 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user