From edde533e1b2cf7ae541350b72fe8de3a5ff5b9e4 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Sat, 6 Feb 2016 16:56:37 +0000
Subject: [PATCH] SAM v3.3: Fixes after testing - More error checking - Better
 error responses - Fix listen port and protocol for DATAGRAM and RAW - Fix
 adding sessions with duplicate dests to DB - Add more sessions in
 SAMStreamSink

---
 .../java/src/net/i2p/sam/MasterSession.java   | 16 +++++++++------
 .../src/net/i2p/sam/SAMMessageSession.java    | 10 ++++------
 .../src/net/i2p/sam/SAMStreamSession.java     |  2 +-
 .../java/src/net/i2p/sam/SAMv3Handler.java    | 14 ++++++-------
 apps/sam/java/src/net/i2p/sam/SessionsDB.java | 20 +++++++++++--------
 .../src/net/i2p/sam/client/SAMReader.java     |  4 ++++
 .../src/net/i2p/sam/client/SAMStreamSink.java | 19 ++++++++++++++++--
 7 files changed, 55 insertions(+), 30 deletions(-)

diff --git a/apps/sam/java/src/net/i2p/sam/MasterSession.java b/apps/sam/java/src/net/i2p/sam/MasterSession.java
index ba64e9255a..76ba6ab3a4 100644
--- a/apps/sam/java/src/net/i2p/sam/MasterSession.java
+++ b/apps/sam/java/src/net/i2p/sam/MasterSession.java
@@ -42,6 +42,7 @@ class MasterSession extends SAMv3StreamSession implements SAMDatagramReceiver, S
 	private final SAMv3DatagramServer dgs;
 	private final Map<String, SAMMessageSess> sessions;
 	private final StreamAcceptor streamAcceptor;
+	private static final String[] INVALID_OPTS = { "PORT", "HOST", "FROM_PORT", "TO_PORT", "PROTOCOL" };
 
 	/**
 	 * Build a Session according to information
@@ -57,6 +58,11 @@ class MasterSession extends SAMv3StreamSession implements SAMDatagramReceiver, S
 	public MasterSession(String nick, SAMv3DatagramServer dgServer, SAMv3Handler handler, Properties props) 
 			throws IOException, DataFormatException, SAMException {
 		super(nick);
+		for (int i = 0; i < INVALID_OPTS.length; i++) {
+			String p = INVALID_OPTS[i];
+			if (props.containsKey(p))
+				throw new SAMException("MASTER session options may not contain " + p);
+		}
 		dgs = dgServer;
 		sessions = new ConcurrentHashMap<String, SAMMessageSess>(4);
 		this.handler = handler;
@@ -162,15 +168,13 @@ class MasterSession extends SAMv3StreamSession implements SAMDatagramReceiver, S
 
 		rec = new SessionRecord(getDestination().toBase64(), props, subhandler);
 		try {
-			if (!SAMv3Handler.sSessionsHash.put(nick, rec))
-				return "Duplicate ID " + nick;
+			SAMv3Handler.sSessionsHash.putDupDestOK(nick, rec);
 			sessions.put(nick, sess);
 		} catch (SessionsDB.ExistingIdException e) {
-			return e.toString();
-		} catch (SessionsDB.ExistingDestException e) {
-			// fixme need new db method for dup dests
+			return "Duplicate ID " + nick;
 		}
-		// listeners etc
+		if (_log.shouldWarn())
+			_log.warn("added " + style + " proto " + listenProtocol + " port " + listenPort);
 
 		sess.start();
 		// all ok
diff --git a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java
index a65f387cf1..328749afb4 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java
@@ -67,11 +67,10 @@ abstract class SAMMessageSession implements SAMMessageSess {
         _log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Initializing SAM message-based session");
-
-        handler = new SAMMessageSessionHandler(destStream, props);
-        session = handler.getSession();
         listenProtocol = I2PSession.PROTO_ANY;
         listenPort = I2PSession.PORT_ANY;
+        handler = new SAMMessageSessionHandler(destStream, props);
+        session = handler.getSession();
     }
 
     /**
@@ -89,11 +88,10 @@ abstract class SAMMessageSession implements SAMMessageSess {
         _log = I2PAppContext.getGlobalContext().logManager().getLog(getClass());
         if (_log.shouldLog(Log.DEBUG))
             _log.debug("Initializing SAM message-based session");
-
-        session = sess;
-        handler = new SAMMessageSessionHandler(session);
         this.listenProtocol = listenProtocol;
         this.listenPort = listenPort;
+        session = sess;
+        handler = new SAMMessageSessionHandler(session);
     }
 
     /*
diff --git a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java
index 42edac0b58..6cebed71da 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMStreamSession.java
@@ -368,7 +368,7 @@ class SAMStreamSession implements SAMMessageSess {
      *  @since 0.9.25 moved from subclass SAMv3StreamSession to implement SAMMessageSess
      */
     public boolean sendBytes(String s, byte[] b, int pr, int fp, int tp) throws I2PSessionException {
-    	throw new I2PSessionException("Unsupported in stream or master session");
+    	throw new I2PSessionException("Unsupported in STREAM or MASTER session");
     }
 
     /** 
diff --git a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java
index e8284964ff..48774d5652 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMv3Handler.java
@@ -314,10 +314,13 @@ class SAMv3Handler extends SAMv1Handler
 		} catch (IOException e) {
 			if (_log.shouldLog(Log.DEBUG))
 				_log.debug("Caught IOException in handler", e);
+			writeString(SESSION_ERROR, e.getMessage());
 		} catch (SAMException e) {
 			_log.error("Unexpected exception for message [" + msg + ']', e);
+			writeString(SESSION_ERROR, e.getMessage());
 		} catch (RuntimeException e) {
 			_log.error("Unexpected exception for message [" + msg + ']', e);
+			writeString(SESSION_ERROR, e.getMessage());
 		} finally {
 			if (_log.shouldLog(Log.DEBUG))
 				_log.debug("Stopping handler");
@@ -467,8 +470,6 @@ class SAMv3Handler extends SAMv1Handler
 					// SAMStreamSession constructor.
 					allProps.setProperty("i2p.streaming.enforceProtocol", "true");
 					allProps.setProperty("i2cp.dontPublishLeaseSet", "false");
-					allProps.setProperty("FROM_PORT", Integer.toString(I2PSession.PORT_UNSPECIFIED));
-					allProps.setProperty("TO_PORT", Integer.toString(I2PSession.PORT_UNSPECIFIED));
 				}
 
 				try {
@@ -530,9 +531,9 @@ class SAMv3Handler extends SAMv1Handler
 					msg = msess.remove(nick, props);
 				}
 				if (msg == null)
-					return writeString("SESSION STATUS RESULT=OK", opcode + ' ' + nick);
+					return writeString("SESSION STATUS RESULT=OK ID=\"" + nick + '"', opcode + ' ' + nick);
 				else
-					return writeString(SESSION_ERROR, msg);
+					return writeString(SESSION_ERROR + " ID=\"" + nick + '"', msg);
 			} else {
 				if (_log.shouldLog(Log.DEBUG))
 					_log.debug("Unrecognized SESSION message opcode: \""
@@ -581,10 +582,9 @@ class SAMv3Handler extends SAMv1Handler
 
 		if ( session != null )
 		{
-			_log.error ( "STREAM message received, but this session is a master session" );
-			
+			_log.error("v3 control socket cannot be used for STREAM");
 			try {
-				notifyStreamResult(true, "I2P_ERROR", "master session cannot be used for streams");
+				notifyStreamResult(true, "I2P_ERROR", "v3 control socket cannot be used for STREAM");
 			} catch (IOException e) {}
 			return false;
 		}
diff --git a/apps/sam/java/src/net/i2p/sam/SessionsDB.java b/apps/sam/java/src/net/i2p/sam/SessionsDB.java
index 21876d7cc6..dfdc3c0041 100644
--- a/apps/sam/java/src/net/i2p/sam/SessionsDB.java
+++ b/apps/sam/java/src/net/i2p/sam/SessionsDB.java
@@ -32,8 +32,7 @@ class SessionsDB {
 		map = new HashMap<String, SessionRecord>() ;
 	}
 
-	/** @return success */
-	synchronized public boolean put( String nick, SessionRecord session )
+	public synchronized void put(String nick, SessionRecord session)
 		throws ExistingIdException, ExistingDestException
 	{
 		if ( map.containsKey(nick) ) {
@@ -44,14 +43,19 @@ class SessionsDB {
 				throw new ExistingDestException();
 			}
 		}
+		session.createThreadGroup("SAM session "+nick);
+		map.put(nick, session) ;
+	}
 
-		if ( !map.containsKey(nick) ) {
-			session.createThreadGroup("SAM session "+nick);
-			map.put(nick, session) ;
-			return true ;
+	/** @since 0.9.25 */
+	public synchronized void putDupDestOK(String nick, SessionRecord session)
+		throws ExistingIdException
+	{
+		if (map.containsKey(nick)) {
+			throw new ExistingIdException();
 		}
-		else
-			return false ;
+		session.createThreadGroup("SAM session "+nick);
+		map.put(nick, session) ;
 	}
 
 	/** @return true if removed */
diff --git a/apps/sam/java/src/net/i2p/sam/client/SAMReader.java b/apps/sam/java/src/net/i2p/sam/client/SAMReader.java
index 0ab88cfcee..7153998e94 100644
--- a/apps/sam/java/src/net/i2p/sam/client/SAMReader.java
+++ b/apps/sam/java/src/net/i2p/sam/client/SAMReader.java
@@ -137,6 +137,10 @@ public class SAMReader {
                     if ( (eq > 0) && (eq < pair.length() - 1) ) {
                         String name = pair.substring(0, eq);
                         String val = pair.substring(eq+1);
+                        if (val.length() <= 0) {
+                            _log.error("Empty value for " + name);
+                            continue;
+                        }
                         while ( (val.charAt(0) == '\"') && (val.length() > 0) )
                             val = val.substring(1);
                         while ( (val.length() > 0) && (val.charAt(val.length()-1) == '\"') )
diff --git a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java
index a0d5d15607..4314965414 100644
--- a/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java
+++ b/apps/sam/java/src/net/i2p/sam/client/SAMStreamSink.java
@@ -702,8 +702,7 @@ public class SAMStreamSink {
                 samOut.flush();
                 if (_log.shouldLog(Log.DEBUG))
                     _log.debug("SESSION " + command + " sent");
-                if (mode == STREAM) {
-                    // why only waiting in stream mode?
+                //if (mode == STREAM) {
                     boolean ok;
                     if (masterMode)
                         ok = eventHandler.waitForSessionAddReply();
@@ -713,6 +712,22 @@ public class SAMStreamSink {
                         throw new IOException("SESSION " + command + " failed");
                     if (_log.shouldLog(Log.DEBUG))
                         _log.debug("SESSION " + command + " reply found: " + ok);
+                //}
+                if (masterMode) {
+                    // do a bunch more
+                    req = "SESSION ADD STYLE=STREAM FROM_PORT=99 ID=stream99\n";
+                    samOut.write(req.getBytes("UTF-8"));
+                    req = "SESSION ADD STYLE=STREAM FROM_PORT=98 ID=stream98\n";
+                    samOut.write(req.getBytes("UTF-8"));
+                    req = "SESSION ADD STYLE=DATAGRAM PORT=9997 LISTEN_PORT=97 ID=dg97\n";
+                    samOut.write(req.getBytes("UTF-8"));
+                    req = "SESSION ADD STYLE=DATAGRAM PORT=9996 FROM_PORT=96 ID=dg96\n";
+                    samOut.write(req.getBytes("UTF-8"));
+                    req = "SESSION ADD STYLE=RAW PORT=9995 LISTEN_PORT=95 ID=raw95\n";
+                    samOut.write(req.getBytes("UTF-8"));
+                    req = "SESSION ADD STYLE=RAW PORT=9994 FROM_PORT=94 LISTEN_PROTOCOL=222 ID=raw94\n";
+                    samOut.write(req.getBytes("UTF-8"));
+                    samOut.flush();
                 }
                 req = "NAMING LOOKUP NAME=ME\n";
                 samOut.write(req.getBytes("UTF-8"));
-- 
GitLab