Compare commits

..

1 Commits

Author SHA1 Message Date
zzz
f932319781 SAM: Add support for Datagram 2/3 (proposal 163) 2025-08-18 14:24:01 -04:00
6 changed files with 202 additions and 45 deletions

View File

@@ -23,6 +23,7 @@ import net.i2p.client.streaming.I2PSocket;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.I2PAppThread; import net.i2p.util.I2PAppThread;
import net.i2p.util.Log; import net.i2p.util.Log;
@@ -123,8 +124,11 @@ class PrimarySession extends SAMv3StreamSession implements SAMDatagramReceiver,
if (spr != null) { if (spr != null) {
try { try {
listenProtocol = Integer.parseInt(spr); listenProtocol = Integer.parseInt(spr);
// RAW can't listen on streaming protocol // RAW can't listen on streaming or DG protocols
if (listenProtocol < 0 || listenProtocol > 255 || if (listenProtocol < 0 || listenProtocol > 255 ||
listenProtocol == I2PSession.PROTO_DATAGRAM ||
listenProtocol == I2PSession.PROTO_DATAGRAM2 ||
listenProtocol == I2PSession.PROTO_DATAGRAM3 ||
listenProtocol == I2PSession.PROTO_STREAMING) listenProtocol == I2PSession.PROTO_STREAMING)
return "Bad RAW LISTEN_PPROTOCOL " + spr; return "Bad RAW LISTEN_PPROTOCOL " + spr;
} catch (NumberFormatException nfe) { } catch (NumberFormatException nfe) {
@@ -134,11 +138,23 @@ class PrimarySession extends SAMv3StreamSession implements SAMDatagramReceiver,
SAMv3RawSession ssess = new SAMv3RawSession(nick, props, handler, isess, listenProtocol, listenPort, dgs); SAMv3RawSession ssess = new SAMv3RawSession(nick, props, handler, isess, listenProtocol, listenPort, dgs);
subhandler.setSession(ssess); subhandler.setSession(ssess);
sess = ssess; sess = ssess;
} else if (style.equals("DATAGRAM")) { } else if (style.equals("DATAGRAM") ||
style.equals("DATAGRAM2") ||
style.equals("DATAGRAM3")) {
if (!props.containsKey("PORT")) if (!props.containsKey("PORT"))
return "DATAGRAM subsession must specify PORT"; return "DATAGRAM subsession must specify PORT";
int v;
if (style.equals("DATAGRAM")) {
listenProtocol = I2PSession.PROTO_DATAGRAM; listenProtocol = I2PSession.PROTO_DATAGRAM;
SAMv3DatagramSession ssess = new SAMv3DatagramSession(nick, props, handler, isess, listenPort, dgs); v = 1;
} else if (style.equals("DATAGRAM2")) {
listenProtocol = I2PSession.PROTO_DATAGRAM2;
v = 2;
} else {
listenProtocol = I2PSession.PROTO_DATAGRAM3;
v = 3;
}
SAMv3DatagramSession ssess = new SAMv3DatagramSession(nick, props, handler, isess, listenPort, dgs, v);
subhandler.setSession(ssess); subhandler.setSession(ssess);
sess = ssess; sess = ssess;
} else if (style.equals("STREAM")) { } else if (style.equals("STREAM")) {
@@ -212,6 +228,15 @@ class PrimarySession extends SAMv3StreamSession implements SAMDatagramReceiver,
throw new IOException("primary session"); throw new IOException("primary session");
} }
/**
* @throws IOException always
* @since 0.9.68
*/
public void receiveDatagramBytes(Hash sender, byte[] data, int proto,
int fromPort, int toPort) throws IOException {
throw new IOException("primary session");
}
/** /**
* Does nothing. * Does nothing.
*/ */

View File

@@ -11,6 +11,7 @@ package net.i2p.sam;
import java.io.IOException; import java.io.IOException;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.Hash;
/** /**
* Interface for sending raw data to a SAM client * Interface for sending raw data to a SAM client
@@ -29,6 +30,20 @@ interface SAMDatagramReceiver {
*/ */
public void receiveDatagramBytes(Destination sender, byte data[], int proto, int fromPort, int toPort) throws IOException; public void receiveDatagramBytes(Destination sender, byte data[], int proto, int fromPort, int toPort) throws IOException;
/**
* Send a byte array to a SAM client.
* Only for Datagram3, where the sender Destination is not available, only the hash.
*
* @param sender Hash
* @param data Byte array to be received
* @param proto I2CP protocol, almost certainly 20 (DATAGRAM3)
* @param fromPort I2CP from port
* @param toPort I2CP to port
* @since 0.9.68
* @throws IOException
*/
public void receiveDatagramBytes(Hash sender, byte data[], int proto, int fromPort, int toPort) throws IOException;
/** /**
* Stop receiving data. * Stop receiving data.
* *

View File

@@ -12,17 +12,22 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Properties; import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.client.I2PSession; import net.i2p.client.I2PSession;
import net.i2p.client.I2PSessionException; import net.i2p.client.I2PSessionException;
import net.i2p.client.datagram.Datagram2;
import net.i2p.client.datagram.Datagram3;
import net.i2p.client.datagram.I2PDatagramDissector; import net.i2p.client.datagram.I2PDatagramDissector;
import net.i2p.client.datagram.I2PDatagramMaker; import net.i2p.client.datagram.I2PDatagramMaker;
import net.i2p.client.datagram.I2PInvalidDatagramException; import net.i2p.client.datagram.I2PInvalidDatagramException;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.Log; import net.i2p.util.Log;
/** /**
* SAM DATAGRAM session class. * SAM DATAGRAM session class.
* Supports DG2/3 as of 0.9.68
* *
* @author human * @author human
*/ */
@@ -33,28 +38,40 @@ class SAMDatagramSession extends SAMMessageSession {
// FIXME make final after fixing SAMv3DatagramSession override // FIXME make final after fixing SAMv3DatagramSession override
protected SAMDatagramReceiver recv; protected SAMDatagramReceiver recv;
private final I2PDatagramMaker dgramMaker; private final I2PDatagramMaker dgramMaker;
private final I2PDatagramDissector dgramDissector = new I2PDatagramDissector(); private final I2PDatagramDissector dgramDissector;
private final int version;
/** /**
* Create a new SAM DATAGRAM session. * Create a new SAM DATAGRAM session.
* v1/v2 (DG1 only) or v3 (DG 1/2/3)
* *
* @param dest Base64-encoded destination (private key) * @param dest Base64-encoded destination (private key)
* @param props Properties to setup the I2P session * @param props Properties to setup the I2P session
* @param recv Object that will receive incoming data * @param recv Object that will receive incoming data
* @param v datagram version 1/2/3
* @throws IOException * @throws IOException
* @throws DataFormatException * @throws DataFormatException
* @throws I2PSessionException * @throws I2PSessionException
*/ */
protected SAMDatagramSession(String dest, Properties props, protected SAMDatagramSession(String dest, Properties props,
SAMDatagramReceiver recv) throws IOException, SAMDatagramReceiver recv, int v) throws IOException,
DataFormatException, I2PSessionException { DataFormatException, I2PSessionException {
super(dest, props); super(dest, props);
this.recv = recv; if (v == 1) {
dgramMaker = new I2PDatagramMaker(getI2PSession()); dgramMaker = new I2PDatagramMaker(getI2PSession());
dgramDissector = new I2PDatagramDissector();
} else if (v == 2 || v == 3) {
dgramMaker = null;
dgramDissector = null;
} else {
throw new IllegalArgumentException("Bad version: " + v);
}
version = v;
} }
/** /**
* Create a new SAM DATAGRAM session. * Create a new SAM DATAGRAM session.
* v1/v2 only, DG1 only
* *
* Caller MUST call start(). * Caller MUST call start().
* *
@@ -64,27 +81,41 @@ class SAMDatagramSession extends SAMMessageSession {
* @throws IOException * @throws IOException
* @throws DataFormatException * @throws DataFormatException
* @throws I2PSessionException * @throws I2PSessionException
* @deprecated unused
*/ */
@Deprecated
public SAMDatagramSession(InputStream destStream, Properties props, public SAMDatagramSession(InputStream destStream, Properties props,
SAMDatagramReceiver recv) throws IOException, SAMDatagramReceiver recv) throws IOException,
DataFormatException, I2PSessionException { DataFormatException, I2PSessionException {
super(destStream, props); super(destStream, props);
this.recv = recv; this.recv = recv;
dgramMaker = new I2PDatagramMaker(getI2PSession()); dgramMaker = new I2PDatagramMaker(getI2PSession());
dgramDissector = new I2PDatagramDissector();
version = 1;
} }
/** /**
* Create a new SAM DATAGRAM session on an existing I2P session. * Create a new SAM DATAGRAM session on an existing I2P session.
* v3 only, DG 1/2/3
* *
* @param props unused for now * @param v datagram version 1/2/3
* @since 0.9.25 * @since 0.9.25
*/ */
protected SAMDatagramSession(I2PSession sess, Properties props, int listenPort, protected SAMDatagramSession(I2PSession sess, Properties props, int listenPort,
SAMDatagramReceiver recv) throws IOException, SAMDatagramReceiver recv, int v) throws IOException,
DataFormatException, I2PSessionException { DataFormatException, I2PSessionException {
super(sess, I2PSession.PROTO_DATAGRAM, listenPort); super(sess, I2PSession.PROTO_DATAGRAM, listenPort);
this.recv = recv; this.recv = recv;
if (v == 1) {
dgramMaker = new I2PDatagramMaker(getI2PSession()); dgramMaker = new I2PDatagramMaker(getI2PSession());
dgramDissector = new I2PDatagramDissector();
} else if (v == 2 || v == 3) {
dgramMaker = null;
dgramDissector = null;
} else {
throw new IllegalArgumentException("Bad version: " + v);
}
version = v;
} }
/** /**
@@ -92,7 +123,7 @@ class SAMDatagramSession extends SAMMessageSession {
* *
* @param dest Destination * @param dest Destination
* @param data Bytes to be sent * @param data Bytes to be sent
* @param proto ignored, will always use PROTO_DATAGRAM (17) * @param proto ignored, will always use PROTO_DATAGRAM (17), PROTO_DATAGRAM2 (19), or PROTO_DATAGRAM3 (20)
* *
* @return True if the data was sent, false otherwise * @return True if the data was sent, false otherwise
* @throws DataFormatException on unknown / bad dest * @throws DataFormatException on unknown / bad dest
@@ -103,15 +134,26 @@ class SAMDatagramSession extends SAMMessageSession {
if (data.length > DGRAM_SIZE_MAX) if (data.length > DGRAM_SIZE_MAX)
throw new DataFormatException("Datagram size exceeded (" + data.length + ")"); throw new DataFormatException("Datagram size exceeded (" + data.length + ")");
byte[] dgram; byte[] dgram;
if (version == 1) {
synchronized (dgramMaker) { synchronized (dgramMaker) {
dgram = dgramMaker.makeI2PDatagram(data); dgram = dgramMaker.makeI2PDatagram(data);
} }
return sendBytesThroughMessageSession(dest, dgram, I2PSession.PROTO_DATAGRAM, fromPort, toPort); proto = I2PSession.PROTO_DATAGRAM;
} else if (version == 2) {
Hash h = new Destination(dest).calculateHash();
dgram = Datagram2.make(I2PAppContext.getGlobalContext(), getI2PSession(), data, h);
proto = I2PSession.PROTO_DATAGRAM2;
} else {
dgram = Datagram3.make(I2PAppContext.getGlobalContext(), getI2PSession(), data);
proto = I2PSession.PROTO_DATAGRAM3;
}
return sendBytesThroughMessageSession(dest, dgram, proto, fromPort, toPort);
} }
/** /**
* Send bytes through a SAM DATAGRAM session. * Send bytes through a SAM DATAGRAM session.
* *
* @param proto ignored, will always use PROTO_DATAGRAM (17), PROTO_DATAGRAM2 (19), or PROTO_DATAGRAM3 (20)
* @since 0.9.25 * @since 0.9.25
*/ */
public boolean sendBytes(String dest, byte[] data, int proto, public boolean sendBytes(String dest, byte[] data, int proto,
@@ -122,22 +164,50 @@ class SAMDatagramSession extends SAMMessageSession {
if (data.length > DGRAM_SIZE_MAX) if (data.length > DGRAM_SIZE_MAX)
throw new DataFormatException("Datagram size exceeded (" + data.length + ")"); throw new DataFormatException("Datagram size exceeded (" + data.length + ")");
byte[] dgram; byte[] dgram;
if (version == 1) {
synchronized (dgramMaker) { synchronized (dgramMaker) {
dgram = dgramMaker.makeI2PDatagram(data); dgram = dgramMaker.makeI2PDatagram(data);
} }
return sendBytesThroughMessageSession(dest, dgram, I2PSession.PROTO_DATAGRAM, fromPort, toPort, proto = I2PSession.PROTO_DATAGRAM;
} else if (version == 2) {
Hash h = new Destination(dest).calculateHash();
dgram = Datagram2.make(I2PAppContext.getGlobalContext(), getI2PSession(), data, h);
proto = I2PSession.PROTO_DATAGRAM2;
} else {
dgram = Datagram3.make(I2PAppContext.getGlobalContext(), getI2PSession(), data);
proto = I2PSession.PROTO_DATAGRAM3;
}
return sendBytesThroughMessageSession(dest, dgram, proto, fromPort, toPort,
sendLeaseSet, sendTags,tagThreshold, expiration); sendLeaseSet, sendTags,tagThreshold, expiration);
} }
protected void messageReceived(byte[] msg, int proto, int fromPort, int toPort) { protected void messageReceived(byte[] msg, int proto, int fromPort, int toPort) {
byte[] payload; byte[] payload;
Destination sender; Destination sender;
Hash h;
try { try {
if (version == 1 && proto == I2PSession.PROTO_DATAGRAM) {
synchronized (dgramDissector) { synchronized (dgramDissector) {
dgramDissector.loadI2PDatagram(msg); dgramDissector.loadI2PDatagram(msg);
sender = dgramDissector.getSender(); sender = dgramDissector.getSender();
payload = dgramDissector.extractPayload(); payload = dgramDissector.extractPayload();
} }
h = null;
} else if (version == 2 && proto == I2PSession.PROTO_DATAGRAM2) {
Datagram2 dg = Datagram2.load(I2PAppContext.getGlobalContext(), getI2PSession(), msg);
sender = dg.getSender();
payload = dg.getPayload();
h = null;
} else if (version == 3 && proto == I2PSession.PROTO_DATAGRAM3) {
Datagram3 dg = Datagram3.load(I2PAppContext.getGlobalContext(), getI2PSession(), msg);
sender = null;
payload = dg.getPayload();
h = dg.getSender();
} else {
if (_log.shouldDebug())
_log.debug("Dropping mismatched protocol, datagram version=" + version + " proto=" + proto);
return;
}
} catch (DataFormatException e) { } catch (DataFormatException e) {
if (_log.shouldLog(Log.DEBUG)) { if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Dropping ill-formatted I2P repliable datagram", e); _log.debug("Dropping ill-formatted I2P repliable datagram", e);
@@ -151,7 +221,13 @@ class SAMDatagramSession extends SAMMessageSession {
} }
try { try {
if (sender != null) {
// DG 1/2
recv.receiveDatagramBytes(sender, payload, proto, fromPort, toPort); recv.receiveDatagramBytes(sender, payload, proto, fromPort, toPort);
} else {
// DG 3
recv.receiveDatagramBytes(h, payload, proto, fromPort, toPort);
}
} catch (IOException e) { } catch (IOException e) {
_log.error("Error forwarding message to receiver", e); _log.error("Error forwarding message to receiver", e);
close(); close();

View File

@@ -31,6 +31,7 @@ import net.i2p.data.Base64;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.Log; import net.i2p.util.Log;
/** /**
@@ -277,7 +278,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
rawSession = new SAMRawSession(destKeystream, props, this); rawSession = new SAMRawSession(destKeystream, props, this);
rawSession.start(); rawSession.start();
} else if (style.equals("DATAGRAM")) { } else if (style.equals("DATAGRAM")) {
datagramSession = new SAMDatagramSession(destKeystream, props,this); datagramSession = new SAMDatagramSession(destKeystream, props, this, 1);
datagramSession.start(); datagramSession.start();
} else if (style.equals("STREAM")) { } else if (style.equals("STREAM")) {
String dir = (String) props.remove("DIRECTION"); String dir = (String) props.remove("DIRECTION");
@@ -829,6 +830,11 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece
writeBytes(ByteBuffer.wrap(msg.toByteArray())); writeBytes(ByteBuffer.wrap(msg.toByteArray()));
} }
public void receiveDatagramBytes(Hash sender, byte data[], int proto,
int fromPort, int toPort) throws IOException {
throw new UnsupportedOperationException();
}
public void stopDatagramReceiving() { public void stopDatagramReceiving() {
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("stopDatagramReceiving() invoked"); _log.debug("stopDatagramReceiving() invoked");

View File

@@ -17,6 +17,7 @@ import net.i2p.client.I2PSessionException;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.Hash;
import net.i2p.util.Log; import net.i2p.util.Log;
@@ -36,15 +37,17 @@ class SAMv3DatagramSession extends SAMDatagramSession implements Session, SAMDat
* Caller MUST call start(). * Caller MUST call start().
* *
* @param nick nickname of the session * @param nick nickname of the session
* @param version datagram version 1/2/3
* @throws IOException * @throws IOException
* @throws DataFormatException * @throws DataFormatException
* @throws I2PSessionException * @throws I2PSessionException
*/ */
public SAMv3DatagramSession(String nick, SAMv3DatagramServer dgServer) public SAMv3DatagramSession(String nick, SAMv3DatagramServer dgServer, int version)
throws IOException, DataFormatException, I2PSessionException, SAMException { throws IOException, DataFormatException, I2PSessionException, SAMException {
super(SAMv3Handler.sSessionsHash.get(nick).getDest(), super(SAMv3Handler.sSessionsHash.get(nick).getDest(),
SAMv3Handler.sSessionsHash.get(nick).getProps(), SAMv3Handler.sSessionsHash.get(nick).getProps(),
null // to be replaced by this null, // to be replaced by this
version
); );
this.nick = nick; this.nick = nick;
this.recv = this; // replacement this.recv = this; // replacement
@@ -67,15 +70,16 @@ class SAMv3DatagramSession extends SAMDatagramSession implements Session, SAMDat
* Caller MUST call start(). * Caller MUST call start().
* *
* @param nick nickname of the session * @param nick nickname of the session
* @param version datagram version 1/2/3
* @throws IOException * @throws IOException
* @throws DataFormatException * @throws DataFormatException
* @throws I2PSessionException * @throws I2PSessionException
* @since 0.9.25 * @since 0.9.25
*/ */
public SAMv3DatagramSession(String nick, Properties props, SAMv3Handler handler, I2PSession isess, public SAMv3DatagramSession(String nick, Properties props, SAMv3Handler handler, I2PSession isess,
int listenPort, SAMv3DatagramServer dgServer) int listenPort, SAMv3DatagramServer dgServer, int version)
throws IOException, DataFormatException, I2PSessionException { throws IOException, DataFormatException, I2PSessionException {
super(isess, props, listenPort, null); // to be replaced by this super(isess, props, listenPort, null, version); // to be replaced by this
this.nick = nick ; this.nick = nick ;
this.recv = this ; // replacement this.recv = this ; // replacement
this.server = dgServer; this.server = dgServer;
@@ -88,8 +92,30 @@ class SAMv3DatagramSession extends SAMDatagramSession implements Session, SAMDat
if (this.clientAddress==null) { if (this.clientAddress==null) {
this.handler.receiveDatagramBytes(sender, data, proto, fromPort, toPort); this.handler.receiveDatagramBytes(sender, data, proto, fromPort, toPort);
} else { } else {
receiveDatagramBytes(sender.toBase64(), data, proto, fromPort, toPort);
}
}
/**
* Only for Datagram3, where the sender Destination is not available, only the hash.
* @since 0.9.68
*/
public void receiveDatagramBytes(Hash sender, byte[] data, int proto,
int fromPort, int toPort) throws IOException {
if (this.clientAddress==null) {
this.handler.receiveDatagramBytes(sender, data, proto, fromPort, toPort);
} else {
receiveDatagramBytes(sender.toBase64(), data, proto, fromPort, toPort);
}
}
/**
* @since 0.9.68 split out from above
*/
private void receiveDatagramBytes(String sender, byte[] data, int proto,
int fromPort, int toPort) throws IOException {
StringBuilder buf = new StringBuilder(600); StringBuilder buf = new StringBuilder(600);
buf.append(sender.toBase64()); buf.append(sender);
if ((handler.verMajor == 3 && handler.verMinor >= 2) || handler.verMajor > 3) { if ((handler.verMajor == 3 && handler.verMinor >= 2) || handler.verMajor > 3) {
buf.append(" FROM_PORT=").append(fromPort).append(" TO_PORT=").append(toPort); buf.append(" FROM_PORT=").append(fromPort).append(" TO_PORT=").append(toPort);
} }
@@ -102,7 +128,6 @@ class SAMv3DatagramSession extends SAMDatagramSession implements Session, SAMDat
((Buffer)msgBuf).flip(); ((Buffer)msgBuf).flip();
this.server.send(this.clientAddress, msgBuf); this.server.send(this.clientAddress, msgBuf);
} }
}
public void stopDatagramReceiving() { public void stopDatagramReceiving() {
} }

View File

@@ -500,10 +500,20 @@ class SAMv3Handler extends SAMv1Handler
rawSession = v3; rawSession = v3;
this.session = v3; this.session = v3;
v3.start(); v3.start();
} else if (style.equals("DATAGRAM")) { } else if (style.equals("DATAGRAM") ||
style.equals("DATAGRAM2") ||
style.equals("DATAGRAM3")) {
detector.start(); detector.start();
SAMv3DatagramServer dgs = bridge.getV3DatagramServer(props); SAMv3DatagramServer dgs = bridge.getV3DatagramServer(props);
SAMv3DatagramSession v3 = new SAMv3DatagramSession(nick, dgs); int v;
if (style.equals("DATAGRAM")) {
v = 1;
} else if (style.equals("DATAGRAM2")) {
v = 2;
} else {
v = 3;
}
SAMv3DatagramSession v3 = new SAMv3DatagramSession(nick, dgs, v);
datagramSession = v3; datagramSession = v3;
this.session = v3; this.session = v3;
v3.start(); v3.start();