diff --git a/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java b/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java
new file mode 100644
index 0000000000000000000000000000000000000000..b47568416d666c583bcee372a5f6dad2753fc57d
--- /dev/null
+++ b/apps/sam/java/src/net/i2p/sam/SAMDatagramReceiver.java
@@ -0,0 +1,32 @@
+package net.i2p.sam;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by human in 2004 and released into the public domain 
+ * with no warranty of any kind, either expressed or implied.  
+ * It probably won't  make your computer catch on fire, or eat 
+ * your children, but it might.  Use at your own risk.
+ *
+ */
+
+import java.io.IOException;
+
+import net.i2p.data.Destination;
+
+/**
+ * Interface for sending raw data to a SAM client
+ */
+public interface SAMDatagramReceiver {
+
+    /**
+     * Send a byte array to a SAM client.
+     *
+     * @param data Byte array to be received
+     */
+    public void receiveDatagramBytes(Destination sender, byte data[]) throws IOException;
+
+    /**
+     * Stop receiving data.
+     *
+     */
+    public void stopDatagramReceiving();
+}
diff --git a/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java
new file mode 100644
index 0000000000000000000000000000000000000000..3eb9492cc904d6d934b987d46cff3be9db009799
--- /dev/null
+++ b/apps/sam/java/src/net/i2p/sam/SAMDatagramSession.java
@@ -0,0 +1,111 @@
+package net.i2p.sam;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by human in 2004 and released into the public domain 
+ * with no warranty of any kind, either expressed or implied.  
+ * It probably won't  make your computer catch on fire, or eat 
+ * your children, but it might.  Use at your own risk.
+ *
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import net.i2p.client.datagram.I2PDatagramDissector;
+import net.i2p.client.datagram.I2PDatagramMaker;
+import net.i2p.client.datagram.I2PInvalidDatagramException;
+import net.i2p.client.I2PSessionException;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.Destination;
+import net.i2p.util.Log;
+
+/**
+ * SAM DATAGRAM session class.
+ *
+ * @author human
+ */
+public class SAMDatagramSession extends SAMMessageSession {
+
+    private final static Log _log = new Log(SAMDatagramSession.class);
+
+    private SAMDatagramReceiver recv = null;
+
+    private I2PDatagramMaker dgramMaker;
+    private I2PDatagramDissector dgramDissector = new I2PDatagramDissector();
+    /**
+     * Create a new SAM DATAGRAM session.
+     *
+     * @param dest Base64-encoded destination (private key)
+     * @param props Properties to setup the I2P session
+     * @param recv Object that will receive incoming data
+     */
+    public SAMDatagramSession(String dest, Properties props,
+                         SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException {
+        super(dest, props);
+
+        this.recv = recv;
+        dgramMaker = new I2PDatagramMaker(getI2PSession());
+    }
+
+    /**
+     * Create a new SAM DATAGRAM session.
+     *
+     * @param destStream Input stream containing the destination keys
+     * @param props Properties to setup the I2P session
+     * @param recv Object that will receive incoming data
+     */
+    public SAMDatagramSession(InputStream destStream, Properties props,
+                         SAMDatagramReceiver recv) throws IOException, DataFormatException, I2PSessionException {
+        super(destStream, props);
+
+        this.recv = recv;
+        dgramMaker = new I2PDatagramMaker(getI2PSession());
+    }
+
+    /**
+     * Send bytes through a SAM DATAGRAM session.
+     *
+     * @param data Bytes to be sent
+     *
+     * @return True if the data was sent, false otherwise
+     */
+    public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
+        byte[] dgram = dgramMaker.makeI2PDatagram(data);
+
+        return sendBytesThroughMessageSession(dest, dgram);
+    }
+
+    protected void messageReceived(byte[] msg) {
+        byte[] payload;
+        Destination sender;
+        try {
+            dgramDissector.loadI2PDatagram(msg);
+            sender = dgramDissector.getSender();
+            payload = dgramDissector.extractPayload();
+        } catch (DataFormatException e) {
+            if (_log.shouldLog(Log.DEBUG)) {
+                _log.debug("Dropping ill-formatted I2P repliable datagram");
+            }
+            return;
+        } catch (I2PInvalidDatagramException e) {
+            if (_log.shouldLog(Log.DEBUG)) {
+                _log.debug("Dropping ill-signed I2P repliable datagram");
+            }
+            return;
+        }
+
+        try {
+            recv.receiveDatagramBytes(sender, payload);
+        } catch (IOException e) {
+            _log.error("Error forwarding message to receiver", e);
+            close();
+        }
+    }
+
+    protected void shutDown() {
+        recv.stopDatagramReceiving();
+    }
+}
diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandler.java b/apps/sam/java/src/net/i2p/sam/SAMHandler.java
index bfd146ffbba7fefc06da0ad51ac9055ed9391a5b..be68e09e1169480c25294184c394b95e3305f212 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMHandler.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMHandler.java
@@ -9,6 +9,7 @@ package net.i2p.sam;
  */
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.Socket;
 
@@ -29,20 +30,36 @@ public abstract class SAMHandler implements Runnable {
     protected I2PThread thread = null;
 
     private Object socketWLock = new Object(); // Guards writings on socket
+    private Socket socket = null;
     private OutputStream socketOS = null; // Stream associated to socket
-    protected Socket socket = null;
 
     protected int verMajor = 0;
     protected int verMinor = 0;
 
-    private boolean stopHandler = false;
     private Object  stopLock = new Object();
+    private boolean stopHandler = false;
+
+    /**
+     * SAMHandler constructor (to be called by subclasses)
+     *
+     * @param s Socket attached to a SAM client
+     * @param verMajor SAM major version to manage
+     * @param verMinor SAM minor version to manage
+     */
+    protected SAMHandler(Socket s,
+                         int verMajor, int verMinor) throws IOException {
+        socket = s;
+        socketOS = socket.getOutputStream();
+
+        this.verMajor = verMajor;
+        this.verMinor = verMinor;
+    }
 
     /**
      * Start handling the SAM connection, detaching an handling thread.
      *
      */
-    public void startHandling() {
+    public final void startHandling() {
         thread = new I2PThread(this, "SAMHandler");
         thread.start();
     }
@@ -53,6 +70,14 @@ public abstract class SAMHandler implements Runnable {
      */
     protected abstract void handle();
 
+    /**
+     * Get the input stream of the socket connected to the SAM client
+     *
+     */
+    protected final InputStream getClientSocketInputStream() throws IOException {
+        return socket.getInputStream();
+    }
+
     /**
      * Write a byte array on the handler's socket.  This method must
      * always be used when writing data, unless you really know what
@@ -60,11 +85,8 @@ public abstract class SAMHandler implements Runnable {
      *
      * @param data A byte array to be written
      */
-    protected void writeBytes(byte[] data) throws IOException {
+    protected final void writeBytes(byte[] data) throws IOException {
         synchronized (socketWLock) {
-            if (socketOS == null) {
-                socketOS = socket.getOutputStream();
-            }
             socketOS.write(data);
             socketOS.flush();
         }
@@ -79,7 +101,7 @@ public abstract class SAMHandler implements Runnable {
      *
      * @return True is the string was successfully written, false otherwise
      */
-    protected boolean writeString(String str) {
+    protected final boolean writeString(String str) {
         try {
             writeBytes(str.getBytes("ISO-8859-1"));
         } catch (IOException e) {
@@ -90,11 +112,19 @@ public abstract class SAMHandler implements Runnable {
         return true;
     }
 
+    /**
+     * Close the socket connected to the SAM client.
+     *
+     */
+    protected final void closeClientSocket() throws IOException {
+        socket.close();
+    }
+
     /**
      * Stop the SAM handler
      *
      */
-    public void stopHandling() {
+    public final void stopHandling() {
         synchronized (stopLock) {
             stopHandler = true;
         }
@@ -105,7 +135,7 @@ public abstract class SAMHandler implements Runnable {
      *
      * @return True if the handler should be stopped, false otherwise
      */
-    protected boolean shouldStop() {
+    protected final boolean shouldStop() {
         synchronized (stopLock) {
             return stopHandler;
         }
@@ -116,7 +146,13 @@ public abstract class SAMHandler implements Runnable {
      *
      * @return A String describing the handler;
      */
-    public abstract String toString();
+    public final String toString() {
+        return ("SAM handler (class: " + this.getClass().getName()
+                + "; SAM version: " + verMajor + "." + verMinor
+                + "; client: "
+                + this.socket.getInetAddress().toString() + ":"
+                + this.socket.getPort() + ")");
+    }
 
     public final void run() {
         handle();
diff --git a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java
index f7fdb8249070bc16b3be9e47969cfee66e539fd2..26dbfd288fc3d2e8c2b8c87fa4cce416c0d8181a 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMHandlerFactory.java
@@ -36,18 +36,23 @@ public class SAMHandlerFactory {
      */
     public static SAMHandler createSAMHandler(Socket s) throws SAMException {
         BufferedReader br;
+        String line;
         StringTokenizer tok;
 
         try {
             br = new BufferedReader(new InputStreamReader(s.getInputStream(),
                                                           "ISO-8859-1"));
-            tok = new StringTokenizer(br.readLine(), " ");
+            line = br.readLine();
+            if (line == null) {
+                _log.debug("Connection closed by client");
+                return null;
+            }
+            tok = new StringTokenizer(line, " ");
         } catch (IOException e) {
             throw new SAMException("Error reading from socket: "
                                    + e.getMessage());
         } catch (Exception e) {
-            throw new SAMException("Unexpected error: "
-                                   + e.getMessage());
+            throw new SAMException("Unexpected error: " + e.getMessage());
         }
 
         // Message format: HELLO VERSION MIN=v1 MAX=v2
@@ -118,15 +123,20 @@ public class SAMHandlerFactory {
         int verMajor = getMajor(ver);
         int verMinor = getMinor(ver);
         SAMHandler handler;
-        switch (verMajor) {
-        case 1:
-            handler = new SAMv1Handler(s, verMajor, verMinor);
-            break;
-        default:
-            _log.error("BUG! Trying to initialize the wrong SAM version!");
-            throw new SAMException("BUG triggered! (handler instantiation)");
-        }
 
+        try {
+            switch (verMajor) {
+            case 1:
+                handler = new SAMv1Handler(s, verMajor, verMinor);
+                break;
+            default:
+                _log.error("BUG! Trying to initialize the wrong SAM version!");
+                throw new SAMException("BUG! (in handler instantiation)");
+            }
+        } catch (IOException e) {
+            _log.error("IOException caught during SAM handler instantiation");
+            return null;
+        }
         return handler;
     }
 
diff --git a/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java
new file mode 100644
index 0000000000000000000000000000000000000000..15bd926c19e8098bba13e3f82becf6b575f98cf8
--- /dev/null
+++ b/apps/sam/java/src/net/i2p/sam/SAMMessageSession.java
@@ -0,0 +1,246 @@
+package net.i2p.sam;
+/*
+ * free (adj.): unencumbered; not under the control of others
+ * Written by human in 2004 and released into the public domain 
+ * with no warranty of any kind, either expressed or implied.  
+ * It probably won't  make your computer catch on fire, or eat 
+ * your children, but it might.  Use at your own risk.
+ *
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import net.i2p.client.I2PClient;
+import net.i2p.client.I2PClientFactory;
+import net.i2p.client.I2PSession;
+import net.i2p.client.I2PSessionException;
+import net.i2p.client.I2PSessionListener;
+import net.i2p.data.Base64;
+import net.i2p.data.DataFormatException;
+import net.i2p.data.Destination;
+import net.i2p.util.HexDump;
+import net.i2p.util.I2PThread;
+import net.i2p.util.Log;
+
+/**
+ * Base abstract class for SAM message-based sessions.
+ *
+ * @author human
+ */
+public abstract class SAMMessageSession {
+
+    private final static Log _log = new Log(SAMMessageSession.class);
+
+    private I2PSession session = null;
+
+    private SAMMessageSessionHandler handler = null;
+
+    /**
+     * Initialize a new SAM message-based session.
+     *
+     * @param dest Base64-encoded destination (private key)
+     * @param props Properties to setup the I2P session
+     */
+    protected SAMMessageSession(String dest, Properties props) throws IOException, DataFormatException, I2PSessionException {
+        ByteArrayInputStream bais;
+
+        bais = new ByteArrayInputStream(Base64.decode(dest));
+
+        initSAMMessageSession(bais, props);
+    }
+
+    /**
+     * Initialize a new SAM message-based session.
+     *
+     * @param destStream Input stream containing the destination keys
+     * @param props Properties to setup the I2P session
+     */
+    protected SAMMessageSession(InputStream destStream, Properties props) throws IOException, DataFormatException, I2PSessionException {
+        initSAMMessageSession(destStream, props);
+    }
+
+    private void initSAMMessageSession (InputStream destStream, Properties props) throws IOException, DataFormatException, I2PSessionException {
+
+        _log.debug("Initializing SAM message-based session");
+
+        handler = new SAMMessageSessionHandler(destStream, props);
+
+        Thread t = new I2PThread(handler, "SAMMessageSessionHandler");
+        t.start();
+    }
+
+    /**
+     * Get the SAM message-based session Destination.
+     *
+     * @return The SAM message-based session Destination.
+     */
+    public Destination getDestination() {
+        return session.getMyDestination();
+    }
+
+    /**
+     * Send bytes through a SAM message-based session.
+     *
+     * @param data Bytes to be sent
+     *
+     * @return True if the data was sent, false otherwise
+     */
+    public abstract boolean sendBytes(String dest, byte[] data) throws DataFormatException;
+
+    /**
+     * Actually send bytes through the SAM message-based session I2PSession
+     * (er...).
+     *
+     * @param data Bytes to be sent
+     *
+     * @return True if the data was sent, false otherwise
+     */
+    protected boolean sendBytesThroughMessageSession(String dest, byte[] data) throws DataFormatException {
+	Destination d = new Destination();
+	d.fromBase64(dest);
+
+	if (_log.shouldLog(Log.DEBUG)) {
+	    _log.debug("Sending " + data.length + " bytes to " + dest);
+	}
+
+	try {
+	    return session.sendMessage(d, data);
+	} catch (I2PSessionException e) {
+	    _log.error("I2PSessionException while sending data", e);
+	    return false;
+	}
+    }
+
+    /**
+     * Close a SAM message-based session.
+     *
+     */
+    public void close() {
+        handler.stopRunning();
+    }
+
+    /**
+     * Handle a new received message
+     */
+    protected abstract void messageReceived(byte[] msg);
+    
+    /**
+     * Do whatever is needed to shutdown the SAM session
+     */
+    protected abstract void shutDown();
+
+
+    /**
+     * Get the I2PSession object used by the SAM message-based session.
+     *
+     * @return The I2PSession of the SAM message-based session
+     */
+    protected I2PSession getI2PSession() {
+        return session;
+    }
+
+    /**
+     * SAM message-based session handler, running in its own thread
+     *
+     * @author human
+     */
+    public class SAMMessageSessionHandler implements Runnable, I2PSessionListener {
+
+        private Object runningLock = new Object();
+        private boolean stillRunning = true;
+                
+        /**
+         * Create a new SAM message-based session handler
+         *
+         * @param destStream Input stream containing the destination keys
+         * @param props Properties to setup the I2P session
+         */
+        public SAMMessageSessionHandler(InputStream destStream, Properties props) throws I2PSessionException {
+            _log.debug("Instantiating new SAM message-based session handler");
+
+            I2PClient client = I2PClientFactory.createClient();
+            session = client.createSession(destStream, props);
+
+            _log.debug("Connecting I2P session...");
+            session.connect();
+            _log.debug("I2P session connected");
+
+            session.setSessionListener(this);
+        }
+
+        /**
+         * Stop a SAM message-based session handling thread
+         *
+         */
+        public final void stopRunning() {
+            synchronized (runningLock) {
+                stillRunning = false;
+                runningLock.notify();
+            }
+        }
+
+        public void run() {
+
+            _log.debug("SAM message-based session handler running");
+
+            synchronized (runningLock) {
+                while (stillRunning) {
+                    try {
+                        runningLock.wait();
+                    } catch (InterruptedException ie) {}
+                }
+            }
+
+            _log.debug("Shutting down SAM message-based session handler");
+            
+            shutDown();
+            
+            try {
+                _log.debug("Destroying I2P session...");
+                session.destroySession();
+                _log.debug("I2P session destroyed");
+            } catch (I2PSessionException e) {
+                    _log.error("Error destroying I2P session", e);
+            }
+        }
+        
+        public void disconnected(I2PSession session) {
+            _log.debug("I2P session disconnected");
+            stopRunning();
+        }
+
+        public void errorOccurred(I2PSession session, String message,
+                                  Throwable error) {
+            _log.debug("I2P error: " + message, error);
+            stopRunning();
+        }
+            
+        public void messageAvailable(I2PSession session, int msgId, long size){
+            if (_log.shouldLog(Log.DEBUG)) {
+                _log.debug("I2P message available (id: " + msgId
+                           + "; size: " + size + ")");
+            }
+            try {
+                byte msg[] = session.receiveMessage(msgId);
+                if (_log.shouldLog(Log.DEBUG)) {
+                    _log.debug("Content of message " + msgId + ":\n"
+                               + HexDump.dump(msg));
+                }
+                
+                messageReceived(msg);
+            } catch (I2PSessionException e) {
+                _log.error("Error fetching I2P message", e);
+                stopRunning();
+            }
+        }
+        
+        public void reportAbuse(I2PSession session, int severity) {
+            _log.warn("Abuse reported (severity: " + severity + ")");
+            stopRunning();
+        }
+    }
+}
diff --git a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java
index 7ced494cc3c5814d66e18578ebd6660775ee14ec..13b4785c00cca2d028ecee577f6a78e5cbc53db0 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMRawSession.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMRawSession.java
@@ -14,16 +14,8 @@ import java.io.InputStream;
 import java.io.IOException;
 import java.util.Properties;
 
-import net.i2p.client.I2PClient;
-import net.i2p.client.I2PClientFactory;
-import net.i2p.client.I2PSession;
 import net.i2p.client.I2PSessionException;
-import net.i2p.client.I2PSessionListener;
-import net.i2p.data.Base64;
 import net.i2p.data.DataFormatException;
-import net.i2p.data.Destination;
-import net.i2p.util.HexDump;
-import net.i2p.util.I2PThread;
 import net.i2p.util.Log;
 
 /**
@@ -31,16 +23,11 @@ import net.i2p.util.Log;
  *
  * @author human
  */
-public class SAMRawSession {
+public class SAMRawSession extends SAMMessageSession {
 
     private final static Log _log = new Log(SAMRawSession.class);
 
-    private I2PSession session = null;
-
     private SAMRawReceiver recv = null;
-
-    private SAMRawSessionHandler handler = null;
-
     /**
      * Create a new SAM RAW session.
      *
@@ -49,12 +36,10 @@ public class SAMRawSession {
      * @param recv Object that will receive incoming data
      */
     public SAMRawSession(String dest, Properties props,
-			 SAMRawReceiver recv) throws IOException, DataFormatException, I2PSessionException {
-	ByteArrayInputStream bais;
-
-	bais = new ByteArrayInputStream(Base64.decode(dest));
+                         SAMRawReceiver recv) throws IOException, DataFormatException, I2PSessionException {
+        super(dest, props);
 
-	initSAMRawSession(bais, props, recv);
+        this.recv = recv;
     }
 
     /**
@@ -65,29 +50,10 @@ public class SAMRawSession {
      * @param recv Object that will receive incoming data
      */
     public SAMRawSession(InputStream destStream, Properties props,
-			 SAMRawReceiver recv) throws IOException, DataFormatException, I2PSessionException {
-	initSAMRawSession(destStream, props, recv);
-    }
-
-    private void initSAMRawSession(InputStream destStream, Properties props,
-				   SAMRawReceiver recv) throws IOException, DataFormatException, I2PSessionException {
-	this.recv = recv;
-
-	_log.debug("SAM RAW session instantiated");
-
-	handler = new SAMRawSessionHandler(destStream, props);
+                         SAMRawReceiver recv) throws IOException, DataFormatException, I2PSessionException {
+        super(destStream, props);
 
-	Thread t = new I2PThread(handler, "SAMRawSessionHandler");
-	t.start();
-    }
-
-    /**
-     * Get the SAM RAW session Destination.
-     *
-     * @return The SAM RAW session Destination.
-     */
-    public Destination getDestination() {
-	return session.getMyDestination();
+        this.recv = recv;
     }
 
     /**
@@ -98,130 +64,19 @@ public class SAMRawSession {
      * @return True if the data was sent, false otherwise
      */
     public boolean sendBytes(String dest, byte[] data) throws DataFormatException {
-	Destination d = new Destination();
-	d.fromBase64(dest);
-
-	if (_log.shouldLog(Log.DEBUG)) {
-	    _log.debug("Sending " + data.length + " bytes to " + dest);
-	}
-
-	try {
-	    return session.sendMessage(d, data);
-	} catch (I2PSessionException e) {
-	    _log.error("I2PSessionException while sending data", e);
-	    return false;
-	}
+        return sendBytesThroughMessageSession(dest, data);
     }
 
-    /**
-     * Close a SAM RAW session.
-     *
-     */
-    public void close() {
-	handler.stopRunning();
+    protected void messageReceived(byte[] msg) {
+        try {
+            recv.receiveRawBytes(msg);
+        } catch (IOException e) {
+            _log.error("Error forwarding message to receiver", e);
+            close();
+        }
     }
 
-    /**
-     * SAM RAW session handler, running in its own thread
-     *
-     * @author human
-     */
-    public class SAMRawSessionHandler implements Runnable, I2PSessionListener {
-
-	private Object runningLock = new Object();
-	private boolean stillRunning = true;
-		
-	/**
-	 * Create a new SAM RAW session handler
-	 *
-	 * @param destStream Input stream containing the destination keys
-	 * @param props Properties to setup the I2P session
-	 */
-	public SAMRawSessionHandler(InputStream destStream, Properties props) throws I2PSessionException {
-	    _log.debug("Instantiating new SAM RAW session handler");
-
-	    I2PClient client = I2PClientFactory.createClient();
-	    session = client.createSession(destStream, props);
-
-	    _log.debug("Connecting I2P session...");
-	    session.connect();
-	    _log.debug("I2P session connected");
-
-	    session.setSessionListener(this);
-	}
-
-	/**
-	 * Stop a SAM RAW session handling thread
-	 *
-	 */
-	public void stopRunning() {
-	    synchronized (runningLock) {
-		stillRunning = false;
-		runningLock.notify();
-	    }
-	}
-
-	public void run() {
-
-	    _log.debug("SAM RAW session handler running");
-
-	    synchronized (runningLock) {
-		while (stillRunning) {
-		    try {
-			runningLock.wait();
-		    } catch (InterruptedException ie) {}
-		}
-	    }
-
-	    _log.debug("Shutting down SAM RAW session handler");
-	    
-	    recv.stopRawReceiving();
-	    
-	    try {
-		_log.debug("Destroying I2P session...");
-		session.destroySession();
-		_log.debug("I2P session destroyed");
-	    } catch (I2PSessionException e) {
-		    _log.error("Error destroying I2P session", e);
-	    }
-	}
-	
-	public void disconnected(I2PSession session) {
-	    _log.debug("I2P session disconnected");
-	    stopRunning();
-	}
-
-	public void errorOccurred(I2PSession session, String message,
-				  Throwable error) {
-	    _log.debug("I2P error: " + message, error);
-	    stopRunning();
-	}
-	
-	
-	public void messageAvailable(I2PSession session, int msgId, long size){
-	    _log.debug("I2P message available (id: " + msgId
-		       + "; size: " + size + ")");
-	    try {
-		byte msg[] = session.receiveMessage(msgId);
-		if (_log.shouldLog(Log.DEBUG)) {
-		    _log.debug("Content of message " + msgId + ":\n"
-			       + HexDump.dump(msg));
-		}
-		
-		recv.receiveRawBytes(msg);
-	    } catch (IOException e) {
-		_log.error("Error forwarding message to receiver", e);
-		stopRunning();
-	    } catch (I2PSessionException e) {
-		_log.error("Error fetching I2P message", e);
-		stopRunning();
-	    }
-	    
-	}
-	
-	public void reportAbuse(I2PSession session, int severity) {
-	    _log.warn("Abuse reported (severity: " + severity + ")");
-	    stopRunning();
-	}
+    protected void shutDown() {
+        recv.stopRawReceiving();
     }
 }
diff --git a/apps/sam/java/src/net/i2p/sam/SAMUtils.java b/apps/sam/java/src/net/i2p/sam/SAMUtils.java
index 778c0d06f08430e04a4df8fcb0e115d8747312aa..f4ef4ff84f6f099da1c0b64a84ddc835744735a5 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMUtils.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMUtils.java
@@ -40,16 +40,16 @@ public class SAMUtils {
      * @param pub Stream used to write the public key (may be null)
      */
     public static void genRandomKey(OutputStream priv, OutputStream pub) {
-	_log.debug("Generating random keys...");
-	try {
-	    I2PClient c = I2PClientFactory.createClient();
-	    Destination d = c.createDestination(priv);
-	    priv.flush();
-
-	    if (pub != null) {
-		d.writeBytes(pub);
-		pub.flush();
-	    }
+        _log.debug("Generating random keys...");
+        try {
+            I2PClient c = I2PClientFactory.createClient();
+            Destination d = c.createDestination(priv);
+            priv.flush();
+
+            if (pub != null) {
+                d.writeBytes(pub);
+                pub.flush();
+            }
         } catch (I2PException e) {
             e.printStackTrace();
         } catch (IOException e) {
@@ -57,28 +57,6 @@ public class SAMUtils {
         }
     }
 
-    /**
-     * Get the Base64 representation of a Destination public key
-     *
-     * @param d A Destination
-     *
-     * @return A String representing the Destination public key
-     */
-    public static String getBase64DestinationPubKey(Destination d) {
-	ByteArrayOutputStream baos = new ByteArrayOutputStream();
-
-	try {
-	    d.writeBytes(baos);
-	    return Base64.encode(baos.toByteArray());
-	} catch (IOException e) {
-	    _log.error("getDestinationPubKey(): caught IOException", e);
-	    return null;
-	} catch (DataFormatException e) {
-	    _log.error("getDestinationPubKey(): caught DataFormatException",e);
-	    return null;
-	}
-    }
-
     /**
      * Check whether a base64-encoded dest is valid
      *
@@ -87,13 +65,13 @@ public class SAMUtils {
      * @return True if the destination is valid, false otherwise
      */
     public static boolean checkDestination(String dest) {
-	try {
-	    Destination d = new Destination();
-	    d.fromBase64(dest);
+        try {
+            Destination d = new Destination();
+            d.fromBase64(dest);
 
-	    return true;
+            return true;
         } catch (DataFormatException e) {
-	    return false;
+            return false;
         }
     }
 
@@ -106,22 +84,22 @@ public class SAMUtils {
      * @return the Destination for the specified hostname, or null if not found
      */
     public static Destination lookupHost(String name, OutputStream pubKey) {
-	NamingService ns = NamingService.getInstance();
-	Destination dest = ns.lookup(name);
-
-	if ((pubKey != null) && (dest != null)) {
-	    try {
-		dest.writeBytes(pubKey);
-	    } catch (IOException e) {
-		e.printStackTrace();
-		return null;
-	    } catch (DataFormatException e) {
-		e.printStackTrace();
-		return null;
-	    }
-	}
-
-	return dest;
+        NamingService ns = NamingService.getInstance();
+        Destination dest = ns.lookup(name);
+
+        if ((pubKey != null) && (dest != null)) {
+            try {
+                dest.writeBytes(pubKey);
+            } catch (IOException e) {
+                e.printStackTrace();
+                return null;
+            } catch (DataFormatException e) {
+                e.printStackTrace();
+                return null;
+            }
+        }
+
+        return dest;
     }
     
     /**
@@ -132,55 +110,55 @@ public class SAMUtils {
      * @return Properties with the parsed SAM params, or null if none is found
      */
     public static Properties parseParams(StringTokenizer tok) {
-	int pos, nprops = 0, ntoks = tok.countTokens();
-	String token, param, value;
-	Properties props = new Properties();
-	
-	for (int i = 0; i < ntoks; ++i) {
-	    token = tok.nextToken();
-
-	    pos = token.indexOf("=");
-	    if (pos == -1) {
-		_log.debug("Error in params format");
-		return null;
-	    }
-	    param = token.substring(0, pos);
-	    value = token.substring(pos + 1);
-
-	    props.setProperty(param, value);
-	    nprops += 1;
-	}
-
-	if (_log.shouldLog(Log.DEBUG)) {
-	    _log.debug("Parsed properties: " + dumpProperties(props));
-	}
-
-	if (nprops != 0) {
-	    return props;
-	} else {
-	    return null;
-	}
+        int pos, nprops = 0, ntoks = tok.countTokens();
+        String token, param, value;
+        Properties props = new Properties();
+        
+        for (int i = 0; i < ntoks; ++i) {
+            token = tok.nextToken();
+
+            pos = token.indexOf("=");
+            if (pos == -1) {
+                _log.debug("Error in params format");
+                return null;
+            }
+            param = token.substring(0, pos);
+            value = token.substring(pos + 1);
+
+            props.setProperty(param, value);
+            nprops += 1;
+        }
+
+        if (_log.shouldLog(Log.DEBUG)) {
+            _log.debug("Parsed properties: " + dumpProperties(props));
+        }
+
+        if (nprops != 0) {
+            return props;
+        } else {
+            return null;
+        }
     }
 
     /* Dump a Properties object in an human-readable form */
     private static String dumpProperties(Properties props) {
-	Enumeration enum = props.propertyNames();
-	String msg = "";
-	String key, val;
-	boolean firstIter = true;
-	
-	while (enum.hasMoreElements()) {
-	    key = (String)enum.nextElement();
-	    val = props.getProperty(key);
-	    
-	    if (!firstIter) {
-		msg += ";";
-	    } else {
-		firstIter = false;
-	    }
-	    msg += " \"" + key + "\" -> \"" + val + "\"";
-	}
-	
-	return msg;
+        Enumeration enum = props.propertyNames();
+        String msg = "";
+        String key, val;
+        boolean firstIter = true;
+        
+        while (enum.hasMoreElements()) {
+            key = (String)enum.nextElement();
+            val = props.getProperty(key);
+            
+            if (!firstIter) {
+                msg += ";";
+            } else {
+                firstIter = false;
+            }
+            msg += " \"" + key + "\" -> \"" + val + "\"";
+        }
+        
+        return msg;
     }
 }
diff --git a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java
index 1d930d1499f7298694e0d009ae11c8d8c17fb271..cc45e7b667f58153ba850e7d199fd5e2b2b54736 100644
--- a/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java
+++ b/apps/sam/java/src/net/i2p/sam/SAMv1Handler.java
@@ -33,14 +33,14 @@ import net.i2p.util.Log;
  *
  * @author human
  */
-public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStreamReceiver {
+public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMDatagramReceiver, SAMStreamReceiver {
     
     private final static Log _log = new Log(SAMv1Handler.class);
 
     private final static int IN_BUFSIZE = 2048;
 
     private SAMRawSession rawSession = null;
-    private SAMRawSession datagramSession = null;
+    private SAMDatagramSession datagramSession = null;
     private SAMStreamSession streamSession = null;
 
     /**
@@ -49,20 +49,16 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
      * stripped) from the socket input stream.
      *
      * @param s Socket attached to a SAM client
+     * @param verMajor SAM major version to manage (should be 1)
+     * @param verMinor SAM minor version to manage
      */
-    public SAMv1Handler(Socket s, int verMajor, int verMinor) throws SAMException{
+    public SAMv1Handler(Socket s, int verMajor, int verMinor) throws SAMException, IOException {
+        super(s, verMajor, verMinor);
         _log.debug("SAM version 1 handler instantiated");
 
-        this.verMajor = verMajor;
-        this.verMinor = verMinor;
-
         if ((this.verMajor != 1) || (this.verMinor != 0)) {
             throw new SAMException("BUG! Wrong protocol version!");
         }
-
-        this.socket = s;
-        this.verMajor = verMajor;
-        this.verMinor = verMinor;
     }
 
     public void handle() {
@@ -76,7 +72,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
         _log.debug("SAM handling started");
 
         try {
-            InputStream in = socket.getInputStream();
+            InputStream in = getClientSocketInputStream();
             int b = -1;
 
             while (true) {
@@ -118,6 +114,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
 
                 if (domain.equals("STREAM")) {
                     canContinue = execStreamMessage(opcode, props);
+                } else if (domain.equals("DATAGRAM")) {
+                    canContinue = execDatagramMessage(opcode, props);
                 } else if (domain.equals("RAW")) {
                     canContinue = execRawMessage(opcode, props);
                 } else if (domain.equals("SESSION")) {
@@ -147,7 +145,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
         } finally {
             _log.debug("Stopping handler");
             try {
-                this.socket.close();
+                closeClientSocket();
             } catch (IOException e) {
                 _log.error("Error closing socket: " + e.getMessage());
             }
@@ -204,6 +202,8 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
                 
                 if (style.equals("RAW")) {
                     rawSession = new SAMRawSession(dest, props, this);
+                } else if (style.equals("DATAGRAM")) {
+                    datagramSession = new SAMDatagramSession(dest, props,this);
                 } else if (style.equals("STREAM")) {
                     String dir = props.getProperty("DIRECTION");
                     if (dir == null) {
@@ -305,7 +305,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
             
             return writeString("NAMING REPLY RESULT=OK NAME=" + name
                                + " VALUE="
-                               + SAMUtils.getBase64DestinationPubKey(dest)
+                               + dest.toBase64()
                                + "\n");
         } else {
             _log.debug("Unrecognized NAMING message opcode: \""
@@ -314,10 +314,82 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
         }
     }
 
+
+    /* Parse and execute a DATAGRAM message */
+    private boolean execDatagramMessage(String opcode, Properties props) {
+        if (datagramSession == null) {
+            _log.error("DATAGRAM message received, but no DATAGRAM session exists");
+            return false;
+        }
+
+        if (opcode.equals("SEND")) {
+            if (props == null) {
+                _log.debug("No parameters specified in DATAGRAM SEND message");
+                return false;
+            }
+            
+            String dest = props.getProperty("DESTINATION");
+            if (dest == null) {
+                _log.debug("Destination not specified in DATAGRAM SEND message");
+                return false;
+            }
+
+            int size;
+            {
+                String strsize = props.getProperty("SIZE");
+                if (strsize == null) {
+                    _log.debug("Size not specified in DATAGRAM SEND message");
+                    return false;
+                }
+                try {
+                    size = Integer.parseInt(strsize);
+                } catch (NumberFormatException e) {
+                    _log.debug("Invalid DATAGRAM SEND size specified: " + strsize);
+                    return false;
+                }
+                if (!checkDatagramSize(size)) {
+                    _log.debug("Specified size (" + size
+                               + ") is out of protocol limits");
+                    return false;
+                }
+            }
+
+            try {
+                DataInputStream in = new DataInputStream(getClientSocketInputStream());
+                byte[] data = new byte[size];
+
+                in.readFully(data);
+
+                if (!datagramSession.sendBytes(dest, data)) {
+                    _log.error("DATAGRAM SEND failed");
+                    return false;
+                }
+
+                return true;
+            } catch (EOFException e) {
+                _log.debug("Too few bytes with DATAGRAM SEND message (expected: "
+                           + size);
+                return false;
+            } catch (IOException e) {
+                _log.debug("Caught IOException while parsing DATAGRAM SEND message",
+                           e);
+                return false;
+            } catch (DataFormatException e) {
+                _log.debug("Invalid key specified with DATAGRAM SEND message",
+                           e);
+                return false;
+            }
+        } else {
+            _log.debug("Unrecognized DATAGRAM message opcode: \""
+                       + opcode + "\"");
+            return false;
+        }
+    }
+
     /* Parse and execute a RAW message */
     private boolean execRawMessage(String opcode, Properties props) {
         if (rawSession == null) {
-            _log.debug("RAW message received, but no RAW session exists");
+            _log.error("RAW message received, but no RAW session exists");
             return false;
         }
 
@@ -354,7 +426,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
             }
 
             try {
-                DataInputStream in = new DataInputStream(socket.getInputStream());
+                DataInputStream in = new DataInputStream(getClientSocketInputStream());
                 byte[] data = new byte[size];
 
                 in.readFully(data);
@@ -388,7 +460,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
     /* Parse and execute a STREAM message */
     private boolean execStreamMessage(String opcode, Properties props) {
         if (streamSession == null) {
-            _log.debug("STREAM message received, but no STREAM session exists");
+            _log.error("STREAM message received, but no STREAM session exists");
             return false;
         }
 
@@ -434,7 +506,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
             }
 
             try {
-                DataInputStream in = new DataInputStream(socket.getInputStream());
+                DataInputStream in = new DataInputStream(getClientSocketInputStream());
                 byte[] data = new byte[size];
 
                 in.readFully(data);
@@ -535,16 +607,15 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
         }
     }
 
-    public String toString() {
-        return "SAM v1 handler (client: "
-            + this.socket.getInetAddress().toString() + ":"
-            + this.socket.getPort() + ")";
-    }
-
     /* Check whether a size is inside the limits allowed by this protocol */
     private boolean checkSize(int size) {
         return ((size >= 1) && (size <= 32768));
     }
+
+    /* Check whether a size is inside the limits allowed by this protocol */
+    private boolean checkDatagramSize(int size) {
+        return ((size >= 1) && (size <= 31744));
+    }
     
     // SAMRawReceiver implementation
     public void receiveRawBytes(byte data[]) throws IOException {
@@ -571,7 +642,39 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
         }
 
         try {
-            this.socket.close();
+            closeClientSocket();
+        } catch (IOException e) {
+            _log.error("Error closing socket: " + e.getMessage());
+        }
+    }
+
+    // SAMDatagramReceiver implementation
+    public void receiveDatagramBytes(Destination sender, byte data[]) throws IOException {
+        if (datagramSession == null) {
+            _log.error("BUG! Received datagram bytes, but session is null!");
+            throw new NullPointerException("BUG! DATAGRAM session is null!");
+        }
+
+        ByteArrayOutputStream msg = new ByteArrayOutputStream();
+
+        msg.write(("DATAGRAM RECEIVED DESTINATION=" + sender.toBase64()
+                   + " SIZE=" + data.length
+                   + "\n").getBytes("ISO-8859-1"));
+        msg.write(data);
+
+        writeBytes(msg.toByteArray());
+    }
+
+    public void stopDatagramReceiving() {
+        _log.debug("stopDatagramReceiving() invoked");
+
+        if (datagramSession == null) {
+            _log.error("BUG! Got datagram receiving stop, but session is null!");
+            throw new NullPointerException("BUG! DATAGRAM session is null!");
+        }
+
+        try {
+            closeClientSocket();
         } catch (IOException e) {
             _log.error("Error closing socket: " + e.getMessage());
         }
@@ -585,7 +688,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
         }
 
         if (!writeString("STREAM CONNECTED DESTINATION="
-                         + SAMUtils.getBase64DestinationPubKey(d)
+                         + d.toBase64()
                          + " ID=" + id + "\n")) {
             throw new IOException("Error notifying connection to SAM client");
         }
@@ -629,7 +732,7 @@ public class SAMv1Handler extends SAMHandler implements SAMRawReceiver, SAMStrea
         }
 
         try {
-            this.socket.close();
+            closeClientSocket();
         } catch (IOException e) {
             _log.error("Error closing socket: " + e.getMessage());
         }