diff --git a/apps/sam/java/src/net/i2p/sam/PrimarySession.java b/apps/sam/java/src/net/i2p/sam/PrimarySession.java index 0dbef2a1e..76580d996 100644 --- a/apps/sam/java/src/net/i2p/sam/PrimarySession.java +++ b/apps/sam/java/src/net/i2p/sam/PrimarySession.java @@ -23,6 +23,7 @@ import net.i2p.client.streaming.I2PSocket; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; +import net.i2p.data.Hash; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; @@ -123,8 +124,11 @@ class PrimarySession extends SAMv3StreamSession implements SAMDatagramReceiver, if (spr != null) { try { 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 || + listenProtocol == I2PSession.PROTO_DATAGRAM || + listenProtocol == I2PSession.PROTO_DATAGRAM2 || + listenProtocol == I2PSession.PROTO_DATAGRAM3 || listenProtocol == I2PSession.PROTO_STREAMING) return "Bad RAW LISTEN_PPROTOCOL " + spr; } catch (NumberFormatException nfe) { @@ -134,11 +138,23 @@ class PrimarySession extends SAMv3StreamSession implements SAMDatagramReceiver, SAMv3RawSession ssess = new SAMv3RawSession(nick, props, handler, isess, listenProtocol, listenPort, dgs); subhandler.setSession(ssess); sess = ssess; - } else if (style.equals("DATAGRAM")) { + } else if (style.equals("DATAGRAM") || + style.equals("DATAGRAM2") || + style.equals("DATAGRAM3")) { if (!props.containsKey("PORT")) return "DATAGRAM subsession must specify PORT"; - listenProtocol = I2PSession.PROTO_DATAGRAM; - SAMv3DatagramSession ssess = new SAMv3DatagramSession(nick, props, handler, isess, listenPort, dgs); + int v; + if (style.equals("DATAGRAM")) { + listenProtocol = I2PSession.PROTO_DATAGRAM; + 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); sess = ssess; } else if (style.equals("STREAM")) { @@ -212,6 +228,15 @@ class PrimarySession extends SAMv3StreamSession implements SAMDatagramReceiver, 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. */ diff --git a/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java b/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java index b07ff8fbe..3ab480ac9 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java +++ b/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java @@ -11,6 +11,7 @@ package net.i2p.sam; import java.io.IOException; import net.i2p.data.Destination; +import net.i2p.data.Hash; /** * 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; + /** + * 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. * diff --git a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java index baef6f454..653bdb554 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java @@ -12,17 +12,22 @@ import java.io.IOException; import java.io.InputStream; import java.util.Properties; +import net.i2p.I2PAppContext; import net.i2p.client.I2PSession; 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.I2PDatagramMaker; import net.i2p.client.datagram.I2PInvalidDatagramException; import net.i2p.data.DataFormatException; import net.i2p.data.Destination; +import net.i2p.data.Hash; import net.i2p.util.Log; /** * SAM DATAGRAM session class. + * Supports DG2/3 as of 0.9.68 * * @author human */ @@ -33,28 +38,40 @@ class SAMDatagramSession extends SAMMessageSession { // FIXME make final after fixing SAMv3DatagramSession override protected SAMDatagramReceiver recv; private final I2PDatagramMaker dgramMaker; - private final I2PDatagramDissector dgramDissector = new I2PDatagramDissector(); + private final I2PDatagramDissector dgramDissector; + private final int version; /** * Create a new SAM DATAGRAM session. + * v1/v2 (DG1 only) or v3 (DG 1/2/3) * * @param dest Base64-encoded destination (private key) * @param props Properties to setup the I2P session * @param recv Object that will receive incoming data + * @param v datagram version 1/2/3 * @throws IOException * @throws DataFormatException * @throws I2PSessionException */ protected SAMDatagramSession(String dest, Properties props, - SAMDatagramReceiver recv) throws IOException, + SAMDatagramReceiver recv, int v) throws IOException, DataFormatException, I2PSessionException { super(dest, props); - this.recv = recv; - dgramMaker = new I2PDatagramMaker(getI2PSession()); + if (v == 1) { + 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. + * v1/v2 only, DG1 only * * Caller MUST call start(). * @@ -64,27 +81,41 @@ class SAMDatagramSession extends SAMMessageSession { * @throws IOException * @throws DataFormatException * @throws I2PSessionException + * @deprecated unused */ + @Deprecated public SAMDatagramSession(InputStream destStream, Properties props, SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException { super(destStream, props); this.recv = recv; dgramMaker = new I2PDatagramMaker(getI2PSession()); + dgramDissector = new I2PDatagramDissector(); + version = 1; } /** * 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 */ protected SAMDatagramSession(I2PSession sess, Properties props, int listenPort, - SAMDatagramReceiver recv) throws IOException, + SAMDatagramReceiver recv, int v) throws IOException, DataFormatException, I2PSessionException { super(sess, I2PSession.PROTO_DATAGRAM, listenPort); this.recv = recv; - dgramMaker = new I2PDatagramMaker(getI2PSession()); + if (v == 1) { + 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 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 * @throws DataFormatException on unknown / bad dest @@ -102,16 +133,27 @@ class SAMDatagramSession extends SAMMessageSession { int fromPort, int toPort) throws DataFormatException, I2PSessionException { if (data.length > DGRAM_SIZE_MAX) throw new DataFormatException("Datagram size exceeded (" + data.length + ")"); - byte[] dgram ; - synchronized (dgramMaker) { - dgram = dgramMaker.makeI2PDatagram(data); + byte[] dgram; + if (version == 1) { + synchronized (dgramMaker) { + dgram = dgramMaker.makeI2PDatagram(data); + } + 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, I2PSession.PROTO_DATAGRAM, fromPort, toPort); + return sendBytesThroughMessageSession(dest, dgram, proto, fromPort, toPort); } /** * 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 */ public boolean sendBytes(String dest, byte[] data, int proto, @@ -121,22 +163,50 @@ class SAMDatagramSession extends SAMMessageSession { throws DataFormatException, I2PSessionException { if (data.length > DGRAM_SIZE_MAX) throw new DataFormatException("Datagram size exceeded (" + data.length + ")"); - byte[] dgram ; - synchronized (dgramMaker) { - dgram = dgramMaker.makeI2PDatagram(data); + byte[] dgram; + if (version == 1) { + synchronized (dgramMaker) { + dgram = dgramMaker.makeI2PDatagram(data); + } + 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, I2PSession.PROTO_DATAGRAM, fromPort, toPort, + return sendBytesThroughMessageSession(dest, dgram, proto, fromPort, toPort, sendLeaseSet, sendTags,tagThreshold, expiration); } protected void messageReceived(byte[] msg, int proto, int fromPort, int toPort) { byte[] payload; Destination sender; + Hash h; try { - synchronized (dgramDissector) { - dgramDissector.loadI2PDatagram(msg); - sender = dgramDissector.getSender(); - payload = dgramDissector.extractPayload(); + if (version == 1 && proto == I2PSession.PROTO_DATAGRAM) { + synchronized (dgramDissector) { + dgramDissector.loadI2PDatagram(msg); + sender = dgramDissector.getSender(); + 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) { if (_log.shouldLog(Log.DEBUG)) { @@ -151,7 +221,13 @@ class SAMDatagramSession extends SAMMessageSession { } try { - recv.receiveDatagramBytes(sender, payload, proto, fromPort, toPort); + if (sender != null) { + // DG 1/2 + recv.receiveDatagramBytes(sender, payload, proto, fromPort, toPort); + } else { + // DG 3 + recv.receiveDatagramBytes(h, payload, proto, fromPort, toPort); + } } catch (IOException e) { _log.error("Error forwarding message to receiver", e); close(); diff --git a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java index b06515110..01cd0c170 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java @@ -31,6 +31,7 @@ import net.i2p.data.Base64; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; +import net.i2p.data.Hash; import net.i2p.util.Log; /** @@ -277,7 +278,7 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece rawSession = new SAMRawSession(destKeystream, props, this); rawSession.start(); } else if (style.equals("DATAGRAM")) { - datagramSession = new SAMDatagramSession(destKeystream, props,this); + datagramSession = new SAMDatagramSession(destKeystream, props, this, 1); datagramSession.start(); } else if (style.equals("STREAM")) { String dir = (String) props.remove("DIRECTION"); @@ -829,6 +830,11 @@ class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramRece 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() { if (_log.shouldLog(Log.DEBUG)) _log.debug("stopDatagramReceiving() invoked"); diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java index 70a3a99ef..18459b9ef 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3DatagramSession.java @@ -17,6 +17,7 @@ import net.i2p.client.I2PSessionException; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; import net.i2p.data.Destination; +import net.i2p.data.Hash; import net.i2p.util.Log; @@ -36,15 +37,17 @@ class SAMv3DatagramSession extends SAMDatagramSession implements Session, SAMDat * Caller MUST call start(). * * @param nick nickname of the session + * @param version datagram version 1/2/3 * @throws IOException * @throws DataFormatException * @throws I2PSessionException */ - public SAMv3DatagramSession(String nick, SAMv3DatagramServer dgServer) + public SAMv3DatagramSession(String nick, SAMv3DatagramServer dgServer, int version) throws IOException, DataFormatException, I2PSessionException, SAMException { super(SAMv3Handler.sSessionsHash.get(nick).getDest(), SAMv3Handler.sSessionsHash.get(nick).getProps(), - null // to be replaced by this + null, // to be replaced by this + version ); this.nick = nick; this.recv = this; // replacement @@ -67,15 +70,16 @@ class SAMv3DatagramSession extends SAMDatagramSession implements Session, SAMDat * Caller MUST call start(). * * @param nick nickname of the session + * @param version datagram version 1/2/3 * @throws IOException * @throws DataFormatException * @throws I2PSessionException * @since 0.9.25 */ public SAMv3DatagramSession(String nick, Properties props, SAMv3Handler handler, I2PSession isess, - int listenPort, SAMv3DatagramServer dgServer) + int listenPort, SAMv3DatagramServer dgServer, int version) 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.recv = this ; // replacement this.server = dgServer; @@ -88,22 +92,43 @@ class SAMv3DatagramSession extends SAMDatagramSession implements Session, SAMDat if (this.clientAddress==null) { this.handler.receiveDatagramBytes(sender, data, proto, fromPort, toPort); } else { - StringBuilder buf = new StringBuilder(600); - buf.append(sender.toBase64()); - if ((handler.verMajor == 3 && handler.verMinor >= 2) || handler.verMajor > 3) { - buf.append(" FROM_PORT=").append(fromPort).append(" TO_PORT=").append(toPort); - } - buf.append('\n'); - String msg = buf.toString(); - ByteBuffer msgBuf = ByteBuffer.allocate(msg.length()+data.length); - msgBuf.put(DataHelper.getASCII(msg)); - msgBuf.put(data); - // not ByteBuffer to avoid Java 8/9 issues with flip() - ((Buffer)msgBuf).flip(); - this.server.send(this.clientAddress, msgBuf); + 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); + buf.append(sender); + if ((handler.verMajor == 3 && handler.verMinor >= 2) || handler.verMajor > 3) { + buf.append(" FROM_PORT=").append(fromPort).append(" TO_PORT=").append(toPort); + } + buf.append('\n'); + String msg = buf.toString(); + ByteBuffer msgBuf = ByteBuffer.allocate(msg.length()+data.length); + msgBuf.put(DataHelper.getASCII(msg)); + msgBuf.put(data); + // not ByteBuffer to avoid Java 8/9 issues with flip() + ((Buffer)msgBuf).flip(); + this.server.send(this.clientAddress, msgBuf); + } + public void stopDatagramReceiving() { } } diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java index e667cb228..ed3a520a2 100644 --- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java +++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java @@ -500,10 +500,20 @@ class SAMv3Handler extends SAMv1Handler rawSession = v3; this.session = v3; v3.start(); - } else if (style.equals("DATAGRAM")) { + } else if (style.equals("DATAGRAM") || + style.equals("DATAGRAM2") || + style.equals("DATAGRAM3")) { detector.start(); 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; this.session = v3; v3.start();