diff --git a/build/build.xml b/build/build.xml
index 5c56b09..2c580aa 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -56,6 +56,7 @@
     <property name="javadoc.dest.dir" value="${basedir}/target/javadoc" />
     <property name="merge.lib.dir" value="${basedir}/build/merge" />
     <property name="test.dir" value="${basedir}/test" />
+    <property name="test-mdns.dir" value="${basedir}/test-mdns" />
     <property name="resources.dir" value="${basedir}/build/resources" />
 	<property name="jingle.extension.merge.lib.dir" value="${basedir}/jingle/extension/build/merge" />	
 	<property name="jingle.extension.lib.dir" value="${basedir}/jingle/extension/build/lib" />	
@@ -106,6 +107,9 @@
                 <fileset dir="${merge.lib.dir}">
                     <include name="*.jar"/>
                 </fileset>
+                <fileset dir="${basedir}/build">
+                    <include name="jmdns.jar"/>
+                </fileset>
             </classpath>
         </javac>		
 		<mkdir dir="${compile.dir}/jingle/extension" />
@@ -158,6 +162,35 @@
         </javac>
     </target>
 
+    <!-- compile-test-mdns -->
+    <!-- ======================================================================================= -->
+    <target name="compile-test-mdns" description="Compiles all source to ${compile.dir}.">
+        <!-- make target dir -->
+        <mkdir dir="${compile.test.dir}" />
+        <javac
+            destdir="${compile.test.dir}"
+            includeAntRuntime="no"
+            debug="on"
+            source="1.5"
+            target="1.5"
+        >
+            <src path="${test-mdns.dir}" />
+            <classpath>
+                <fileset dir="${merge.lib.dir}">
+                    <include name="*.jar"/>
+                </fileset>
+                <fileset dir="${basedir}/build">
+                    <include name="jmdns.jar"/>
+                </fileset>
+                <fileset dir="${basedir}/build">
+                    <include name="junit.jar"/>
+                    <include name="xmlunit.jar"/>
+                </fileset>
+                <pathelement location="${compile.dir}" />
+            </classpath>
+        </javac>
+    </target>
+
     <!-- jar -->
     <!-- ======================================================================================= -->
     <target name="jar" depends="compile" unless="jar.uptodate" description="Produces smack.jar">
@@ -296,7 +329,7 @@
                 <fileset dir="${merge.lib.dir}">
                     <include name="*.jar"/>
                 </fileset>
-                <fileset dir="${basedir}">
+		<fileset dir="${basedir}/target">
                     <include name="smack-test.jar"/>
                 </fileset>
                 <pathelement location="${compile.dir}" />
diff --git a/build/resources/META-INF/smack-config.xml b/build/resources/META-INF/smack-config.xml
index 5b11d4a..d14d6a5 100644
--- a/build/resources/META-INF/smack-config.xml
+++ b/build/resources/META-INF/smack-config.xml
@@ -4,6 +4,7 @@
 
     <!-- Classes that will be loaded when Smack starts -->
     <startupClasses>
+        <className>org.jivesoftware.smack.XMPPLLConnection</className>        
         <className>org.jivesoftware.smackx.ServiceDiscoveryManager</className>
         <className>org.jivesoftware.smack.PrivacyListManager</className>        
         <className>org.jivesoftware.smackx.XHTMLManager</className>
@@ -20,4 +21,4 @@
     <!-- Keep-alive interval in milleseconds -->
     <keepAliveInterval>30000</keepAliveInterval>
 
-</smack>
\ No newline at end of file
+</smack>
diff --git a/source/org/jivesoftware/smack/AbstractConnection.java b/source/org/jivesoftware/smack/AbstractConnection.java
new file mode 100644
index 0000000..af60c72
--- /dev/null
+++ b/source/org/jivesoftware/smack/AbstractConnection.java
@@ -0,0 +1,325 @@
+/**
+ * Copyright 2008 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.debugger.SmackDebugger;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.packet.XMPPError;
+import org.jivesoftware.smack.util.StringUtils;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.KeyStore;
+import java.security.Provider;
+import java.security.Security;
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public abstract class AbstractConnection {
+
+    /**
+     * Value that indicates whether debugging is enabled. When enabled, a debug
+     * window will apear for each new connection that will contain the following
+     * information:<ul>
+     * <li> Client Traffic -- raw XML traffic generated by Smack and sent to the server.
+     * <li> Server Traffic -- raw XML traffic sent by the server to the client.
+     * <li> Interpreted Packets -- shows XML packets from the server as parsed by Smack.
+     * </ul>
+     * <p/>
+     * Debugging can be enabled by setting this field to true, or by setting the Java system
+     * property <tt>smack.debugEnabled</tt> to true. The system property can be set on the
+     * command line such as "java SomeApp -Dsmack.debugEnabled=true".
+     */
+    public static boolean DEBUG_ENABLED = false;
+
+    // Counter to uniquely identify connections that are created. This is distinct from the
+    // connection ID, which is a value sent by the server once a connection is made.
+    private static AtomicInteger connectionCounter = new AtomicInteger(0);
+
+    static {
+        // Use try block since we may not have permission to get a system
+        // property (for example, when an applet).
+        try {
+            DEBUG_ENABLED = Boolean.getBoolean("smack.debugEnabled");
+        }
+        catch (Exception e) {
+            // Ignore.
+        }
+        // Ensure the SmackConfiguration class is loaded by calling a method in it.
+        //
+        // FIXME loading the class here makes the updated ServiceDiscoveryManager load
+        // before XMPPLLConnection, causing a NullPointerException when adding
+        // link-local connection creation listener.
+        //SmackConfiguration.getVersion();
+    }
+
+    protected SmackDebugger debugger = null;
+
+    /**
+     * IP address or host name of the server. This information is only used when
+     * creating new socket connections to the server. If this information is not
+     * configured then it will be assumed that the host name matches the service name.
+     */
+    protected String host;
+    protected int port;
+    protected Socket socket;
+
+    protected boolean connected = false;
+
+    // Stream reader/writer
+    protected Writer writer;
+    protected Reader reader;
+
+    // Packet reader/writer
+    protected PacketWriter packetWriter;
+    protected AbstractPacketReader packetReader;
+
+    /**
+     * Service name of the XMPP connection. 
+     * In the normal c2s case servers usually use the same service name as the name
+     * of the server. However, there are some servers like google where host would be
+     * talk.google.com and the serviceName would be gmail.com.
+     * In a Link-local connection the service name is the service name of the presence.
+     */
+    protected String serviceName;
+
+    int connectionCounterValue = connectionCounter.getAndIncrement();
+    String connectionID = null;
+
+    /**
+     * Returns the connection ID for this connection, which is the value set by the server
+     * when opening a XMPP stream. If the server does not set a connection ID, this value
+     * will be null. This value will be <tt>null</tt> if not connected to the server.
+     *
+     * @return the ID of this connection returned from the XMPP server or <tt>null</tt> if
+     *      not connected to the server.
+     */
+    public String getConnectionID() {
+        if (!isConnected()) {
+            return null;
+        }
+        return connectionID;
+    }
+
+    /**
+     * Returns the name of the service provided by the XMPP server for this connection. After
+     * authenticating with the server the returned value may be different.
+     *
+     * @return the name of the service provided by the XMPP server.
+     */
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    /**
+     * Returns the host name of the server where the XMPP server is running. This would be the
+     * IP address of the server or a name that may be resolved by a DNS server.
+     *
+     * @return the host name of the server where the XMPP server is running.
+     */
+    public String getHost() {
+        return host;
+    }
+
+    /**
+     * Returns the port number of the XMPP server for this connection. The default port
+     * for normal connections is 5222. The default port for SSL connections is 5223.
+     *
+     * @return the port number of the XMPP server.
+     */
+    public int getPort() {
+        return port;
+    }
+
+    /**
+     * Returns the full XMPP address of the user that of the connection.
+     * @see XMPPConnection#getUser
+     *
+     * @return the full XMPP address of the user logged in.
+     */
+    public abstract String getUser();
+
+    /**
+     * Returns true if currently connected to the XMPP server.
+     *
+     * @return true if connected.
+     */
+    public boolean isConnected() {
+        return connected;
+    }
+
+    /**
+     * Closes the connection by closing the stream.
+     */
+    protected abstract void shutdown();
+
+    /**
+     * Closes the connection.
+     */
+    protected abstract void disconnect();
+
+    /**
+     * Sends the specified packet to the server.
+     *
+     * @param packet the packet to send.
+     */
+    public void sendPacket(Packet packet) {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected to server.");
+        }
+        if (packet == null) {
+            throw new NullPointerException("Packet is null.");
+        }
+        packetWriter.sendPacket(packet);
+    }
+
+    /**
+     * Registers a packet listener with this connection. A packet filter determines
+     * which packets will be delivered to the listener. If the same packet listener
+     * is added again with a different filter, only the new filter will be used.
+     *
+     * @param packetListener the packet listener to notify of new packets.
+     * @param packetFilter   the packet filter to use.
+     */
+    public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected to server.");
+        }
+        packetReader.addPacketListener(packetListener, packetFilter);
+    }
+
+    /**
+     * Removes a packet listener from this connection.
+     *
+     * @param packetListener the packet listener to remove.
+     */
+    public void removePacketListener(PacketListener packetListener) {
+    	if (packetReader != null) {
+    		packetReader.removePacketListener(packetListener);
+    	}
+    }
+
+    /**
+     * Registers a packet listener with this connection. The listener will be
+     * notified of every packet that this connection sends. A packet filter determines
+     * which packets will be delivered to the listener. Note that the thread
+     * that writes packets will be used to invoke the listeners. Therefore, each
+     * packet listener should complete all operations quickly or use a different
+     * thread for processing.
+     *
+     * @param packetListener the packet listener to notify of sent packets.
+     * @param packetFilter   the packet filter to use.
+     */
+    public void addPacketWriterListener(PacketListener packetListener, PacketFilter packetFilter) {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected to server.");
+        }
+        packetWriter.addPacketListener(packetListener, packetFilter);
+    }
+
+    /**
+     * Removes a packet listener from this connection.
+     *
+     * @param packetListener the packet listener to remove.
+     */
+    public void removePacketWriterListener(PacketListener packetListener) {
+    	if (packetWriter != null) {
+    		packetWriter.removePacketListener(packetListener);
+    	}
+    }
+
+    /**
+     * Registers a packet interceptor with this connection. The interceptor will be
+     * invoked every time a packet is about to be sent by this connection. Interceptors
+     * may modify the packet to be sent. A packet filter determines which packets
+     * will be delivered to the interceptor.
+     *
+     * @param packetInterceptor the packet interceptor to notify of packets about to be sent.
+     * @param packetFilter      the packet filter to use.
+     */
+    public void addPacketWriterInterceptor(PacketInterceptor packetInterceptor,
+            PacketFilter packetFilter) {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected to server.");
+        }
+        packetWriter.addPacketInterceptor(packetInterceptor, packetFilter);
+    }
+
+    /**
+     * Removes a packet interceptor.
+     *
+     * @param packetInterceptor the packet interceptor to remove.
+     */
+    public void removePacketWriterInterceptor(PacketInterceptor packetInterceptor) {
+        packetWriter.removePacketInterceptor(packetInterceptor);
+    }
+
+    /**
+     * Creates a new packet collector for this connection. A packet filter determines
+     * which packets will be accumulated by the collector.
+     *
+     * @param packetFilter the packet filter to use.
+     * @return a new packet collector.
+     */
+    public PacketCollector createPacketCollector(PacketFilter packetFilter) {
+        return packetReader.createPacketCollector(packetFilter);
+    }
+
+    /**
+     * Adds a connection listener to this connection that will be notified when
+     * the connection closes or fails. The connection needs to already be connected
+     * or otherwise an IllegalStateException will be thrown.
+     *
+     * @param connectionListener a connection listener.
+     */
+    public void addConnectionListener(ConnectionListener connectionListener) {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected to server.");
+        }
+        if (connectionListener == null) {
+            return;
+        }
+        if (!packetReader.connectionListeners.contains(connectionListener)) {
+            packetReader.connectionListeners.add(connectionListener);
+        }
+    }
+
+    /**
+     * Removes a connection listener from this connection.
+     *
+     * @param connectionListener a connection listener.
+     */
+    public void removeConnectionListener(ConnectionListener connectionListener) {
+    	if (packetReader != null) {
+    		packetReader.connectionListeners.remove(connectionListener);
+    	}
+    }
+}
diff --git a/source/org/jivesoftware/smack/AbstractPacketReader.java b/source/org/jivesoftware/smack/AbstractPacketReader.java
new file mode 100644
index 0000000..4019578
--- /dev/null
+++ b/source/org/jivesoftware/smack/AbstractPacketReader.java
@@ -0,0 +1,358 @@
+/**
+ * $RCSfile$
+ * $Revision: 10856 $
+ * $Date: 2008-10-31 11:51:12 +0800 (Fri, 31 Oct 2008) $
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.provider.ProviderManager;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.xmlpull.mxp1.MXParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.*;
+
+/**
+ * Listens for XML traffic from the XMPP server and parses it into packet objects.
+ * The packet reader also manages all packet listeners and collectors.<p>
+ *
+ * @see PacketCollector
+ * @see PacketListener
+ * @author Matt Tucker
+ */
+abstract class AbstractPacketReader {
+    protected Thread readerThread;
+    private ExecutorService listenerExecutor;
+
+    private AbstractConnection connection;
+
+    protected XmlPullParser parser;
+    protected boolean done;
+    private Collection<PacketCollector> collectors = new ConcurrentLinkedQueue<PacketCollector>();
+    protected final Map<PacketListener, ListenerWrapper> listeners =
+            new ConcurrentHashMap<PacketListener, ListenerWrapper>();
+    protected final Collection<ConnectionListener> connectionListeners =
+            new CopyOnWriteArrayList<ConnectionListener>();
+
+    protected String connectionID = null;
+    private Semaphore connectionSemaphore;
+
+    protected AbstractPacketReader(final AbstractConnection connection) {
+        this.connection = connection;
+    }
+
+    /**
+     * Initializes the reader in order to be used. The reader is initialized during the
+     * first connection and when reconnecting due to an abruptly disconnection.
+     */
+    protected void init() {
+        done = false;
+        connectionID = null;
+
+        readerThread = new Thread() {
+            public void run() {
+                parsePackets(this);
+            }
+        };
+        readerThread.setName("Smack Packet Reader (" + connection.connectionCounterValue + ")");
+        readerThread.setDaemon(true);
+
+        // Create an executor to deliver incoming packets to listeners. We'll use a single
+        // thread with an unbounded queue.
+        listenerExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
+
+            public Thread newThread(Runnable runnable) {
+                Thread thread = new Thread(runnable,
+                        "Smack Listener Processor (" + connection.connectionCounterValue + ")");
+                thread.setDaemon(true);
+                return thread;
+            }
+        });
+
+        resetParser();
+    }
+
+    /**
+     * Creates a new packet collector for this reader. A packet filter determines
+     * which packets will be accumulated by the collector.
+     *
+     * @param packetFilter the packet filter to use.
+     * @return a new packet collector.
+     */
+    public PacketCollector createPacketCollector(PacketFilter packetFilter) {
+        PacketCollector collector = new PacketCollector(this, packetFilter);
+        collectors.add(collector);
+        // Add the collector to the list of active collector.
+        return collector;
+    }
+
+    protected void cancelPacketCollector(PacketCollector packetCollector) {
+        collectors.remove(packetCollector);
+    }
+
+    /**
+     * Registers a packet listener with this reader. A packet filter determines
+     * which packets will be delivered to the listener.
+     *
+     * @param packetListener the packet listener to notify of new packets.
+     * @param packetFilter the packet filter to use.
+     */
+    public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) {
+        ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter);
+        listeners.put(packetListener, wrapper);
+    }
+
+    /**
+     * Removes a packet listener.
+     *
+     * @param packetListener the packet listener to remove.
+     */
+    public void removePacketListener(PacketListener packetListener) {
+        listeners.remove(packetListener);
+    }
+
+    /**
+     * Starts the packet reader thread and returns once a connection to the server
+     * has been established. A connection will be attempted for a maximum of five
+     * seconds. An XMPPException will be thrown if the connection fails.
+     *
+     * @throws XMPPException if the server fails to send an opening stream back
+     *      for more than five seconds.
+     */
+    public void startup() throws XMPPException {
+        connectionSemaphore = new Semaphore(1);
+
+        readerThread.start();
+        // Wait for stream tag before returing. We'll wait a couple of seconds before
+        // giving up and throwing an error.
+        try {
+            connectionSemaphore.acquire();
+
+            // A waiting thread may be woken up before the wait time or a notify
+            // (although this is a rare thing). Therefore, we continue waiting
+            // until either a connectionID has been set (and hence a notify was
+            // made) or the total wait time has elapsed.
+            int waitTime = SmackConfiguration.getPacketReplyTimeout();
+            connectionSemaphore.tryAcquire(3 * waitTime, TimeUnit.MILLISECONDS);
+        }
+        catch (InterruptedException ie) {
+            // Ignore.
+        }
+        if (connectionID == null) {
+            throw new XMPPException("Connection failed. No response from server.");
+        }
+        else {
+            connection.connectionID = connectionID;
+        }
+    }
+
+    /**
+     * Shuts the packet reader down.
+     */
+    public void shutdown() {
+        // Notify connection listeners of the connection closing if done hasn't already been set.
+        if (!done) {
+            for (ConnectionListener listener : connectionListeners) {
+                try {
+                    listener.connectionClosed();
+                }
+                catch (Exception e) {
+                    // Cath and print any exception so we can recover
+                    // from a faulty listener and finish the shutdown process
+                    e.printStackTrace();
+                }
+            }
+        }
+        done = true;
+
+        // Shut down the listener executor.
+        listenerExecutor.shutdown();
+    }
+
+    /**
+     * Cleans up all resources used by the packet reader.
+     */
+    void cleanup() {
+        connectionListeners.clear();
+        listeners.clear();
+        collectors.clear();
+    }
+
+    /**
+     * Parse top-level packets in order to process them further.
+     *
+     * @param thread the thread that is being used by the reader to parse incoming packets.
+     */
+    abstract protected void parsePackets(Thread thread);
+
+
+    /**
+     * Sends out a notification that there was an error with the connection
+     * and closes the connection.
+     *
+     * @param e the exception that causes the connection close event.
+     */
+    void notifyConnectionError(Exception e) {
+        done = true;
+        // Closes the connection temporary. A reconnection is possible
+        connection.shutdown();
+        // Print the stack trace to help catch the problem
+        e.printStackTrace();
+        // Notify connection listeners of the error.
+        for (ConnectionListener listener : connectionListeners) {
+            try {
+                listener.connectionClosedOnError(e);
+            }
+            catch (Exception e2) {
+                // Cath and print any exception so we can recover
+                // from a faulty listener
+                e2.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Sends a notification indicating that the connection was reconnected successfully.
+     */
+    protected void notifyReconnection() {
+        // Notify connection listeners of the reconnection.
+        for (ConnectionListener listener : connectionListeners) {
+            try {
+                listener.reconnectionSuccessful();
+            }
+            catch (Exception e) {
+                // Cath and print any exception so we can recover
+                // from a faulty listener
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * Resets the parser using the latest connection's reader. Reseting the parser is necessary
+     * when the plain connection has been secured or when a new opening stream element is going
+     * to be sent by the server.
+     */
+    protected void resetParser() {
+        try {
+            parser = new MXParser();
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+            parser.setInput(connection.reader);
+        }
+        catch (XmlPullParserException xppe) {
+            xppe.printStackTrace();
+        }
+    }
+
+    /**
+     * Releases the connection ID lock so that the thread that was waiting can resume. The
+     * lock will be released when one of the following three conditions is met:<p>
+     *
+     * 1) An opening stream was sent from a non XMPP 1.0 compliant server
+     * 2) Stream features were received from an XMPP 1.0 compliant server that does not support TLS
+     * 3) TLS negotiation was successful
+     *
+     */
+    protected void releaseConnectionIDLock() {
+        connectionSemaphore.release();
+    }
+
+    /**
+     * Processes a packet after it's been fully parsed by looping through the installed
+     * packet collectors and listeners and letting them examine the packet to see if
+     * they are a match with the filter.
+     *
+     * @param packet the packet to process.
+     */
+    protected void processPacket(Packet packet) {
+        if (packet == null) {
+            return;
+        }
+
+        // Loop through all collectors and notify the appropriate ones.
+        for (PacketCollector collector: collectors) {
+            collector.processPacket(packet);
+        }
+
+        // Deliver the incoming packet to listeners.
+        listenerExecutor.submit(new ListenerNotification(packet));
+    }
+
+    protected StreamError parseStreamError(XmlPullParser parser) throws IOException,
+            XmlPullParserException {
+        StreamError streamError = null;
+        boolean done = false;
+        while (!done) {
+            int eventType = parser.next();
+
+            if (eventType == XmlPullParser.START_TAG) {
+                streamError = new StreamError(parser.getName());
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("error")) {
+                    done = true;
+                }
+            }
+        }
+        return streamError;
+    }
+
+    /**
+     * A runnable to notify all listeners of a packet.
+     */
+    private class ListenerNotification implements Runnable {
+
+        private Packet packet;
+
+        public ListenerNotification(Packet packet) {
+            this.packet = packet;
+        }
+
+        public void run() {
+            for (ListenerWrapper listenerWrapper : listeners.values()) {
+                listenerWrapper.notifyListener(packet);
+            }
+        }
+    }
+
+    /**
+     * A wrapper class to associate a packet filter with a listener.
+     */
+    private static class ListenerWrapper {
+
+        private PacketListener packetListener;
+        private PacketFilter packetFilter;
+
+        public ListenerWrapper(PacketListener packetListener, PacketFilter packetFilter) {
+            this.packetListener = packetListener;
+            this.packetFilter = packetFilter;
+        }
+       
+        public void notifyListener(Packet packet) {
+            if (packetFilter == null || packetFilter.accept(packet)) {
+                packetListener.processPacket(packet);
+            }
+        }
+    }
+}
diff --git a/source/org/jivesoftware/smack/JmDNSPresenceDiscoverer.java b/source/org/jivesoftware/smack/JmDNSPresenceDiscoverer.java
new file mode 100644
index 0000000..5daa463
--- /dev/null
+++ b/source/org/jivesoftware/smack/JmDNSPresenceDiscoverer.java
@@ -0,0 +1,114 @@
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.util.Tuple;
+
+import javax.jmdns.JmDNS;
+import javax.jmdns.ServiceEvent;
+import javax.jmdns.ServiceListener;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+import java.util.LinkedList;
+
+
+/**
+ * An implementation of LLPresenceDiscoverer using JmDNS.
+ *
+ * @author Jonas Ådahl
+ */
+class JmDNSPresenceDiscoverer extends LLPresenceDiscoverer {
+    protected static final int SERVICE_REQUEST_TIMEOUT = 10000; 
+    protected static JmDNS jmdns;
+
+    public JmDNSPresenceDiscoverer() throws XMPPException {
+        jmdns = JmDNSService.jmdns;
+        if (jmdns == null)
+            throw new XMPPException("Failed to fully initiate mDNS daemon.");
+        jmdns.addServiceListener(JmDNSService.SERVICE_TYPE, new PresenceServiceListener());
+    }
+
+    /**
+     * Convert raw TXT fields to a list of strings.
+     * The raw TXT fields are encoded as follows:
+     * <ul>
+     *  <li>Byte 0 specifies the length of the first field (which starts at byte 1).</li>
+     *  <li>If the last byte of the previous field is the last byte of the array,
+     *  all TXT fields has been read.</li>
+     *  <li>If there are more bytes following, the next byte after the last of the
+     *  previous field specifies the length of the next field.</li>
+     * </ul>
+     *
+     * @param bytes raw TXT fields as an array of bytes.
+     * @return TXT fields as a list of strings.
+     */
+    private static List<String> TXTToList(byte[] bytes) {
+        List<String> list = new LinkedList<String>();
+        try {
+            int size_i = 0;
+            while (size_i < bytes.length) {
+                int s = (int)(bytes[size_i]);
+                list.add(new String(bytes, ++size_i, s, "UTF-8"));
+                size_i += s;
+            }
+        } catch (UnsupportedEncodingException uee) {
+            // XXX Without UTF-8 we are totally screwed;
+        }
+        return list;
+    }
+
+    /**
+     * Convert a TXT field list bundled with a '_presence._tcp' service to a
+     * String,String tuple. The TXT field list looks as following:
+     * "key=value" which is converted into the tuple (key, value).
+     *
+     * @param strings the TXT fields.
+     * @return a list of key,value tuples.
+     */
+    private static List<Tuple<String,String>> TXTListToXMPPRecords(List<String> strings) {
+        // records :: [(String, String)]
+        List<Tuple<String,String>> records = new LinkedList<Tuple<String,String>>();
+        for (String s : strings) {
+            String[] record = s.split("=", 2);
+            // check if valid
+            if (record.length == 2)
+                records.add(new Tuple<String, String>(record[0], record[1]));
+        }
+        return records;
+    }
+
+    /**
+     * Implementation of a JmDNS ServiceListener. Listens to service resolved and
+     * service information resolved events.
+     */
+    private class PresenceServiceListener implements ServiceListener {
+        public void serviceAdded(ServiceEvent event) {
+            // XXX
+            // To reduce network usage, we should only request information
+            // when needed.
+            new RequestInfoThread(event).start();
+        }
+        public void serviceRemoved(ServiceEvent event) {
+            presenceRemoved(event.getName());
+        }
+        public void serviceResolved(ServiceEvent event) {
+            presenceInfoAdded(event.getName(),
+                    new LLPresence(event.getName(),
+                        event.getInfo().getHostAddress(), event.getInfo().getPort(),
+                        TXTListToXMPPRecords(TXTToList(event.getInfo().getTextBytes()))));
+        }
+
+        private class RequestInfoThread extends Thread {
+            ServiceEvent event;
+
+            RequestInfoThread(ServiceEvent event) {
+                this.event = event;
+            }
+
+            public void run() {
+                jmdns.requestServiceInfo(event.getType(), event.getName(),
+                        true, SERVICE_REQUEST_TIMEOUT);
+            }
+        }
+    }
+}
diff --git a/source/org/jivesoftware/smack/JmDNSService.java b/source/org/jivesoftware/smack/JmDNSService.java
new file mode 100644
index 0000000..4cae916
--- /dev/null
+++ b/source/org/jivesoftware/smack/JmDNSService.java
@@ -0,0 +1,121 @@
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.util.Tuple;
+
+import javax.jmdns.JmDNS;
+import javax.jmdns.ServiceInfo;
+//import javax.jmdns.impl.JmDNSImpl;
+//import javax.jmdns.impl.DNSRecord;
+//import javax.jmdns.impl.DNSListener;
+
+import java.io.IOException;
+import java.util.Hashtable;
+
+/**
+ * Implements a LLService using JmDNS.
+ *
+ * @author Jonas Ådahl
+ */
+public class JmDNSService extends LLService {
+    static JmDNS jmdns = null;
+    static JmDNSPresenceDiscoverer presenceDiscoverer = null;
+    private ServiceInfo serviceInfo;
+    static final String SERVICE_TYPE = "_presence._tcp.local.";
+
+    private JmDNSService(LLPresence presence, LLPresenceDiscoverer presenceDiscoverer) {
+        super(presence, presenceDiscoverer);
+    }
+
+    /**
+     * Instantiate a new JmDNSService and start to listen for connections.
+     *
+     * @param presence the mDNS presence information that should be used.
+     */
+    public static LLService create(LLPresence presence) throws XMPPException {
+        // Start the JmDNS daemon.
+        initJmDNS();
+
+        // Start the presence discoverer
+        JmDNSPresenceDiscoverer presenceDiscoverer = new JmDNSPresenceDiscoverer();
+
+        // Start the presence service
+        JmDNSService service = new JmDNSService(presence, presenceDiscoverer);
+
+        // Initiate the mDNS XMPP Service 
+        service.init();
+
+        return service;
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        jmdns.close();
+    }
+
+    /**
+     * Start the JmDNS daemon.
+     */
+    private static void initJmDNS() throws XMPPException {
+        try {
+            if (jmdns == null) {
+                jmdns = JmDNS.create();
+            }
+        }
+        catch (IOException ioe) {
+            throw new XMPPException(ioe);
+        }
+    }
+
+    protected void updateText() {
+        Hashtable<String,String> ht = new Hashtable<String,String>();
+        
+        for (Tuple<String,String> t : presence.toList()) {
+            if (t.a != null && t.b != null) {
+                ht.put(t.a, t.b);
+            }
+        }
+
+        serviceInfo.setText(ht);
+    }
+
+    /**
+     * Register the DNS-SD service with the daemon.
+     */
+    protected void registerService() throws XMPPException {
+        Hashtable<String,String> ht = new Hashtable<String,String>();
+        
+        for (Tuple<String,String> t : presence.toList()) {
+            if (t.a != null && t.b != null)
+                ht.put(t.a, t.b);
+        }
+        serviceInfo = ServiceInfo.create(SERVICE_TYPE,
+                presence.getServiceName(), presence.getPort(), 0, 0, ht);
+        try {
+            jmdns.registerService(serviceInfo);
+        }
+        catch (IOException ioe) {
+            throw new XMPPException(ioe);
+        }
+    }
+
+    /**
+     * Reregister the DNS-SD service with the daemon.
+     */
+    protected void reannounceService() throws XMPPException {
+        try {
+            jmdns.reannounceService(serviceInfo);
+        }
+        catch (IOException ioe) {
+            throw new XMPPException("Exception occured when reannouncing mDNS presence.", ioe);
+        }
+    }
+
+    /**
+     * Unregister the DNS-SD service, making the client unavailable.
+     */
+    public void makeUnavailable() {
+        jmdns.unregisterService(serviceInfo);
+        serviceInfo = null;
+    }
+}
diff --git a/source/org/jivesoftware/smack/LLChat.java b/source/org/jivesoftware/smack/LLChat.java
new file mode 100644
index 0000000..41d120e
--- /dev/null
+++ b/source/org/jivesoftware/smack/LLChat.java
@@ -0,0 +1,95 @@
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.packet.Message;
+
+import java.util.Set;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * Keeps track of a chat session between two link-local clients.
+ */
+public class LLChat {
+    private String serviceName;
+    private LLService service;
+
+    private Set<LLMessageListener> listeners = new CopyOnWriteArraySet<LLMessageListener>();
+
+    // Queue for storing messages in case no listener is available when a
+    // message is to be delivered.
+    private List<Message> messageQueue = new LinkedList<Message>();
+
+
+    LLChat(LLService service, LLPresence presence) throws XMPPException {
+        this.service = service;;
+        serviceName = presence.getServiceName();
+    }
+
+    /**
+     * Get the service name of the remote client of this chat session.
+     * 
+     * @return the service name of the remote client of this chat session
+     */
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    /**
+     * Deliver a message to the message listeners.
+     *
+     * @param message the message to be delivered.
+     */
+    void deliver(Message message) {
+        // if no listeners are available, queue the messages for later.
+        synchronized (listeners) {
+            if (listeners.isEmpty())
+                messageQueue.add(message);
+            else {
+                for (LLMessageListener listener : listeners) {
+                    listener.processMessage(this, message);
+                }
+            }
+        }
+    }
+
+    /**
+     * Send a message packet to the remote client.
+     *
+     * @param message the message to be sent.
+     * @throws XMPPException if an exception occurs during transmission.
+     */
+    public void sendMessage(Message message) throws XMPPException {
+        message.setTo(serviceName);
+        message.setType(Message.Type.chat);
+        service.sendMessage(message);
+    }
+
+    /**
+     * Send a message to the remote client.
+     *
+     * @param message the message to be sent.
+     * @throws XMPPException if an exception occurs during transmission.
+     */
+    public void sendMessage(String text) throws XMPPException {
+        Message message = new Message(serviceName, Message.Type.chat);
+        message.setBody(text);
+
+        service.sendMessage(message);
+    }
+
+    /**
+     * Add a message listener. The message listener will be notified when new
+     * messages are received. If there was no listener when messages was to be
+     * delivered, the first listener to be added will receive all of the queued
+     * messages.
+     */
+    public void addMessageListener(LLMessageListener listener) {
+        synchronized (listeners) {
+            listeners.add(listener);
+            for (Message message : messageQueue)
+                listener.processMessage(this, message);
+            messageQueue.clear();
+        }
+    }
+}
diff --git a/source/org/jivesoftware/smack/LLChatListener.java b/source/org/jivesoftware/smack/LLChatListener.java
new file mode 100644
index 0000000..5ce7b75
--- /dev/null
+++ b/source/org/jivesoftware/smack/LLChatListener.java
@@ -0,0 +1,14 @@
+package org.jivesoftware.smack;
+
+/**
+ * Notification about new Link-local chat sessions.
+ */
+public interface LLChatListener {
+
+    /**
+     * New chat has been created.
+     *
+     * @param chat the newly created chat.
+     */
+    public void newChat(LLChat chat);
+}
diff --git a/source/org/jivesoftware/smack/LLConnectionConfiguration.java b/source/org/jivesoftware/smack/LLConnectionConfiguration.java
new file mode 100644
index 0000000..0f4b5c2
--- /dev/null
+++ b/source/org/jivesoftware/smack/LLConnectionConfiguration.java
@@ -0,0 +1,91 @@
+package org.jivesoftware.smack;
+
+import javax.net.SocketFactory;
+import java.net.Socket;
+
+/**
+ * Link-local connection configuration settings. Two general cases exists,
+ * one where the we want to connect to a remote peer, and one when o remote
+ * peer has connected to us.
+ */
+public class LLConnectionConfiguration implements Cloneable {
+    private LLPresence remotePresence;
+    private LLPresence localPresence;
+    private Socket socket;
+
+    private boolean debuggerEnabled = AbstractConnection.DEBUG_ENABLED;
+
+    // Holds the socket factory that is used to generate the socket in the connection
+    private SocketFactory socketFactory;
+
+    /** 
+     * Configuration used for connecting to remote peer.
+     * @param local LLPresence for the local user
+     * @param remote LLPresence for the remote user
+     */
+    LLConnectionConfiguration(LLPresence local, LLPresence remote) {
+        this.localPresence = local;
+        this.remotePresence = remote;
+    }
+
+    /** 
+     * Instantiate a link-local configuration when the connection is acting as
+     * the host.
+     * 
+     * @param local the local link-local presence class.
+     * @param remoteSocket the socket which the new connection is assigned to.
+     */
+    LLConnectionConfiguration(LLPresence local, Socket remoteSocket) {
+        this.localPresence = local;
+        this.socket = remoteSocket;
+    }
+
+    /**
+     * Tells if the connection is the initiating one.
+     * @return true if this configuration is for the connecting connection.
+     */
+    public boolean isInitiator() {
+        return socket == null;
+    }
+
+    /**
+     * Return the service name of the remote peer.
+     * @return the remote peer's service name.
+     */
+    public String getRemoteServiceName() {
+        return remotePresence.getServiceName();
+    }
+
+    /**
+     * Return the service name of this client.
+     * @return this clients service name.
+     */
+    public String getLocalServiceName() {
+        return localPresence.getServiceName();
+    } 
+
+    /**
+     * Return this clients link-local presence information.
+     * @return this clients link-local presence information.
+     */
+    public LLPresence getLocalPresence() {
+        return localPresence;
+    }
+
+    /**
+     * Return the remote client's link-local presence information.
+     * @return the remote client's link-local presence information.
+     */
+    public LLPresence getRemotePresence() {
+        return remotePresence;
+    }
+
+    /**
+     * Return the socket which has been established when the
+     * remote client connected.
+     * @return the socket established when the remote client connected.
+     */
+    public Socket getSocket() {
+        return socket;
+    }
+}
diff --git a/source/org/jivesoftware/smack/LLConnectionListener.java b/source/org/jivesoftware/smack/LLConnectionListener.java
new file mode 100644
index 0000000..1208882
--- /dev/null
+++ b/source/org/jivesoftware/smack/LLConnectionListener.java
@@ -0,0 +1,14 @@
+package org.jivesoftware.smack;
+
+/**
+ * Notification about when new Link-local connections has been established.
+ */
+public interface LLConnectionListener {
+
+    /**
+     * A new link-local connection has been established.
+     *
+     * @param connection the new established connection.
+     */
+    public void connectionCreated(XMPPLLConnection connection);
+}
diff --git a/source/org/jivesoftware/smack/LLMessageListener.java b/source/org/jivesoftware/smack/LLMessageListener.java
new file mode 100644
index 0000000..8710ed4
--- /dev/null
+++ b/source/org/jivesoftware/smack/LLMessageListener.java
@@ -0,0 +1,16 @@
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.packet.Message;
+
+/**
+ * Notification when messages are being delivered to a chat.
+ */
+public interface LLMessageListener {
+    /**
+     * New message in chat.
+     *
+     * @param chat the chat session which the message was delivered to.
+     * @param message the message being delivered.
+     */
+    void processMessage(LLChat chat, Message message);
+}
diff --git a/source/org/jivesoftware/smack/LLPacketReader.java b/source/org/jivesoftware/smack/LLPacketReader.java
new file mode 100644
index 0000000..566ed0e
--- /dev/null
+++ b/source/org/jivesoftware/smack/LLPacketReader.java
@@ -0,0 +1,244 @@
+/**
+ * $RCSfile$
+ * $Revision: 7232 $
+ * $Date: 2007-02-20 16:57:31 -0800 (Tue, 20 Feb 2007) $
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.provider.ProviderManager;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.xmlpull.mxp1.MXParser;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.*;
+
+/**
+ * Listens for XML traffic from a remote XMPP client and parses it into packet objects.
+ * The packet reader also manages all packet listeners and collectors.<p>
+ *
+ * @see PacketCollector
+ * @see PacketListener
+ * @author Matt Tucker
+ */
+public class LLPacketReader extends AbstractPacketReader {
+
+    private XMPPLLConnection connection;
+    private LLService service;
+
+    public LLPacketReader(final LLService service, final XMPPLLConnection connection) {
+        super(connection);
+        this.service = service;
+        this.connection = connection;
+        this.init();
+    }
+
+    /**
+     * Parse top-level packets in order to process them further.
+     *
+     * @param thread the thread that is being used by the reader to parse incoming packets.
+     */
+    protected void parsePackets(Thread thread) {
+        try {
+            int eventType = parser.getEventType();
+            do {
+                connection.updateLastActivity();
+                if (eventType == XmlPullParser.START_TAG) {
+                    if (parser.getName().equals("message")) {
+                        processPacket(PacketParserUtils.parseMessage(parser));
+                    }
+                    else if (parser.getName().equals("iq")) {
+                        processPacket(parseIQ(parser));
+                    }
+                    else if (parser.getName().equals("presence")) {
+                        processPacket(PacketParserUtils.parsePresence(parser));
+                    }
+                    // We found an opening stream. Record information about it, then notify
+                    // the connectionID lock so that the packet reader startup can finish.
+                    else if (parser.getName().equals("stream")) {
+                        // Ensure the correct jabber:client namespace is being used.
+                        if ("jabber:client".equals(parser.getNamespace(null))) {
+                            // Get the connection id.
+                            for (int i=0; i<parser.getAttributeCount(); i++) {
+                                if (parser.getAttributeName(i).equals("id")) {
+                                    // Save the connectionID
+                                    connectionID = parser.getAttributeValue(i);
+                                    /*
+                                    TODO implement 1.0 features (stream features etc)
+                                    if (!"1.0".equals(parser.getAttributeValue("", "version"))) {
+                                        // Notify that a stream has been opened if the
+                                        // server is not XMPP 1.0 compliant otherwise make the
+                                        // notification after TLS has been negotiated or if TLS
+                                        // is not supported
+                                        releaseConnectionIDLock();
+                                    }*/
+                                }
+                                else if (parser.getAttributeName(i).equals("from")) {
+                                    // Use the server name that the server says that it is.
+                                    connection.setServiceName(parser.getAttributeValue(i));
+                                }
+                            }
+
+
+                            // if we are the initiator, this means stream has been initiated
+                            // if we aren't the initiator, this means we have to respond with
+                            // stream initiator.
+                            if (connection.isInitiator()) {
+                                connectionID = connection.getServiceName();
+                                releaseConnectionIDLock();
+                            }
+                            else {
+                                // Check if service name is a known entity
+                                // if it is, open the stream and keep it open
+                                // otherwise open and immediately close it
+                                if (connection.getServiceName() == null) {
+                                    System.err.println("No service name specified in stream initiation, canceling.");
+                                    shutdown();
+                                } else {
+                                    // Check if service name is known, if so
+                                    // we will continue the session
+
+                                    LLPresence presence = service.getPresenceByServiceName(connection.getServiceName());
+                                    if (presence != null) {
+                                        connection.setRemotePresence(presence);
+                                        connectionID = connection.getServiceName();
+                                        connection.streamInitiatingReceived();
+                                        releaseConnectionIDLock();
+                                    } else {
+                                        System.err.println("Unknown service name '" +
+                                                connection.getServiceName() +
+                                                "' specified in stream initation, canceling.");
+                                        shutdown();
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    else if (parser.getName().equals("error")) {
+                        throw new XMPPException(parseStreamError(parser));
+                    }
+                }
+                else if (eventType == XmlPullParser.END_TAG) {
+                    if (parser.getName().equals("stream")) {
+                        // Disconnect the connection
+                        connection.disconnect();
+                    }
+                }
+                eventType = parser.next();
+            } while (!done && eventType != XmlPullParser.END_DOCUMENT && thread == readerThread);
+        }
+        catch (Exception e) {
+            if (!done) {
+                // Close the connection and notify connection listeners of the
+                // error.
+                notifyConnectionError(e);
+            }
+        }
+    }
+
+    /**
+     * Parses an IQ packet.
+     *
+     * @param parser the XML parser, positioned at the start of an IQ packet.
+     * @return an IQ object.
+     * @throws Exception if an exception occurs while parsing the packet.
+     */
+    private IQ parseIQ(XmlPullParser parser) throws Exception {
+        IQ iqPacket = null;
+
+        String id = parser.getAttributeValue("", "id");
+        String to = parser.getAttributeValue("", "to");
+        String from = parser.getAttributeValue("", "from");
+        IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
+        XMPPError error = null;
+
+        boolean done = false;
+        while (!done) {
+            int eventType = parser.next();
+
+            if (eventType == XmlPullParser.START_TAG) {
+                String elementName = parser.getName();
+                String namespace = parser.getNamespace();
+                if (elementName.equals("error")) {
+                    error = PacketParserUtils.parseError(parser);
+                }
+                // Otherwise, see if there is a registered provider for
+                // this element name and namespace.
+                else {
+                    Object provider = ProviderManager.getInstance().getIQProvider(elementName, namespace);
+                    if (provider != null) {
+                        if (provider instanceof IQProvider) {
+                            iqPacket = ((IQProvider)provider).parseIQ(parser);
+                        }
+                        else if (provider instanceof Class) {
+                            iqPacket = (IQ)PacketParserUtils.parseWithIntrospection(elementName,
+                                    (Class)provider, parser);
+                        }
+                    }
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("iq")) {
+                    done = true;
+                }
+            }
+        }
+        // Decide what to do when an IQ packet was not understood
+        if (iqPacket == null) {
+            if (IQ.Type.GET == type || IQ.Type.SET == type ) {
+                // If the IQ stanza is of type "get" or "set" containing a child element
+                // qualified by a namespace it does not understand, then answer an IQ of
+                // type "error" with code 501 ("feature-not-implemented")
+                iqPacket = new IQ() {
+                    public String getChildElementXML() {
+                        return null;
+                    }
+                };
+                iqPacket.setPacketID(id);
+                iqPacket.setTo(from);
+                iqPacket.setFrom(to);
+                iqPacket.setType(IQ.Type.ERROR);
+                iqPacket.setError(new XMPPError(XMPPError.Condition.feature_not_implemented));
+                connection.sendPacket(iqPacket);
+                return null;
+            }
+            else {
+                // If an IQ packet wasn't created above, create an empty IQ packet.
+                iqPacket = new IQ() {
+                    public String getChildElementXML() {
+                        return null;
+                    }
+                };
+            }
+        }
+
+        // Set basic values on the iq packet.
+        iqPacket.setPacketID(id);
+        iqPacket.setTo(to);
+        iqPacket.setFrom(from);
+        iqPacket.setType(type);
+        iqPacket.setError(error);
+
+        return iqPacket;
+    }
+}
diff --git a/source/org/jivesoftware/smack/LLPresence.java b/source/org/jivesoftware/smack/LLPresence.java
new file mode 100644
index 0000000..69d606b
--- /dev/null
+++ b/source/org/jivesoftware/smack/LLPresence.java
@@ -0,0 +1,194 @@
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.util.Tuple;
+
+import java.util.List;
+import java.util.LinkedList;
+
+/**
+ * Class for describing a Link-local presence information according to XEP-0174.
+ * XEP-0174 describes how to represent XMPP presences using mDNS/DNS-SD.
+ * The presence information is stored as TXT fields; example from the documentation
+ * follows:
+ *        juliet IN TXT "txtvers=1"
+ *        juliet IN TXT "1st=Juliet"
+ *        juliet IN TXT "email=juliet@capulet.lit"
+ *        juliet IN TXT "hash=sha-1"
+ *        juliet IN TXT "jid=juliet@capulet.lit"
+ *        juliet IN TXT "last=Capulet"
+ *        juliet IN TXT "msg=Hanging out downtown"
+ *        juliet IN TXT "nick=JuliC"
+ *        juliet IN TXT "node=http://www.adiumx.com"
+ *        juliet IN TXT "phsh=a3839614e1a382bcfebbcf20464f519e81770813"
+ *        juliet IN TXT "port.p2pj=5562"
+ *        juliet IN TXT "status=avail"
+ *        juliet IN TXT "vc=CA!"
+ *        juliet IN TXT "ver=66/0NaeaBKkwk85efJTGmU47vXI="
+ */
+public class LLPresence {
+    // Service info, gathered from the TXT fields
+    private String firstName, lastName, email, msg, nick, jid;
+    // XEP-0174 specifies that if status is not specified it is equal to "avail".
+    private Mode status = Mode.avail;
+
+    public static enum Mode {
+        avail, away, dnd
+    }
+
+    // Host details
+    private int port = 0;
+    private String host;
+    private String serviceName;
+
+    public LLPresence(String serviceName) {
+        this.serviceName = serviceName;
+    }
+
+    public LLPresence(String serviceName, String host, int port) {
+        this.serviceName = serviceName;
+        this.host = host;
+        this.port = port;
+    }
+
+    public LLPresence(String serviceName, String host, int port,
+            List<Tuple<String,String>> records) {
+        this(serviceName, host, port);
+
+        // Parse the tuple list (originating from the TXT fields) and put them
+        // in variables
+        for (Tuple<String,String> t : records) {
+            if (t.a.equals("1st"))
+                setFirstName(t.b);
+            else if (t.a.equals("last"))
+                setLastName(t.b);
+            else if (t.a.equals("email"))
+                setEMail(t.b);
+            else if (t.a.equals("jid"))
+                setJID(t.b);
+            else if (t.a.equals("nick"))
+                setNick(t.b);
+            else if (t.a.equals("status")) {
+                try {
+                    setStatus(Mode.valueOf(t.b));
+                }
+                catch (IllegalArgumentException iae) {
+                    System.err.println("Found invalid presence status (" +
+                            t.b + ") in TXT entry.");
+                }
+            }
+            else if (t.a.equals("msg"))
+                setMsg(t.b);
+        }
+    }
+
+    public List<Tuple<String, String>> toList() {
+        LinkedList<Tuple<String, String>> list = new LinkedList<Tuple<String, String>>();
+        list.add(new Tuple<String,String>("1st", firstName));
+        list.add(new Tuple<String,String>("last", lastName));
+        list.add(new Tuple<String,String>("email", email));
+        list.add(new Tuple<String,String>("jid", jid));
+        list.add(new Tuple<String,String>("nick", nick));
+        list.add(new Tuple<String,String>("status", status.toString()));
+        list.add(new Tuple<String,String>("msg", msg));
+        list.add(new Tuple<String,String>("port.p2ppj", new Integer(port).toString()));
+        return list;
+    }
+
+    /**
+     * Update all the values of the presence.
+     */
+    void update(LLPresence p) {
+        setFirstName(p.getFirstName());
+        setLastName(p.getLastName());
+        setEMail(p.getEMail());
+        setMsg(p.getMsg());
+        setNick(p.getNick());
+        setStatus(p.getStatus());
+        setJID(p.getJID());
+    }
+
+    public void setFirstName(String name) {
+        firstName = name;
+    }
+
+    public void setLastName(String name) {
+        lastName = name;
+    }
+
+    public void setEMail(String email) {
+        this.email = email;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public void setNick(String nick) {
+        this.nick = nick;
+    }
+
+    public void setStatus(Mode status) {
+        this.status = status;
+    }
+
+    public void setJID(String jid) {
+        this.jid = jid;
+    }
+
+    void setPort(int port) {
+        this.port = port;
+    }
+
+    public String getFirstName() {
+        return firstName;
+    }
+
+    public String getLastName() {
+        return lastName;
+    }
+
+    public String getEMail() {
+        return email;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public String getNick() {
+        return nick;
+    }
+
+    public Mode getStatus() {
+        return status;
+    }
+
+    public String getJID() {
+        return jid;
+    }
+
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public boolean equals(Object o) {
+        if (o instanceof LLPresence) {
+            LLPresence p = (LLPresence)o;
+            return p.serviceName == serviceName &&
+                p.host == host;
+        }
+        return false;
+    }
+
+    public int hashCode() {
+        return serviceName.hashCode();
+    }
+}
diff --git a/source/org/jivesoftware/smack/LLPresenceDiscoverer.java b/source/org/jivesoftware/smack/LLPresenceDiscoverer.java
new file mode 100644
index 0000000..2b366d4
--- /dev/null
+++ b/source/org/jivesoftware/smack/LLPresenceDiscoverer.java
@@ -0,0 +1,95 @@
+package org.jivesoftware.smack;
+
+import javax.jmdns.JmDNS;
+import javax.jmdns.ServiceEvent;
+import javax.jmdns.ServiceListener;
+
+import java.util.Set;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * Link-local presence discoverer. XEP-0174 describes how to use mDNS/DNS-SD.
+ * This class in an abstract representation of the basic functionality of
+ * handeling presences discovering.
+ */
+public abstract class LLPresenceDiscoverer {
+    // Listeners to be notified about changes.
+    protected Set<LLPresenceListener> listeners = new CopyOnWriteArraySet<LLPresenceListener>();
+    // Map of service name -> Link-local presence
+    private Map<String,LLPresence> presences = new ConcurrentHashMap<String,LLPresence>();
+
+    /**
+     * Add listener which will be notified when new presences are discovered,
+     * presence information changed or presences goes offline.
+     * @param listener the listener to be notified.
+     */
+    public void addPresenceListener(LLPresenceListener listener) {
+        listeners.add(listener);
+        for (LLPresence presence : presences.values())
+            listener.presenceNew(presence);
+    }
+
+    /**
+     * Remove presence listener.
+     * @param listener listener to be removed.
+     */
+    public void removePresenceListener(LLPresenceListener listener) {
+        listeners.remove(listener);
+    }
+
+    /**
+     * Return a collection of presences known.
+     * @return all known presences.
+     */
+    public Collection<LLPresence> getPresences() {
+        return presences.values();
+    }
+
+    /**
+     * Return the presence with the specified service name.
+     * 
+     * @param name service name of the presence.
+     * @return the presence information with the given service name.
+     */
+    public LLPresence getPresence(String name) {
+        return presences.get(name);
+    }
+
+    /**
+     * Used by the class extending this one to tell when new
+     * presence is added.
+     * 
+     * @param name service name of the presence.
+     */
+    protected void presenceAdded(String name) {
+        presences.put(name, null);
+    }
+
+    /**
+     * Used by the class extending this one to tell when new
+     * presence information is added.
+     *
+     * @param name service name of the presence.
+     * @param presence presence information.
+     */
+    protected void presenceInfoAdded(String name, LLPresence presence) {
+        presences.put(name, presence);
+        for (LLPresenceListener l : listeners)
+            l.presenceNew(presence);
+    }
+
+    /** 
+     * Used by the class extending htis one to tell when a presence
+     * goes offline.
+     *
+     * @param name service name of the presence going offline.
+     */
+    protected void presenceRemoved(String name) {
+        LLPresence presence = presences.remove(name);
+        for (LLPresenceListener l : listeners)
+            l.presenceRemove(presence);
+    }
+}
diff --git a/source/org/jivesoftware/smack/LLPresenceListener.java b/source/org/jivesoftware/smack/LLPresenceListener.java
new file mode 100644
index 0000000..318aa88
--- /dev/null
+++ b/source/org/jivesoftware/smack/LLPresenceListener.java
@@ -0,0 +1,19 @@
+package org.jivesoftware.smack;
+
+/**
+ * Interface for receiving notifications about presence changes.
+ */
+public interface LLPresenceListener {
+    /**
+     * New link-local presence has been discovered.
+     * 
+     * @param presence information about the new presence
+     */
+
+    public void presenceNew(LLPresence presence);
+    /**
+     * A link-local presence has gone offline.
+     * @param presence the presence which went offline.
+     */
+    public void presenceRemove(LLPresence presence);
+}
diff --git a/source/org/jivesoftware/smack/LLService.java b/source/org/jivesoftware/smack/LLService.java
new file mode 100644
index 0000000..b4065ad
--- /dev/null
+++ b/source/org/jivesoftware/smack/LLService.java
@@ -0,0 +1,567 @@
+package org.jivesoftware.smack;
+
+
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.AndFilter;
+import org.jivesoftware.smack.filter.OrFilter;
+import org.jivesoftware.smack.filter.PacketTypeFilter;
+import org.jivesoftware.smack.filter.MessageTypeFilter;
+
+
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Set;
+import java.util.Map;
+import java.util.LinkedList;
+import java.util.ArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * LLService acts as an abstract interface to a Link-local XMPP service
+ * according to XEP-0174. XEP-0174 describes how this is achieved using
+ * mDNS/DNS-SD, and this class creates an implementation unspecific 
+ * interface for doing this.
+ *
+ * The mDNS/DNS-SD is for example implemented by JmDNSService (using JmDNS).
+ *
+ * There is only one instance of LLService possible at one time.
+ *
+ * Tasks taken care of here are:
+ * <ul>
+ *   <li>Connection Management
+ *     <ul>
+ *       <li>Keep track of active connections from and to the local client</li>
+ *       <li>Listen for connections on a semi randomized port announced by the
+ *           mDNS/DNS-SD daemon</li>
+ *       <li>Establish new connections when there is none to use and packets are
+ *           to be sent</li>
+ *     <ul>
+ *   <li>Chat Management - Keep track of messaging sessions between users</li>
+ * </ul>
+ *
+ * @author Jonas Ådahl
+ */
+public abstract class LLService {
+    private LLService service = null;
+
+    static final int DEFAULT_MIN_PORT = 2300;
+    static final int DEFAULT_MAX_PORT = 2400;
+    protected LLPresence presence;
+    private String serviceName;
+    private String host;
+    private boolean done = false;
+    private Thread listenerThread;
+
+    private Map<String,LLChat> chats = new ConcurrentHashMap<String,LLChat>();
+
+    private Map<String,XMPPLLConnection> ingoing = new ConcurrentHashMap<String,XMPPLLConnection>();
+    private Map<String,XMPPLLConnection> outgoing = new ConcurrentHashMap<String,XMPPLLConnection>();
+
+    // Listeners for state updates, such as LLService closed down
+    private Set<LLServiceStateListener> stateListeners = new CopyOnWriteArraySet<LLServiceStateListener>();
+
+    // Presence discoverer, notifies of changes in presences on the network.
+    private LLPresenceDiscoverer presenceDiscoverer;
+
+    // chat listeners gets notified when new chats are created
+    private Set<LLChatListener> chatListeners = new CopyOnWriteArraySet<LLChatListener>();
+
+    private ServerSocket socket;
+
+    /**
+     * Spam stdout with some debug information.
+     */
+    public void spam() {
+        System.out.println("Number of ingoing connection in map: " + ingoing.size());
+        System.out.println("Number of outgoing connection in map: " + outgoing.size());
+
+        System.out.println("Active chats:");
+        for (LLChat chat : chats.values()) {
+            System.out.println(" * " + chat.getServiceName());
+        }
+
+        System.out.println("Known presences:");
+        for (LLPresence presence : presenceDiscoverer.getPresences()) {
+            System.out.println(" * " + presence.getServiceName() + "(" + presence.getStatus() + ")");
+        }
+        Thread.currentThread().getThreadGroup().list();
+    }
+
+    protected LLService(LLPresence presence, LLPresenceDiscoverer discoverer) {
+        this.presence = presence;
+        presenceDiscoverer = discoverer;
+        serviceName = presence.getServiceName();
+        host = presence.getHost();
+        service = this;
+
+        XMPPLLConnection.addLLConnectionListener(new LLConnectionListener() {
+            public void connectionCreated(XMPPLLConnection connection) {
+                if (connection.isInitiator()) {
+                    addOutgoingConnection(connection);
+                }
+                else {
+                    addIngoingConnection(connection);
+                }
+                connection.addConnectionListener(new ConnectionActivityListener(connection));
+                // add message listener. filter logic:
+                // type = msg ^ (msg.type = chat v msg.type = normal v msg.type = error)
+                connection.addPacketListener(new MessageListener(service),
+                    new AndFilter(
+                        new PacketTypeFilter(Message.class),
+                        new OrFilter(
+                            new MessageTypeFilter(Message.Type.chat),
+                            new OrFilter(
+                                new MessageTypeFilter(Message.Type.normal),
+                                new MessageTypeFilter(Message.Type.error)))));
+            }
+        });
+
+    }
+
+    /**
+     * Returns the running mDNS/DNS-SD XMPP instance. There can only be one
+     * instance at a time.
+     *
+     * @return the active LLService instance.
+     * @throws XMPPException if the LLService hasn't been instantiated.
+     */
+    /*public static LLService getServiceInstance() throws XMPPException {
+        if (service == null)
+            throw new XMPPException("Link-local service not initiated.");
+        return service;
+    }*/
+
+    /**
+     * Registers the service to the mDNS/DNS-SD daemon.
+     * Should be implemented by the class extending this, for mDNS/DNS-SD library specific calls.
+     */
+    protected abstract void registerService() throws XMPPException;
+
+    /**
+     * Re-announce the presence information by using the mDNS/DNS-SD daemon.
+     */
+    protected abstract void reannounceService() throws XMPPException;
+
+    /**
+     * Make the client unavailabe. Equivalent to sending unavailable-presence.
+     */
+    public abstract void makeUnavailable();
+
+    /**
+     * Update the text field information. Used for setting new presence information.
+     */
+    protected abstract void updateText();
+
+    protected void init() throws XMPPException {
+        // allocate a new port for remote clients to connect to
+        socket = bindRange(DEFAULT_MIN_PORT, DEFAULT_MAX_PORT);
+        presence.setPort(socket.getLocalPort());
+
+        // register service on the allocated port
+        registerService();
+
+        // start to listen for new connections
+        listenerThread = new Thread() {
+            public void run() {
+                try {
+                    listenForConnections();
+                    for (LLServiceStateListener listener : stateListeners)
+                        listener.serviceClosed();
+                } catch (XMPPException e) {
+                    for (LLServiceStateListener listener : stateListeners)
+                        listener.serviceClosedOnError(e);
+                }
+            }
+        };
+        listenerThread.setName("Smack Link-local Service Listener");
+        listenerThread.setDaemon(true);
+        listenerThread.start();
+    }
+
+    public void close() {
+        done = true;
+
+        // close incoming connections
+        for (XMPPLLConnection connection : ingoing.values()) {
+            try {
+                connection.shutdown();
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+
+        // close outgoing connections
+        for (XMPPLLConnection connection : outgoing.values()) {
+            try {
+                connection.shutdown();
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+        try {
+            socket.close();
+        } catch (IOException ioe) {
+            // ignore
+        }
+    }
+
+    /**
+     * Listen for new connections on socket, and spawn XMPPLLConnections
+     * when new connections are established.
+     *
+     * @throws XMPPException whenever an exception occurs
+     */
+    private void listenForConnections() throws XMPPException {
+        while (!done) {
+            try {
+                // wait for new connection
+                Socket s = socket.accept();
+
+                LLConnectionConfiguration config =
+                    new LLConnectionConfiguration(presence, s);
+                XMPPLLConnection connection = new XMPPLLConnection(this, config);
+
+                // Spawn new thread to handle the connecting.
+                // The reason for spawning a new thread is to let two remote clients
+                // be able to connect at the same time.
+                Thread connectionInitiatorThread =
+                    new ConnectionInitiatorThread(connection);
+                connectionInitiatorThread.setName("Smack Link-local Connection Initiator");
+                connectionInitiatorThread.setDaemon(true);
+                connectionInitiatorThread.start();
+            }
+            catch (IOException ioe) {
+                throw new XMPPException("Link-local service unexpectedly closed down.", ioe);
+            }
+        }
+    }
+
+    /**
+     * Bind one socket to any port within a given range.
+     *
+     * @param min the minimum port number allowed
+     * @param max hte maximum port number allowed
+     * @throws XMPPException if binding failed on all allowed ports.
+     */
+    private static ServerSocket bindRange(int min, int max) throws XMPPException {
+        int port = 0;
+        for (int try_port = min; try_port <= max; try_port++) {
+            try {
+                ServerSocket socket = new ServerSocket(try_port);
+                return socket;
+            }
+            catch (IOException e) {
+                // failed to bind, try next
+            }
+        }
+        throw new XMPPException("Unable to bind port, no ports available.");
+    }
+
+    private void unknownOriginMessage(Message message) {
+        for (LLServiceStateListener listener : stateListeners) {
+            listener.unknownOriginMessage(message);
+        }
+    }
+
+    /**
+     * Add service state listener.
+     *
+     * @param listener the service state listener to be added.
+     */
+    public void addServiceStateListener(LLServiceStateListener listener) {
+        stateListeners.add(listener);
+    }
+
+    /**
+     * Remove service state listener.
+     *
+     * @param listener the service state listener to be removed.
+     */
+    public void removeServiceStateListener(LLServiceStateListener listener) {
+        stateListeners.remove(listener);
+    }
+
+    /**
+     * Add Link-local chat session listener. The chat session listener will
+     * be notified when new link-local chat sessions are created.
+     *
+     * @param listener the listener to be added.
+     */
+    public void addLLChatListener(LLChatListener listener) {
+        chatListeners.add(listener);
+    }
+
+    /**
+     * Remove Link-local chat session listener. 
+     *
+     * @param listener the listener to be removed.
+     */
+    public void removeLLChatListener(LLChatListener listener) {
+        chatListeners.remove(listener);
+    }
+
+    /**
+     * Add presence listener. A presence listener will be notified of new
+     * presences, presences going offline, and changes in presences.
+     *
+     * @param listener the listener to be added.
+     */
+    public void addPresenceListener(LLPresenceListener listener) {
+        presenceDiscoverer.addPresenceListener(listener);
+    }
+
+    /**
+     * Remove presence listener.
+     *
+     * @param listener presence listener to be removed.
+     */
+    public void removePresenceListener(LLPresenceListener listener) {
+        presenceDiscoverer.removePresenceListener(listener);
+    }
+
+    /**
+     * Get the presence information associated with the given service name.
+     *
+     * @param serviceName the service name which information should be returned.
+     * @return the service information.
+     */
+    LLPresence getPresenceByServiceName(String serviceName) {
+        return presenceDiscoverer.getPresence(serviceName);
+    }
+
+    /**
+     * Return a collection of all active connections. This may be used if the
+     * user wants to change a property on all connections, such as add a service
+     * discovery feature or other.
+     *
+     * @return a colllection of all active connections.
+     */
+    public Collection<XMPPLLConnection> getConnections() {
+        Collection<XMPPLLConnection> connections =
+            new ArrayList<XMPPLLConnection>(outgoing.values());
+        connections.addAll(ingoing.values());
+        return connections;
+    }
+
+    /**
+     * Returns a connection to a given service name.
+     * First checks for an outgoing connection, if noone exists,
+     * try ingoing.
+     *
+     * @param serviceName the service name
+     * @return a connection associated with the service name or null if no
+     * connection is available.
+     */
+    XMPPLLConnection getConnectionTo(String serviceName) {
+        XMPPLLConnection connection = outgoing.get(serviceName);
+        if (connection != null)
+            return connection;
+        return ingoing.get(serviceName);
+    }
+
+    void addIngoingConnection(XMPPLLConnection connection) {
+        ingoing.put(connection.getServiceName(), connection);
+    }
+
+    void removeIngoingConnection(XMPPLLConnection connection) {
+        ingoing.remove(connection.getServiceName());
+    }
+
+    void addOutgoingConnection(XMPPLLConnection connection) {
+        outgoing.put(connection.getServiceName(), connection);
+    }
+
+    void removeOutgoingConnection(XMPPLLConnection connection) {
+        outgoing.remove(connection.getServiceName());
+    }
+
+    void newLLChat(LLChat chat) {
+        chats.put(chat.getServiceName(), chat);
+        for (LLChatListener listener : chatListeners) {
+            listener.newChat(chat);
+        }
+    }
+
+    /**
+     * Get a LLChat associated with a given service name.
+     * If no LLChat session is available, a new one is created.
+     *
+     * @param serviceName the service name
+     * @return a chat session instance associated with the given service name.
+     */
+    public LLChat getChat(String serviceName) throws XMPPException {
+        LLChat chat = chats.get(serviceName);
+        if (chat == null) {
+            LLPresence presence = getPresenceByServiceName(serviceName);
+            if (presence == null)
+                throw new XMPPException("Can't initiate new chat to '" +
+                        serviceName + "': mDNS presence unknown.");
+            chat = new LLChat(this, presence);
+            newLLChat(chat);
+        }
+        return chat;
+    }
+
+    /**
+     * Returns a XMPPLLConnection to the serviceName.
+     * If no established connection exists, a new connection is created.
+     * 
+     * @param serviceName Service name of the remote client.
+     * @return A connection to the given service name.
+     */
+    public XMPPLLConnection getConnection(String serviceName) throws XMPPException {
+        // If a connection exists, return it.
+        XMPPLLConnection connection = getConnectionTo(serviceName);
+        if (connection != null)
+            return connection;
+
+        // If no connection exists, look up the presence and connect according to.
+        LLPresence remotePresence = getPresenceByServiceName(serviceName);
+
+        if (remotePresence == null) {
+            throw new XMPPException("Can't initiate connection, remote peer is not available.");
+        }
+
+        LLConnectionConfiguration config =
+            new LLConnectionConfiguration(presence, remotePresence);
+        connection = new XMPPLLConnection(this, config);
+        connection.connect();
+        addOutgoingConnection(connection);
+
+        return connection;
+    }
+
+    /**
+     * Send a message to the remote peer.
+     *
+     * @param message the message to be sent.
+     * @throws XMPPException if the message cannot be sent.
+     */
+    void sendMessage(Message message) throws XMPPException {
+        sendPacket(message);
+    }
+
+
+    /**
+     * Send a packet to the remote peer.
+     *
+     * @param packet the packet to be sent.
+     * @throws XMPPException if the packet cannot be sent.
+     */
+    void sendPacket(Packet packet) throws XMPPException {
+        getConnection(packet.getTo()).sendPacket(packet);
+    }
+
+    /**
+     * Update the presence information announced by the mDNS/DNS-SD daemon.
+     * The presence object stored in the LLService class will be updated
+     * with the new information and the daemon will reannounce the changes.
+     *
+     * @param presence the new presence information
+     * @throws XMPPException if an error occurs
+     */
+    public void updatePresence(LLPresence presence) throws XMPPException {
+        this.presence.update(presence);
+        updateText();
+        reannounceService();
+    }
+
+    /**
+     * ConnectionActivityListener listens for link-local connection activity
+     * such as closed connection and broken connection, and keeps record of
+     * what active connections exist up to date.
+     */
+    private class ConnectionActivityListener implements ConnectionListener {
+        private XMPPLLConnection connection;
+
+        ConnectionActivityListener(XMPPLLConnection connection) {
+            this.connection = connection;
+        }
+
+        public void connectionClosed() {
+            removeConnectionRecord();
+        }
+
+        public void connectionClosedOnError(Exception e) {
+            removeConnectionRecord();
+        }
+
+        public void reconnectingIn(int seconds) {
+        }
+
+        public void reconnectionSuccessful() {
+        }
+
+        public void reconnectionFailed(Exception e) {
+        }
+
+        private void removeConnectionRecord() {
+            if (connection.isInitiator())
+                removeOutgoingConnection(connection);
+            else
+                removeIngoingConnection(connection);
+        }
+    }
+
+    /**
+     * MessageListener listenes for messages from connections and delivers them
+     * to the corresponding chat session. If no session is available, a new one
+     * is created.
+     */
+    private class MessageListener implements PacketListener {
+        private LLService service;
+
+        MessageListener(LLService service) {
+            this.service = service;
+        }
+
+        public void processPacket(Packet packet) {
+            // handle message
+            if (packet instanceof Message) {
+                Message message = (Message)packet;
+                String remoteServiceName = message.getFrom();
+                LLPresence presence =  getPresenceByServiceName(remoteServiceName);
+
+                // Get existing chat instance or create a new one and deliver
+                // the message.
+                try {
+                    getChat(remoteServiceName).deliver(message);
+                }
+                catch (XMPPException xe) {
+                    // If getChat throws an exception, it's because no
+                    // presence could be found, thus known origin.
+                    service.unknownOriginMessage(message);
+                }
+            }
+        }
+    }
+
+    /**
+     * Initiates a connection in a seperate thread, controlling
+     * it was established correctly and stream was initiated.
+     */
+    private class ConnectionInitiatorThread extends Thread {
+        XMPPLLConnection connection;
+
+        ConnectionInitiatorThread(XMPPLLConnection connection) {
+            this.connection = connection;
+        }
+
+        public void run() {
+            try {
+                connection.initListen();
+            }
+            catch (XMPPException xe) {
+                // ignore, since its an incoming connection
+                // there is nothing to save
+            }
+        }
+    }
+}
+
diff --git a/source/org/jivesoftware/smack/LLServiceStateListener.java b/source/org/jivesoftware/smack/LLServiceStateListener.java
new file mode 100644
index 0000000..0a2251b
--- /dev/null
+++ b/source/org/jivesoftware/smack/LLServiceStateListener.java
@@ -0,0 +1,31 @@
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.packet.Message;
+
+/**
+ * Interface for handeling link-local service events such as 
+ * service closing, service crashes and other events.
+ */
+public interface LLServiceStateListener {
+
+    /**
+     * Notification that the connection was closed normally.
+     */
+    public void serviceClosed();
+
+    /**
+     * Notification that the connection was closed due to an exception.
+     *
+     * @param e the exception.
+     */
+    public void serviceClosedOnError(Exception e);
+
+    /**
+     * Notification that a message with unknown presence was received.
+     * This could be someone being invisible, meaning no presece is
+     * announced.
+     *
+     * @param e the exception.
+     */
+    public void unknownOriginMessage(Message e);
+}
diff --git a/source/org/jivesoftware/smack/PacketCollector.java b/source/org/jivesoftware/smack/PacketCollector.java
index e609c76..8765126 100644
--- a/source/org/jivesoftware/smack/PacketCollector.java
+++ b/source/org/jivesoftware/smack/PacketCollector.java
@@ -49,7 +49,7 @@ public class PacketCollector {
 
     private PacketFilter packetFilter;
     private LinkedList<Packet> resultQueue;
-    private PacketReader packetReader;
+    private AbstractPacketReader packetReader;
     private boolean cancelled = false;
 
     /**
@@ -59,7 +59,7 @@ public class PacketCollector {
      * @param packetReader the packetReader the collector is tied to.
      * @param packetFilter determines which packets will be returned by this collector.
      */
-    protected PacketCollector(PacketReader packetReader, PacketFilter packetFilter) {
+    protected PacketCollector(AbstractPacketReader packetReader, PacketFilter packetFilter) {
         this.packetReader = packetReader;
         this.packetFilter = packetFilter;
         this.resultQueue = new LinkedList<Packet>();
diff --git a/source/org/jivesoftware/smack/PacketReader.java b/source/org/jivesoftware/smack/PacketReader.java
index 180ebb5..0252001 100644
--- a/source/org/jivesoftware/smack/PacketReader.java
+++ b/source/org/jivesoftware/smack/PacketReader.java
@@ -41,229 +41,22 @@ import java.util.concurrent.*;
  * @see PacketListener
  * @author Matt Tucker
  */
-class PacketReader {
-
-    private Thread readerThread;
-    private ExecutorService listenerExecutor;
+class PacketReader extends AbstractPacketReader {
 
     private XMPPConnection connection;
-    private XmlPullParser parser;
-    private boolean done;
-    private Collection<PacketCollector> collectors = new ConcurrentLinkedQueue<PacketCollector>();
-    protected final Map<PacketListener, ListenerWrapper> listeners =
-            new ConcurrentHashMap<PacketListener, ListenerWrapper>();
-    protected final Collection<ConnectionListener> connectionListeners =
-            new CopyOnWriteArrayList<ConnectionListener>();
-
-    private String connectionID = null;
-    private Semaphore connectionSemaphore;
 
     protected PacketReader(final XMPPConnection connection) {
+        super(connection);
         this.connection = connection;
         this.init();
     }
 
     /**
-     * Initializes the reader in order to be used. The reader is initialized during the
-     * first connection and when reconnecting due to an abruptly disconnection.
-     */
-    protected void init() {
-        done = false;
-        connectionID = null;
-
-        readerThread = new Thread() {
-            public void run() {
-                parsePackets(this);
-            }
-        };
-        readerThread.setName("Smack Packet Reader (" + connection.connectionCounterValue + ")");
-        readerThread.setDaemon(true);
-
-        // Create an executor to deliver incoming packets to listeners. We'll use a single
-        // thread with an unbounded queue.
-        listenerExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
-
-            public Thread newThread(Runnable runnable) {
-                Thread thread = new Thread(runnable,
-                        "Smack Listener Processor (" + connection.connectionCounterValue + ")");
-                thread.setDaemon(true);
-                return thread;
-            }
-        });
-
-        resetParser();
-    }
-
-    /**
-     * Creates a new packet collector for this reader. A packet filter determines
-     * which packets will be accumulated by the collector.
-     *
-     * @param packetFilter the packet filter to use.
-     * @return a new packet collector.
-     */
-    public PacketCollector createPacketCollector(PacketFilter packetFilter) {
-        PacketCollector collector = new PacketCollector(this, packetFilter);
-        collectors.add(collector);
-        // Add the collector to the list of active collector.
-        return collector;
-    }
-
-    protected void cancelPacketCollector(PacketCollector packetCollector) {
-        collectors.remove(packetCollector);
-    }
-
-    /**
-     * Registers a packet listener with this reader. A packet filter determines
-     * which packets will be delivered to the listener.
-     *
-     * @param packetListener the packet listener to notify of new packets.
-     * @param packetFilter the packet filter to use.
-     */
-    public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) {
-        ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter);
-        listeners.put(packetListener, wrapper);
-    }
-
-    /**
-     * Removes a packet listener.
-     *
-     * @param packetListener the packet listener to remove.
-     */
-    public void removePacketListener(PacketListener packetListener) {
-        listeners.remove(packetListener);
-    }
-
-    /**
-     * Starts the packet reader thread and returns once a connection to the server
-     * has been established. A connection will be attempted for a maximum of five
-     * seconds. An XMPPException will be thrown if the connection fails.
-     *
-     * @throws XMPPException if the server fails to send an opening stream back
-     *      for more than five seconds.
-     */
-    public void startup() throws XMPPException {
-        connectionSemaphore = new Semaphore(1);
-
-        readerThread.start();
-        // Wait for stream tag before returing. We'll wait a couple of seconds before
-        // giving up and throwing an error.
-        try {
-            connectionSemaphore.acquire();
-
-            // A waiting thread may be woken up before the wait time or a notify
-            // (although this is a rare thing). Therefore, we continue waiting
-            // until either a connectionID has been set (and hence a notify was
-            // made) or the total wait time has elapsed.
-            int waitTime = SmackConfiguration.getPacketReplyTimeout();
-            connectionSemaphore.tryAcquire(3 * waitTime, TimeUnit.MILLISECONDS);
-        }
-        catch (InterruptedException ie) {
-            // Ignore.
-        }
-        if (connectionID == null) {
-            throw new XMPPException("Connection failed. No response from server.");
-        }
-        else {
-            connection.connectionID = connectionID;
-        }
-    }
-
-    /**
-     * Shuts the packet reader down.
-     */
-    public void shutdown() {
-        // Notify connection listeners of the connection closing if done hasn't already been set.
-        if (!done) {
-            for (ConnectionListener listener : connectionListeners) {
-                try {
-                    listener.connectionClosed();
-                }
-                catch (Exception e) {
-                    // Cath and print any exception so we can recover
-                    // from a faulty listener and finish the shutdown process
-                    e.printStackTrace();
-                }
-            }
-        }
-        done = true;
-
-        // Shut down the listener executor.
-        listenerExecutor.shutdown();
-    }
-
-    /**
-     * Cleans up all resources used by the packet reader.
-     */
-    void cleanup() {
-        connectionListeners.clear();
-        listeners.clear();
-        collectors.clear();
-    }
-
-    /**
-     * Sends out a notification that there was an error with the connection
-     * and closes the connection.
-     *
-     * @param e the exception that causes the connection close event.
-     */
-    void notifyConnectionError(Exception e) {
-        done = true;
-        // Closes the connection temporary. A reconnection is possible
-        connection.shutdown(new Presence(Presence.Type.unavailable));
-        // Print the stack trace to help catch the problem
-        e.printStackTrace();
-        // Notify connection listeners of the error.
-        for (ConnectionListener listener : connectionListeners) {
-            try {
-                listener.connectionClosedOnError(e);
-            }
-            catch (Exception e2) {
-                // Cath and print any exception so we can recover
-                // from a faulty listener
-                e2.printStackTrace();
-            }
-        }
-    }
-
-    /**
-     * Sends a notification indicating that the connection was reconnected successfully.
-     */
-    protected void notifyReconnection() {
-        // Notify connection listeners of the reconnection.
-        for (ConnectionListener listener : connectionListeners) {
-            try {
-                listener.reconnectionSuccessful();
-            }
-            catch (Exception e) {
-                // Cath and print any exception so we can recover
-                // from a faulty listener
-                e.printStackTrace();
-            }
-        }
-    }
-
-    /**
-     * Resets the parser using the latest connection's reader. Reseting the parser is necessary
-     * when the plain connection has been secured or when a new opening stream element is going
-     * to be sent by the server.
-     */
-    private void resetParser() {
-        try {
-            parser = new MXParser();
-            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-            parser.setInput(connection.reader);
-        }
-        catch (XmlPullParserException xppe) {
-            xppe.printStackTrace();
-        }
-    }
-
-    /**
      * Parse top-level packets in order to process them further.
      *
      * @param thread the thread that is being used by the reader to parse incoming packets.
      */
-    private void parsePackets(Thread thread) {
+    protected void parsePackets(Thread thread) {
         try {
             int eventType = parser.getEventType();
             do {
@@ -377,59 +170,6 @@ class PacketReader {
         }
     }
 
-    /**
-     * Releases the connection ID lock so that the thread that was waiting can resume. The
-     * lock will be released when one of the following three conditions is met:<p>
-     *
-     * 1) An opening stream was sent from a non XMPP 1.0 compliant server
-     * 2) Stream features were received from an XMPP 1.0 compliant server that does not support TLS
-     * 3) TLS negotiation was successful
-     *
-     */
-    private void releaseConnectionIDLock() {
-        connectionSemaphore.release();
-    }
-
-    /**
-     * Processes a packet after it's been fully parsed by looping through the installed
-     * packet collectors and listeners and letting them examine the packet to see if
-     * they are a match with the filter.
-     *
-     * @param packet the packet to process.
-     */
-    private void processPacket(Packet packet) {
-        if (packet == null) {
-            return;
-        }
-
-        // Loop through all collectors and notify the appropriate ones.
-        for (PacketCollector collector: collectors) {
-            collector.processPacket(packet);
-        }
-
-        // Deliver the incoming packet to listeners.
-        listenerExecutor.submit(new ListenerNotification(packet));
-    }
-
-    private StreamError parseStreamError(XmlPullParser parser) throws IOException,
-            XmlPullParserException {
-        StreamError streamError = null;
-        boolean done = false;
-        while (!done) {
-            int eventType = parser.next();
-
-            if (eventType == XmlPullParser.START_TAG) {
-                streamError = new StreamError(parser.getName());
-            }
-            else if (eventType == XmlPullParser.END_TAG) {
-                if (parser.getName().equals("error")) {
-                    done = true;
-                }
-            }
-        }
-        return streamError;
-    }
-
     private void parseFeatures(XmlPullParser parser) throws Exception {
         boolean startTLSReceived = false;
         boolean startTLSRequired = false;
@@ -782,42 +522,4 @@ class PacketReader {
 
         return bind;
     }
-
-    /**
-     * A runnable to notify all listeners of a packet.
-     */
-    private class ListenerNotification implements Runnable {
-
-        private Packet packet;
-
-        public ListenerNotification(Packet packet) {
-            this.packet = packet;
-        }
-
-        public void run() {
-            for (ListenerWrapper listenerWrapper : listeners.values()) {
-                listenerWrapper.notifyListener(packet);
-            }
-        }
-    }
-
-    /**
-     * A wrapper class to associate a packet filter with a listener.
-     */
-    private static class ListenerWrapper {
-
-        private PacketListener packetListener;
-        private PacketFilter packetFilter;
-
-        public ListenerWrapper(PacketListener packetListener, PacketFilter packetFilter) {
-            this.packetListener = packetListener;
-            this.packetFilter = packetFilter;
-        }
-       
-        public void notifyListener(Packet packet) {
-            if (packetFilter == null || packetFilter.accept(packet)) {
-                packetListener.processPacket(packet);
-            }
-        }
-    }
-}
\ No newline at end of file
+}
diff --git a/source/org/jivesoftware/smack/PacketWriter.java b/source/org/jivesoftware/smack/PacketWriter.java
index da1a345..e1bfde5 100644
--- a/source/org/jivesoftware/smack/PacketWriter.java
+++ b/source/org/jivesoftware/smack/PacketWriter.java
@@ -42,7 +42,7 @@ class PacketWriter {
     private Thread writerThread;
     private Thread keepAliveThread;
     private Writer writer;
-    private XMPPConnection connection;
+    private AbstractConnection connection;
     private final BlockingQueue<Packet> queue;
     private boolean done;
     
@@ -68,7 +68,7 @@ class PacketWriter {
      *
      * @param connection the connection.
      */
-    protected PacketWriter(XMPPConnection connection) {
+    protected PacketWriter(AbstractConnection connection) {
         this.queue = new ArrayBlockingQueue<Packet>(500, true);
         this.connection = connection;
         init();
@@ -345,6 +345,10 @@ class PacketWriter {
         StringBuilder stream = new StringBuilder();
         stream.append("<stream:stream");
         stream.append(" to=\"").append(connection.serviceName).append("\"");
+        // If the connection is a Link-local connection, a 'from' attribute
+        // shoud be added.
+       if (connection instanceof XMPPLLConnection) 
+           stream.append(" from=\"").append(connection.getUser()).append("\"");
         stream.append(" xmlns=\"jabber:client\"");
         stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
         stream.append(" version=\"1.0\">");
@@ -456,4 +460,4 @@ class PacketWriter {
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/source/org/jivesoftware/smack/XMPPConnection.java b/source/org/jivesoftware/smack/XMPPConnection.java
index 1076f6a..98e2988 100644
--- a/source/org/jivesoftware/smack/XMPPConnection.java
+++ b/source/org/jivesoftware/smack/XMPPConnection.java
@@ -80,68 +80,11 @@ import java.util.concurrent.atomic.AtomicInteger;
  *
  * @author Matt Tucker
  */
-public class XMPPConnection {
-
-    /**
-     * Value that indicates whether debugging is enabled. When enabled, a debug
-     * window will apear for each new connection that will contain the following
-     * information:<ul>
-     * <li> Client Traffic -- raw XML traffic generated by Smack and sent to the server.
-     * <li> Server Traffic -- raw XML traffic sent by the server to the client.
-     * <li> Interpreted Packets -- shows XML packets from the server as parsed by Smack.
-     * </ul>
-     * <p/>
-     * Debugging can be enabled by setting this field to true, or by setting the Java system
-     * property <tt>smack.debugEnabled</tt> to true. The system property can be set on the
-     * command line such as "java SomeApp -Dsmack.debugEnabled=true".
-     */
-    public static boolean DEBUG_ENABLED = false;
-
+public class XMPPConnection extends AbstractConnection {
     private final static Set<ConnectionCreationListener> connectionEstablishedListeners =
             new CopyOnWriteArraySet<ConnectionCreationListener>();
 
-    // Counter to uniquely identify connections that are created. This is distinct from the
-    // connection ID, which is a value sent by the server once a connection is made.
-    private static AtomicInteger connectionCounter = new AtomicInteger(0);
-
-    // CallbackHandler to handle prompting for theh keystore password.
-    private CallbackHandler callbackHandler = null;
-
-    static {
-        // Use try block since we may not have permission to get a system
-        // property (for example, when an applet).
-        try {
-            DEBUG_ENABLED = Boolean.getBoolean("smack.debugEnabled");
-        }
-        catch (Exception e) {
-            // Ignore.
-        }
-        // Ensure the SmackConfiguration class is loaded by calling a method in it.
-        SmackConfiguration.getVersion();
-    }
-
-    private SmackDebugger debugger = null;
-
-    /**
-     * IP address or host name of the server. This information is only used when
-     * creating new socket connections to the server. If this information is not
-     * configured then it will be assumed that the host name matches the service name.
-     */
-    String host;
-    int port;
-    Socket socket;
-
-    /**
-     * Hostname of the XMPP server. Usually servers use the same service name as the name
-     * of the server. However, there are some servers like google where host would be
-     * talk.google.com and the serviceName would be gmail.com.
-     */
-    String serviceName;
-
-    int connectionCounterValue = connectionCounter.getAndIncrement();
-    String connectionID = null;
     private String user = null;
-    private boolean connected = false;
     /**
      * Flag that indicates if the user is currently authenticated with the server.
      */
@@ -154,15 +97,12 @@ public class XMPPConnection {
     private boolean anonymous = false;
     private boolean usingTLS = false;
 
-    PacketWriter packetWriter;
-    PacketReader packetReader;
-
     Roster roster = null;
     private AccountManager accountManager = null;
     private SASLAuthentication saslAuthentication = new SASLAuthentication(this);
 
-    Writer writer;
-    Reader reader;
+    // CallbackHandler to handle prompting for theh keystore password.
+    private CallbackHandler callbackHandler = null;
 
     /**
      * Collection of available stream compression methods offered by the server.
@@ -268,51 +208,6 @@ public class XMPPConnection {
 
 
     /**
-     * Returns the connection ID for this connection, which is the value set by the server
-     * when opening a XMPP stream. If the server does not set a connection ID, this value
-     * will be null. This value will be <tt>null</tt> if not connected to the server.
-     *
-     * @return the ID of this connection returned from the XMPP server or <tt>null</tt> if
-     *      not connected to the server.
-     */
-    public String getConnectionID() {
-        if (!isConnected()) {
-            return null;
-        }
-        return connectionID;
-    }
-
-    /**
-     * Returns the name of the service provided by the XMPP server for this connection. After
-     * authenticating with the server the returned value may be different.
-     *
-     * @return the name of the service provided by the XMPP server.
-     */
-    public String getServiceName() {
-        return serviceName;
-    }
-
-    /**
-     * Returns the host name of the server where the XMPP server is running. This would be the
-     * IP address of the server or a name that may be resolved by a DNS server.
-     *
-     * @return the host name of the server where the XMPP server is running.
-     */
-    public String getHost() {
-        return host;
-    }
-
-    /**
-     * Returns the port number of the XMPP server for this connection. The default port
-     * for normal connections is 5222. The default port for SSL connections is 5223.
-     *
-     * @return the port number of the XMPP server.
-     */
-    public int getPort() {
-        return port;
-    }
-
-    /**
      * Returns the full XMPP address of the user that is logged in to the connection or
      * <tt>null</tt> if not logged in yet. An XMPP address is in the form
      * username@server/resource.
@@ -578,15 +473,6 @@ public class XMPPConnection {
     }
 
     /**
-     * Returns true if currently connected to the XMPP server.
-     *
-     * @return true if connected.
-     */
-    public boolean isConnected() {
-        return connected;
-    }
-
-    /**
      * Returns true if the connection to the server has successfully negotiated TLS. Once TLS
      * has been negotiatied the connection has been secured. @see #isUsingTLS. 
      *
@@ -668,6 +554,17 @@ public class XMPPConnection {
     }
 
     /**
+     * Shuts down the connection by calling #shutdown(Presence) with a basic
+     * unavailable presence.
+     *
+     * @see #shutdown(Presence)
+     */
+    protected void shutdown() {
+        shutdown(new Presence(Presence.Type.unavailable));
+    }
+
+
+    /**
      * Closes the connection by setting presence to unavailable then closing the stream to
      * the XMPP server. The XMPPConnection can still be used for connecting to the server
      * again.<p>
@@ -718,144 +615,6 @@ public class XMPPConnection {
         packetReader.cleanup();
         packetReader = null;
     }
-
-    /**
-     * Sends the specified packet to the server.
-     *
-     * @param packet the packet to send.
-     */
-    public void sendPacket(Packet packet) {
-        if (!isConnected()) {
-            throw new IllegalStateException("Not connected to server.");
-        }
-        if (packet == null) {
-            throw new NullPointerException("Packet is null.");
-        }
-        packetWriter.sendPacket(packet);
-    }
-
-    /**
-     * Registers a packet listener with this connection. A packet filter determines
-     * which packets will be delivered to the listener. If the same packet listener
-     * is added again with a different filter, only the new filter will be used.
-     *
-     * @param packetListener the packet listener to notify of new packets.
-     * @param packetFilter   the packet filter to use.
-     */
-    public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) {
-        if (!isConnected()) {
-            throw new IllegalStateException("Not connected to server.");
-        }
-        packetReader.addPacketListener(packetListener, packetFilter);
-    }
-
-    /**
-     * Removes a packet listener from this connection.
-     *
-     * @param packetListener the packet listener to remove.
-     */
-    public void removePacketListener(PacketListener packetListener) {
-    	if (packetReader != null) {
-    		packetReader.removePacketListener(packetListener);
-    	}
-    }
-
-    /**
-     * Registers a packet listener with this connection. The listener will be
-     * notified of every packet that this connection sends. A packet filter determines
-     * which packets will be delivered to the listener. Note that the thread
-     * that writes packets will be used to invoke the listeners. Therefore, each
-     * packet listener should complete all operations quickly or use a different
-     * thread for processing.
-     *
-     * @param packetListener the packet listener to notify of sent packets.
-     * @param packetFilter   the packet filter to use.
-     */
-    public void addPacketWriterListener(PacketListener packetListener, PacketFilter packetFilter) {
-        if (!isConnected()) {
-            throw new IllegalStateException("Not connected to server.");
-        }
-        packetWriter.addPacketListener(packetListener, packetFilter);
-    }
-
-    /**
-     * Removes a packet listener from this connection.
-     *
-     * @param packetListener the packet listener to remove.
-     */
-    public void removePacketWriterListener(PacketListener packetListener) {
-    	if (packetWriter != null) {
-    		packetWriter.removePacketListener(packetListener);
-    	}
-    }
-
-    /**
-     * Registers a packet interceptor with this connection. The interceptor will be
-     * invoked every time a packet is about to be sent by this connection. Interceptors
-     * may modify the packet to be sent. A packet filter determines which packets
-     * will be delivered to the interceptor.
-     *
-     * @param packetInterceptor the packet interceptor to notify of packets about to be sent.
-     * @param packetFilter      the packet filter to use.
-     */
-    public void addPacketWriterInterceptor(PacketInterceptor packetInterceptor,
-            PacketFilter packetFilter) {
-        if (!isConnected()) {
-            throw new IllegalStateException("Not connected to server.");
-        }
-        packetWriter.addPacketInterceptor(packetInterceptor, packetFilter);
-    }
-
-    /**
-     * Removes a packet interceptor.
-     *
-     * @param packetInterceptor the packet interceptor to remove.
-     */
-    public void removePacketWriterInterceptor(PacketInterceptor packetInterceptor) {
-        packetWriter.removePacketInterceptor(packetInterceptor);
-    }
-
-    /**
-     * Creates a new packet collector for this connection. A packet filter determines
-     * which packets will be accumulated by the collector.
-     *
-     * @param packetFilter the packet filter to use.
-     * @return a new packet collector.
-     */
-    public PacketCollector createPacketCollector(PacketFilter packetFilter) {
-        return packetReader.createPacketCollector(packetFilter);
-    }
-
-    /**
-     * Adds a connection listener to this connection that will be notified when
-     * the connection closes or fails. The connection needs to already be connected
-     * or otherwise an IllegalStateException will be thrown.
-     *
-     * @param connectionListener a connection listener.
-     */
-    public void addConnectionListener(ConnectionListener connectionListener) {
-        if (!isConnected()) {
-            throw new IllegalStateException("Not connected to server.");
-        }
-        if (connectionListener == null) {
-            return;
-        }
-        if (!packetReader.connectionListeners.contains(connectionListener)) {
-            packetReader.connectionListeners.add(connectionListener);
-        }
-    }
-
-    /**
-     * Removes a connection listener from this connection.
-     *
-     * @param connectionListener a connection listener.
-     */
-    public void removeConnectionListener(ConnectionListener connectionListener) {
-    	if (packetReader != null) {
-    		packetReader.connectionListeners.remove(connectionListener);
-    	}
-    }
-
     /**
      * Adds a new listener that will be notified when new XMPPConnections are created. Note
      * that newly created connections will not be actually connected to the server.
diff --git a/source/org/jivesoftware/smack/XMPPLLConnection.java b/source/org/jivesoftware/smack/XMPPLLConnection.java
new file mode 100644
index 0000000..5a26632
--- /dev/null
+++ b/source/org/jivesoftware/smack/XMPPLLConnection.java
@@ -0,0 +1,446 @@
+package org.jivesoftware.smack;
+
+
+import org.jivesoftware.smack.debugger.SmackDebugger;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.packet.XMPPError;
+
+import java.io.*;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.lang.reflect.Constructor;
+import java.util.Date;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+
+/**
+ * Link-local XMPP connection according to XEP-0174 connection. Automatically
+ * created by LLService and closed by inactivity.
+ *
+ */
+public class XMPPLLConnection extends AbstractConnection // public for debugging reasons
+{
+    private final static Set<LLConnectionListener> linkLocalListeners =
+            new CopyOnWriteArraySet<LLConnectionListener>();
+    
+
+    private LLService service;
+    private LLPresence localPresence, remotePresence;
+    private boolean initiator;
+    private long lastActivity = 0;
+    protected XMPPLLConnection connection;
+    private Thread timeoutThread;
+
+    private LLConnectionConfiguration configuration;
+
+    /**
+     * Instantiate a new link-local connection. Use the config parameter to
+     * specify if the connection is acting as server or client.
+     *
+     * @param config specification about how the new connection is to be set up.
+     */
+    XMPPLLConnection(LLService service, LLConnectionConfiguration config) {
+        connection = this;
+        this.service = service;
+        configuration = config;
+        updateLastActivity();
+
+        // A timeout thread's purpose is to close down inactive connections
+        // after a certain amount of seconds (defaults to 15).
+        timeoutThread = new Thread() {
+            public void run() {
+                try {
+                    while (connection != null) {
+                        //synchronized (connection) {
+                            Thread.sleep(14000);
+                            long currentTime = new Date().getTime();
+                            if (currentTime - lastActivity > 15000) {
+                                shutdown();
+                                break;
+                            }
+                        //}
+                    }
+                } catch (InterruptedException ie) {
+                    shutdown();
+                }
+            }
+        };
+
+        timeoutThread.setName("Smack Link-local Connection Timeout (" + connection.connectionCounterValue + ")");
+        timeoutThread.setDaemon(true);
+
+
+        if (config.isInitiator()) {
+            // we are connecting to remote host
+            localPresence = config.getLocalPresence();
+            remotePresence = config.getRemotePresence();
+            serviceName = remotePresence.getServiceName();
+            initiator = true;
+        } else {
+            // a remote host connected to us
+            localPresence = config.getLocalPresence();
+            remotePresence = null;
+            serviceName = null;
+            initiator = false;
+            socket = config.getSocket();
+        }
+    }
+
+    /**
+     * Tells if this connection instance is the initiator.
+     *
+     * @return true if this instance is the one connecting to a remote peer.
+     */
+    public boolean isInitiator() {
+        return initiator;
+    }
+
+    /**
+     * Return the user name of the remote peer (service name).
+     *
+     * @return the remote hosts service name / username
+     */
+    public String getUser() {
+        // username is the service name of the local presence
+        return localPresence.getServiceName();
+    }
+
+    /**
+     * Sets the name of the service provided in the <stream:stream ...> from the remote peer.
+     *
+     * @param serviceName the name of the service
+     */
+    public void setServiceName(String serviceName) {
+        this.serviceName = serviceName;
+    }
+
+
+    /**
+     * Set the remote presence. Used when being connected,
+     * will not know the remote service name until stream is initiated.
+     *
+     * @param remotePresence presence information about the connecting client.
+     */
+    void setRemotePresence(LLPresence remotePresence) {
+        this.remotePresence = remotePresence;
+    }
+
+    /**
+     * Start listen for data and a stream tag.
+     */
+    void initListen() throws XMPPException {
+        initConnection();
+    }
+
+    /**
+     * Adds a listener that are notified when a new link-local connection
+     * has been established.
+     *
+     * @param listener A class implementing the LLConnectionListener interface.
+     */
+    public static void addLLConnectionListener(LLConnectionListener listener) {
+        linkLocalListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener from the new connection listener list.
+     *
+     * @param listener The class implementing the LLConnectionListener interface that
+     * is to be removed.
+     */
+    public static void removeLLConnectionListener(LLConnectionListener listener) {
+        linkLocalListeners.remove(listener);
+    }
+
+    /**
+     * Create a socket, connect to the remote peer and initiate a XMPP stream session.
+     */
+    void connect() throws XMPPException {
+        String host = remotePresence.getHost();
+        int port = remotePresence.getPort();
+
+        try {
+            socket = new Socket(host, port);
+        }
+        catch (UnknownHostException uhe) {
+            String errorMessage = "Could not connect to " + host + ":" + port + ".";
+            throw new XMPPException(errorMessage, new XMPPError(
+                    XMPPError.Condition.remote_server_timeout, errorMessage),
+                    uhe);
+        }
+        catch (IOException ioe) {
+            String errorMessage = "Error connecting to " + host + ":"
+                    + port + ".";
+            throw new XMPPException(errorMessage, new XMPPError(
+                    XMPPError.Condition.remote_server_error, errorMessage), ioe);
+        }
+        initConnection();
+
+        notifyLLListenersConnected();
+    }
+
+
+    /**
+     * Handles the opening of a stream after a remote client has connected and opened a stream.
+     * @throws XMPPException if service name is missing or service is unknown to the mDNS daemon.
+     */
+    public void streamInitiatingReceived() throws XMPPException {
+        if (serviceName == null) {
+            shutdown();
+        } else {
+            packetWriter = new PacketWriter(this);
+            if (debugger.getWriterListener() != null) {
+                packetWriter.addPacketListener(debugger.getWriterListener(), null);
+            }
+            packetWriter.startup();
+            notifyLLListenersConnected();
+        }
+    }
+
+    /**
+     * Notify new connection listeners that a new connection has been established.
+     */
+    private void notifyLLListenersConnected() {
+        for (LLConnectionListener listener : linkLocalListeners) {
+            listener.connectionCreated(this);
+        }
+    }
+
+    /**
+     * Update the timer telling when the last activity happend. Used by timeout
+     * thread to tell how long the connection has been inactive.
+     */
+    void updateLastActivity() {
+        lastActivity = new Date().getTime();
+    }
+
+    /**
+     * Sends the specified packet to the remote peer.
+     *
+     * @param packet the packet to send
+     */
+    @Override
+    public void sendPacket(Packet packet) {
+        updateLastActivity();
+        // always add the from='' attribute
+        packet.setFrom(getUser());
+        super.sendPacket(packet);
+    }
+
+    /**
+     * Initializes the connection by creating a packet reader and writer and opening a
+     * XMPP stream to the server.
+     *
+     * @throws XMPPException if establishing a connection to the server fails.
+     */
+    private void initConnection() throws XMPPException {
+        // Set the reader and writer instance variables
+        initReaderAndWriter();
+        timeoutThread.start();
+
+        try {
+            // Don't initialize packet writer until we know it's a valid connection
+            // unless we are the initiator. If we are NOT the initializer, we instead
+            // wait for a stream initiation before doing anything.
+            if (isInitiator())
+                packetWriter = new PacketWriter(this);
+
+            // Initialize packet reader
+            packetReader = new LLPacketReader(service, this);
+
+            // If debugging is enabled, we should start the thread that will listen for
+            // all packets and then log them.
+            // XXX FIXME
+            if (true) {//configuration.isDebuggerEnabled()) {
+                packetReader.addPacketListener(debugger.getReaderListener(), null);
+            }
+
+            // Make note of the fact that we're now connected.
+            connected = true;
+
+            // If we are the initiator start the packet writer. This will open a XMPP
+            // stream to the server. If not, a packet writer will be started after
+            // receiving an initial stream start tag.
+            if (isInitiator())
+                packetWriter.startup();
+            // Start the packet reader. The startup() method will block until we
+            // get an opening stream packet back from server.
+            packetReader.startup();
+        }
+        catch (XMPPException ex) {
+            // An exception occurred in setting up the connection. Make sure we shut down the
+            // readers and writers and close the socket.
+
+            if (packetWriter != null) {
+                try {
+                    packetWriter.shutdown();
+                }
+                catch (Throwable ignore) { /* ignore */ }
+                packetWriter = null;
+            }
+            if (packetReader != null) {
+                try {
+                    packetReader.shutdown();
+                }
+                catch (Throwable ignore) { /* ignore */ }
+                packetReader = null;
+            }
+            if (socket != null) {
+                try {
+                    socket.close();
+                }
+                catch (Exception e) { /* ignore */ }
+                socket = null;
+            }
+            // closing reader after socket since reader.close() blocks otherwise
+            if (reader != null) {
+                try {
+                    reader.close();
+                }
+                catch (Throwable ignore) { /* ignore */ }
+                reader = null;
+            }
+            if (writer != null) {
+                try {
+                    writer.close();
+                }
+                catch (Throwable ignore) {  /* ignore */ }
+                writer = null;
+            }
+            connected = false;
+
+            throw ex;        // Everything stoppped. Now throw the exception.
+        }
+    }
+
+    private void initReaderAndWriter() throws XMPPException {
+        try {
+            reader =
+                    new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
+            writer = new BufferedWriter(
+                    new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
+        }
+        catch (IOException ioe) {
+            throw new XMPPException(
+                    "XMPPError establishing connection with server.",
+                    new XMPPError(XMPPError.Condition.remote_server_error,
+                            "XMPPError establishing connection with server."),
+                    ioe);
+        }
+
+        // If debugging is enabled, we open a window and write out all network traffic.
+        if (true) {//configuration.isDebuggerEnabled()) {
+            if (debugger == null) {
+                // Detect the debugger class to use.
+                String className = null;
+                // Use try block since we may not have permission to get a system
+                // property (for example, when an applet).
+                try {
+                    className = System.getProperty("smack.debuggerClass");
+                }
+                catch (Throwable t) {
+                    // Ignore.
+                }
+                Class<?> debuggerClass = null;
+                if (className != null) {
+                    try {
+                        debuggerClass = Class.forName(className);
+                    }
+                    catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                }
+                if (debuggerClass == null) {
+                    try {
+                        debuggerClass =
+                                Class.forName("org.jivesoftware.smackx.debugger.EnhancedDebugger");
+                    }
+                    catch (Exception ex) {
+                        try {
+                            debuggerClass =
+                                    Class.forName("org.jivesoftware.smack.debugger.LiteDebugger");
+                        }
+                        catch (Exception ex2) {
+                            ex2.printStackTrace();
+                        }
+                    }
+                }
+                // Create a new debugger instance. If an exception occurs then disable the debugging
+                // option
+                try {
+                    Constructor<?> constructor = debuggerClass
+                            .getConstructor(XMPPLLConnection.class, Writer.class, Reader.class);
+                    debugger = (SmackDebugger) constructor.newInstance(this, writer, reader);
+                    reader = debugger.getReader();
+                    writer = debugger.getWriter();
+                }
+                catch (Exception e) {
+                    e.printStackTrace();
+                    DEBUG_ENABLED = false;
+                }
+            }
+            else {
+                // Obtain new reader and writer from the existing debugger
+                reader = debugger.newConnectionReader(reader);
+                writer = debugger.newConnectionWriter(writer);
+            }
+        }
+    }
+
+    protected void shutdown() {
+        connection = null;
+
+        if (packetReader != null)
+            packetReader.shutdown();
+        if (packetWriter != null)
+            packetWriter.shutdown();
+
+        // Wait 150 ms for processes to clean-up, then shutdown.
+        try {
+            Thread.sleep(150);
+        }
+        catch (Exception e) {
+            // Ignore.
+        }
+
+        // Close down the readers and writers.
+        if (reader != null) {
+            try {
+                reader.close();
+            }
+            catch (Throwable ignore) { /* ignore */ }
+            reader = null;
+        }
+        if (writer != null) {
+            try {
+                writer.close();
+            }
+            catch (Throwable ignore) { /* ignore */ }
+            writer = null;
+        }
+
+        try {
+            socket.close();
+        }
+        catch (Exception e) {
+            // Ignore.
+        }
+    } 
+
+    public void disconnect() {
+        // If not connected, ignore this request.
+        if (packetReader == null || packetWriter == null) {
+            return;
+        }
+
+        shutdown();
+
+        packetWriter.cleanup();
+        packetWriter = null;
+        packetReader.cleanup();
+        packetReader = null;
+    }
+}
diff --git a/source/org/jivesoftware/smack/debugger/LiteDebugger.java b/source/org/jivesoftware/smack/debugger/LiteDebugger.java
index 6326055..068338a 100644
--- a/source/org/jivesoftware/smack/debugger/LiteDebugger.java
+++ b/source/org/jivesoftware/smack/debugger/LiteDebugger.java
@@ -42,7 +42,7 @@ public class LiteDebugger implements SmackDebugger {
     private static final String NEWLINE = "\n";
 
     private JFrame frame = null;
-    private XMPPConnection connection = null;
+    private AbstractConnection connection = null;
 
     private PacketListener listener = null;
 
@@ -51,13 +51,21 @@ public class LiteDebugger implements SmackDebugger {
     private ReaderListener readerListener;
     private WriterListener writerListener;
 
-    public LiteDebugger(XMPPConnection connection, Writer writer, Reader reader) {
+    public LiteDebugger(AbstractConnection connection, Writer writer, Reader reader) {
         this.connection = connection;
         this.writer = writer;
         this.reader = reader;
         createDebug();
     }
 
+    public LiteDebugger(XMPPConnection connection, Writer writer, Reader reader) {
+        this((AbstractConnection)connection, writer, reader);
+    }
+
+    public LiteDebugger(XMPPLLConnection connection, Writer writer, Reader reader) {
+        this((AbstractConnection)connection, writer, reader);
+    }
+
     /**
      * Creates the debug process, which is a GUI window that displays XML traffic.
      */
diff --git a/source/org/jivesoftware/smack/util/Tuple.java b/source/org/jivesoftware/smack/util/Tuple.java
new file mode 100644
index 0000000..df31a1f
--- /dev/null
+++ b/source/org/jivesoftware/smack/util/Tuple.java
@@ -0,0 +1,11 @@
+package org.jivesoftware.smack.util;
+
+public class Tuple<A,B> {
+    public A a;
+    public B b;
+
+    public Tuple(A a, B b) {
+        this.a = a;
+        this.b = b;
+    }
+}
diff --git a/source/org/jivesoftware/smackx/LLServiceDiscoveryManager.java b/source/org/jivesoftware/smackx/LLServiceDiscoveryManager.java
new file mode 100644
index 0000000..8dad023
--- /dev/null
+++ b/source/org/jivesoftware/smackx/LLServiceDiscoveryManager.java
@@ -0,0 +1,325 @@
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.LLConnectionListener;
+import org.jivesoftware.smack.XMPPLLConnection;
+import org.jivesoftware.smack.LLService;
+import org.jivesoftware.smackx.packet.DiscoverInfo;
+import org.jivesoftware.smackx.packet.DiscoverItems;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.List;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+/** 
+ * LLServiceDiscoveryManager acts as a wrapper around ServiceDiscoveryManager
+ * as ServiceDiscoveryManager only creates an interface for requesting service
+ * information on existing connections. Simply said it creates new connections
+ * when needed,  uses already active connections  when appropriate and applies
+ * values to new connections.
+ *
+ * @author Jonas Ådahl
+ */
+public class LLServiceDiscoveryManager {
+    private static Map<String, NodeInformationProvider> nodeInformationProviders =
+            new ConcurrentHashMap<String, NodeInformationProvider>();
+    private static final List<String> features = new ArrayList<String>();
+    private LLService service;
+
+    static {
+        XMPPLLConnection.addLLConnectionListener(new ConnectionServiceMaintainer());
+    }
+
+    public LLServiceDiscoveryManager(LLService service) {
+        this.service = service;
+    }
+
+    /**
+     * Returns the name of the client that will be returned when asked for the client identity
+     * in a disco request. The name could be any value you need to identity this client.
+     * 
+     * @return the name of the client that will be returned when asked for the client identity
+     *          in a disco request.
+     */
+    public static String getIdentityName() {
+        return ServiceDiscoveryManager.getIdentityName();
+    }
+
+    /**
+     * Sets the name of the client that will be returned when asked for the client identity
+     * in a disco request. The name could be any value you need to identity this client.
+     * 
+     * @param name the name of the client that will be returned when asked for the client identity
+     *          in a disco request.
+     */
+    public static void setIdentityName(String name) {
+        ServiceDiscoveryManager.setIdentityType(name);
+    }
+
+    /**
+     * Returns the type of client that will be returned when asked for the client identity in a 
+     * disco request. The valid types are defined by the category client. Follow this link to learn 
+     * the possible types: <a href="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
+     * 
+     * @return the type of client that will be returned when asked for the client identity in a 
+     *          disco request.
+     */
+    public static String getIdentityType() {
+        return ServiceDiscoveryManager.getIdentityType();
+    }
+
+    /**
+     * Sets the type of client that will be returned when asked for the client identity in a 
+     * disco request. The valid types are defined by the category client. Follow this link to learn 
+     * the possible types: <a href="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
+     * 
+     * @param type the type of client that will be returned when asked for the client identity in a 
+     *          disco request.
+     */
+    public static void setIdentityType(String type) {
+        ServiceDiscoveryManager.setIdentityType(type);
+    }
+
+    /**
+     * Returns a new or already established connection to the given service name.
+     *
+     * @param serviceName remote service to which we wish to be connected to.
+     * @returns an established connection to the given service name.
+     */
+    private XMPPLLConnection getConnection(String serviceName) throws XMPPException {
+        return service.getConnection(serviceName);
+    }
+
+    /** 
+     * Returns a ServiceDiscoveryManager instance for a new or already established
+     * connection to the given service name.
+     *
+     * @param serviceName the name of the service we wish to get the ServiceDiscoveryManager instance for.
+     * @returns the ServiceDiscoveryManager instance.
+     */
+    private ServiceDiscoveryManager getInstance(String serviceName) throws XMPPException  {
+        return ServiceDiscoveryManager.getInstanceFor(getConnection(serviceName));
+    }
+
+    /**
+     * Returns the discovered information of a given XMPP entity addressed by its JID.
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @return the discovered information.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public DiscoverInfo discoverInfo(String serviceName) throws XMPPException {
+        return discoverInfo(serviceName, null);
+    }
+    /**
+     * Returns the discovered information of a given XMPP entity addressed by its JID and
+     * note attribute. Use this message only when trying to query information which is not 
+     * directly addressable.
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @param node the attribute that supplements the 'jid' attribute.
+     * @return the discovered information.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public DiscoverInfo discoverInfo(String serviceName, String node) throws XMPPException {
+        return getInstance(serviceName).discoverInfo(serviceName, node);
+    }
+
+    /**
+     * Returns the discovered items of a given XMPP entity addressed by its JID.
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @return the discovered information.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public DiscoverItems discoverItems(String entityID) throws XMPPException {
+        return discoverItems(entityID, null);
+    }
+
+    /**
+     * Returns the discovered items of a given XMPP entity addressed by its JID and
+     * note attribute. Use this message only when trying to query information which is not 
+     * directly addressable.
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @param node the attribute that supplements the 'jid' attribute.
+     * @return the discovered items.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public DiscoverItems discoverItems(String serviceName, String node) throws XMPPException {
+        return getInstance(serviceName).discoverItems(serviceName, node);
+    }
+
+    /**
+     * Sets the NodeInformationProvider responsible for providing information 
+     * (ie items) related to a given node. Every time this client receives a disco request
+     * regarding the items of a given node, the provider associated to that node will be the 
+     * responsible for providing the requested information.<p>
+     * 
+     * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
+     * NodeInformationProvider will provide information about the rooms where the user has joined. 
+     * 
+     * @param node the node whose items will be provided by the NodeInformationProvider.
+     * @param listener the NodeInformationProvider responsible for providing items related
+     *      to the node.
+     */
+    public void setNodeInformationProvider(String node,
+            NodeInformationProvider listener) throws XMPPException {
+        // store this NodeInformationProvider so we can add it to new connections.
+        nodeInformationProviders.put(node, listener);
+
+        // set for already active connections
+        for (XMPPLLConnection connection : service.getConnections())
+            ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(node, listener);
+    }
+
+    /**
+     * Removes the NodeInformationProvider responsible for providing information 
+     * (ie items) related to a given node. This means that no more information will be
+     * available for the specified node.
+     * 
+     * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
+     * NodeInformationProvider will provide information about the rooms where the user has joined. 
+     * 
+     * @param node the node to remove the associated NodeInformationProvider.
+     */
+    public void removeNodeInformationProvider(String node) throws XMPPException {
+        // remove from wrapper class
+        nodeInformationProviders.remove(node);
+
+        // remove from existing connections
+        for (XMPPLLConnection connection : service.getConnections())
+            ServiceDiscoveryManager.getInstanceFor(connection).removeNodeInformationProvider(node);
+    }
+
+    /**
+     * Returns the supported features by this XMPP entity.
+     * 
+     * @return an Iterator on the supported features by this XMPP entity.
+     */
+    public Iterator<String> getFeatures() {
+        synchronized (features) {
+            return Collections.unmodifiableList(new ArrayList<String>(features)).iterator();
+        }
+    }
+
+    /**
+     * Registers that a new feature is supported by this XMPP entity. When this client is 
+     * queried for its information the registered features will be answered.<p>
+     *
+     * Since no packet is actually sent to the server it is safe to perform this operation
+     * before logging to the server. In fact, you may want to configure the supported features
+     * before logging to the server so that the information is already available if it is required
+     * upon login.
+     *
+     * @param feature the feature to register as supported.
+     */
+    public void addFeature(String feature) {
+        synchronized (features) {
+            features.add(feature);
+        }
+    }
+
+    /**
+     * Removes the specified feature from the supported features by this XMPP entity.<p>
+     *
+     * Since no packet is actually sent to the server it is safe to perform this operation
+     * before logging to the server.
+     *
+     * @param feature the feature to remove from the supported features.
+     */
+    public void removeFeature(String feature) throws XMPPException {
+        synchronized (features) {
+            features.remove(feature);
+            for (XMPPLLConnection connection : service.getConnections())
+                ServiceDiscoveryManager.getInstanceFor(connection).removeFeature(feature);
+        }
+
+    }
+
+    /**
+     * Returns true if the specified feature is registered in the ServiceDiscoveryManager.
+     *
+     * @param feature the feature to look for.
+     * @return a boolean indicating if the specified featured is registered or not.
+     */
+    public boolean includesFeature(String feature) {
+        synchronized (features) {
+            return features.contains(feature);
+        }
+    }
+
+    /**
+     * Returns true if the server supports publishing of items. A client may wish to publish items
+     * to the server so that the server can provide items associated to the client. These items will
+     * be returned by the server whenever the server receives a disco request targeted to the bare
+     * address of the client (i.e. user@host.com).
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @return true if the server supports publishing of items.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public boolean canPublishItems(String entityID) throws XMPPException {
+        return getInstance(entityID).canPublishItems(entityID);
+    }
+
+    /**
+     * Publishes new items to a parent entity. The item elements to publish MUST have at least 
+     * a 'jid' attribute specifying the Entity ID of the item, and an action attribute which 
+     * specifies the action being taken for that item. Possible action values are: "update" and 
+     * "remove".
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @param discoverItems the DiscoveryItems to publish.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public void publishItems(String entityID, DiscoverItems discoverItems)
+            throws XMPPException {
+        publishItems(entityID, null, discoverItems);
+    }
+
+    /**
+     * Publishes new items to a parent entity and node. The item elements to publish MUST have at 
+     * least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which 
+     * specifies the action being taken for that item. Possible action values are: "update" and 
+     * "remove".
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @param node the attribute that supplements the 'jid' attribute.
+     * @param discoverItems the DiscoveryItems to publish.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public void publishItems(String entityID, String node, DiscoverItems discoverItems)
+            throws XMPPException {
+        getInstance(entityID).publishItems(entityID, node, discoverItems);
+    }
+
+    /**
+     * In case that a connection is unavailable we create a new connection
+     * and push the service discovery procedure until the new connection is
+     * established.
+     */
+    private static class ConnectionServiceMaintainer implements LLConnectionListener {
+
+        public void connectionCreated(XMPPLLConnection connection) {
+            ServiceDiscoveryManager manager =
+                ServiceDiscoveryManager.getInstanceFor(connection);
+
+            // set node information providers
+            for (Map.Entry<String,NodeInformationProvider> entry :
+                    nodeInformationProviders.entrySet()) {
+                manager.setNodeInformationProvider(entry.getKey(), entry.getValue());
+            }
+
+            // add features
+            synchronized (features) {
+                for (String feature : features) {
+                    manager.addFeature(feature);
+                }
+            }
+        }
+    }
+}
diff --git a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java
index 04fe944..0ef65b4 100644
--- a/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java
+++ b/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java
@@ -20,6 +20,8 @@
 
 package org.jivesoftware.smackx;
 
+import org.jivesoftware.smack.XMPPLLConnection;
+//import org.jivesoftware.smack.*;
 import org.jivesoftware.smack.*;
 import org.jivesoftware.smack.filter.PacketFilter;
 import org.jivesoftware.smack.filter.PacketIDFilter;
@@ -50,10 +52,10 @@ public class ServiceDiscoveryManager {
     private static String identityName = "Smack";
     private static String identityType = "pc";
 
-    private static Map<XMPPConnection, ServiceDiscoveryManager> instances =
-            new ConcurrentHashMap<XMPPConnection, ServiceDiscoveryManager>();
+    private static Map<AbstractConnection, ServiceDiscoveryManager> instances =
+            new ConcurrentHashMap<AbstractConnection, ServiceDiscoveryManager>();
 
-    private XMPPConnection connection;
+    private AbstractConnection connection;
     private final List<String> features = new ArrayList<String>();
     private DataForm extendedInfo = null;
     private Map<String, NodeInformationProvider> nodeInformationProviders =
@@ -61,6 +63,14 @@ public class ServiceDiscoveryManager {
 
     // Create a new ServiceDiscoveryManager on every established connection
     static {
+        // Add service discovery for Link-local connections.\
+        XMPPLLConnection.addLLConnectionListener(new LLConnectionListener() {
+            public void connectionCreated(XMPPLLConnection connection) {
+                new ServiceDiscoveryManager(connection);
+            }
+        });
+
+        // Add service discovery for normal XMPP c2s connections
         XMPPConnection.addConnectionCreationListener(new ConnectionCreationListener() {
             public void connectionCreated(XMPPConnection connection) {
                 new ServiceDiscoveryManager(connection);
@@ -69,24 +79,24 @@ public class ServiceDiscoveryManager {
     }
 
     /**
-     * Creates a new ServiceDiscoveryManager for a given XMPPConnection. This means that the 
+     * Creates a new ServiceDiscoveryManager for a given connection. This means that the 
      * service manager will respond to any service discovery request that the connection may
      * receive. 
      * 
      * @param connection the connection to which a ServiceDiscoveryManager is going to be created.
      */
-    public ServiceDiscoveryManager(XMPPConnection connection) {
+    public ServiceDiscoveryManager(AbstractConnection connection) {
         this.connection = connection;
         init();
     }
 
     /**
-     * Returns the ServiceDiscoveryManager instance associated with a given XMPPConnection.
+     * Returns the ServiceDiscoveryManager instance associated with a given connection.
      * 
      * @param connection the connection used to look for the proper ServiceDiscoveryManager.
-     * @return the ServiceDiscoveryManager associated with a given XMPPConnection.
+     * @return the ServiceDiscoveryManager associated with a given connection.
      */
-    public static ServiceDiscoveryManager getInstanceFor(XMPPConnection connection) {
+    public static ServiceDiscoveryManager getInstanceFor(AbstractConnection connection) {
         return instances.get(connection);
     }
 
@@ -561,4 +571,4 @@ public class ServiceDiscoveryManager {
             throw new XMPPException(result.getError());
         }
     }
-}
\ No newline at end of file
+}
diff --git a/test-mdns/org/itri/test/MDNSListener.java b/test-mdns/org/itri/test/MDNSListener.java
new file mode 100644
index 0000000..306d46d
--- /dev/null
+++ b/test-mdns/org/itri/test/MDNSListener.java
@@ -0,0 +1,27 @@
+package org.itri.test;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.packet.*;
+
+public class MDNSListener implements LLPresenceListener {
+
+    public void presenceNew(LLPresence pr) {
+        try {
+            System.out.println("New presence: " + pr.getServiceName() + 
+                    " (" + pr.getStatus() + ")");
+        } catch (Exception e) {
+            System.err.println(e);
+            e.printStackTrace();
+        }
+
+    }
+
+    public void presenceRemove(LLPresence pr) {
+        System.out.println("Removed presence: " + pr.getServiceName());
+    }
+
+
+    public void connectionCreated(AbstractConnection connection) {
+        System.err.println("Notification about a connection that was created...");
+    }
+}
diff --git a/test-mdns/org/itri/test/TestMDNS.java b/test-mdns/org/itri/test/TestMDNS.java
new file mode 100644
index 0000000..6c6dcf8
--- /dev/null
+++ b/test-mdns/org/itri/test/TestMDNS.java
@@ -0,0 +1,175 @@
+package org.itri.test;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smackx.*;
+import org.jivesoftware.smackx.packet.*;
+
+import javax.jmdns.*;
+//import javax.jmdns.impl.SocketListener;
+import java.io.*;
+import java.util.HashMap;
+import java.util.logging.*;
+import java.lang.Runtime;
+import java.util.logging.*;
+
+public class TestMDNS {
+    LLService service;
+    public static void main(String[] argv) {
+        Handler ch = new ConsoleHandler();
+        //System.out.println(ConsoleHandler.class.getName());
+        Logger.getLogger("").addHandler(ch);
+        //Logger.getLogger("javax.jmdns.impl.SocketListener").setLevel(Level.FINEST);
+        TestMDNS test = new TestMDNS();
+        test.run();
+    }
+
+    public void run() {
+        try {
+            // Initiate stdin buffer for reading commands (the fancy UI)
+            BufferedReader stdIn = new BufferedReader(
+                    new InputStreamReader(System.in));
+
+            // Create some kind of user name
+            String name = "smack-mdns@localhost";
+            try {
+                name = System.getenv("USERNAME") + "@" + java.net.InetAddress.getLocalHost().getHostName();
+            } catch (Exception e) {}
+
+            System.out.println("Link-local presens name set to '" + name + "'");
+            // Create a basic presence (only set name, and status to available)
+            LLPresence presence = new LLPresence(name);
+
+            System.out.println("Initiating Link-local service...");
+            // Create a XMPP Link-local service.
+            service = JmDNSService.create(presence);
+
+            // Adding presence listener.
+            service.addPresenceListener(new MDNSListener());
+
+            System.out.println("Prepering link-local service discovery...");
+            // As we want to play with service discovery, initiate the wrapper
+            LLServiceDiscoveryManager disco = new LLServiceDiscoveryManager(service);
+
+            // Start listen for Link-local chats
+            service.addLLChatListener(new MyChatListener());
+
+            // Add hook for doing a clean shut down
+            Runtime.getRuntime().addShutdownHook(new CloseDownService(service));
+
+
+            // Implement a user friendly interface.
+            String line;
+            boolean done = false;
+
+            System.out.println("Welcome to the Smack Link-local sample client interface!");
+            System.out.println("========================================================");
+            while (!done) {
+                try {
+                    System.out.print("> ");
+                    line = stdIn.readLine();
+                    if ("quit".equals(line))
+                        done = true;
+                    else if ("spam".equals(line))
+                        service.spam();
+                    else if ("msg".equals(line)) {
+                        System.out.print("Enter user: ");
+                        String user = stdIn.readLine();
+                        System.out.print("Enter message: ");
+                        String message = stdIn.readLine();
+                        LLChat chat = service.getChat(user);
+                        chat.sendMessage(message);
+                        System.out.println("Message sent.");
+                    }
+                    else if ("disco".equals(line)) {
+                        System.out.print("Enter user: ");
+                        String user = stdIn.readLine();
+                        DiscoverInfo info = disco.discoverInfo(user);
+                        System.out.println(" # Discovered: " + info.toXML());
+                    }
+                    else if ("status".equals(line)) {
+                        System.out.print("Enter new status: ");
+                        String status = stdIn.readLine();
+                        try {
+                            presence.setStatus(LLPresence.Mode.valueOf(status));
+                            service.updatePresence(presence);
+                        }
+                        catch (IllegalArgumentException iae) {
+                            System.err.println("Illegal status: " + status);
+                        }
+                    }
+                }
+                catch (XMPPException xe) {
+                    System.out.println("Cought XMPPException: " + xe);
+                    xe.printStackTrace();
+                    //done = true;
+                }
+                catch (IOException ioe) {
+                    System.out.println("Cought IOException: " + ioe);
+                    ioe.printStackTrace();
+                    done = true;
+                }
+            }
+            System.exit(0);
+        } catch (XMPPException xe) {
+            System.err.println(xe);
+        }
+    }
+
+    private class CloseDownService extends Thread {
+        LLService service;
+        
+        public CloseDownService(LLService service) {
+            this.service = service;
+        }
+
+        public void run () {
+            System.out.println("### Unregistering service....");
+            //service.makeUnavailable();
+            System.out.println("### Done, now closing daemon...");
+
+            try { Thread.sleep(1000); } catch (Exception e) { }
+            service.close();
+            System.out.println("### Done.");
+            try { Thread.sleep(2000); } catch (Exception e) { }
+            Thread.currentThread().getThreadGroup().list();
+        }
+    }
+
+    private class MyChatListener implements LLChatListener {
+        public MyChatListener() {}
+
+        public void newChat(LLChat chat) {
+            System.out.println("Discovered new chat being created.");
+            chat.addMessageListener(new MyMessageListener(chat));
+        }
+    }
+
+    private class MyMessageListener implements LLMessageListener {
+        LLChat chat;
+
+        MyMessageListener(LLChat chat) {
+            this.chat = chat;
+        }
+
+        public void processMessage(LLChat chat, Message message) {
+            try {
+                if (message.getBody().equals("ping")) {
+                    chat.sendMessage("pong");
+                    System.out.println("### received a ping, replied with pong.");
+                }
+                else if (message.getBody().equals("spam")) {
+                    service.spam();
+                }
+                else {
+                    System.out.println("### <" + chat.getServiceName() +
+                            "> " + message.getBody());
+                }
+            }
+            catch (XMPPException xe) {
+                System.out.println("Cought exception in message listener: " + xe);
+                xe.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/test-mdns/org/jivesoftware/smack/test/MDNSListener.java b/test-mdns/org/jivesoftware/smack/test/MDNSListener.java
new file mode 100644
index 0000000..49d085c
--- /dev/null
+++ b/test-mdns/org/jivesoftware/smack/test/MDNSListener.java
@@ -0,0 +1,27 @@
+package org.jivesoftware.smack.test;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.packet.*;
+
+public class MDNSListener implements LLPresenceListener {
+
+    public void presenceNew(LLPresence pr) {
+        try {
+            System.out.println("New presence: " + pr.getServiceName() + 
+                    " (" + pr.getStatus() + ")");
+        } catch (Exception e) {
+            System.err.println(e);
+            e.printStackTrace();
+        }
+
+    }
+
+    public void presenceRemove(LLPresence pr) {
+        System.out.println("Removed presence: " + pr.getServiceName());
+    }
+
+
+    public void connectionCreated(AbstractConnection connection) {
+        System.err.println("Notification about a connection that was created...");
+    }
+}
diff --git a/test-mdns/org/jivesoftware/smack/test/TestMDNS.java b/test-mdns/org/jivesoftware/smack/test/TestMDNS.java
new file mode 100644
index 0000000..9f88c0a
--- /dev/null
+++ b/test-mdns/org/jivesoftware/smack/test/TestMDNS.java
@@ -0,0 +1,175 @@
+package org.jivesoftware.smack.test;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smackx.*;
+import org.jivesoftware.smackx.packet.*;
+
+import javax.jmdns.*;
+//import javax.jmdns.impl.SocketListener;
+import java.io.*;
+import java.util.HashMap;
+import java.util.logging.*;
+import java.lang.Runtime;
+import java.util.logging.*;
+
+public class TestMDNS {
+    LLService service;
+    public static void main(String[] argv) {
+        Handler ch = new ConsoleHandler();
+        //System.out.println(ConsoleHandler.class.getName());
+        Logger.getLogger("").addHandler(ch);
+        //Logger.getLogger("javax.jmdns.impl.SocketListener").setLevel(Level.FINEST);
+        TestMDNS test = new TestMDNS();
+        test.run();
+    }
+
+    public void run() {
+        try {
+            // Initiate stdin buffer for reading commands (the fancy UI)
+            BufferedReader stdIn = new BufferedReader(
+                    new InputStreamReader(System.in));
+
+            // Create some kind of user name
+            String name = "smack-mdns@localhost";
+            try {
+                name = System.getenv("USERNAME") + "@" + java.net.InetAddress.getLocalHost().getHostName();
+            } catch (Exception e) {}
+
+            System.out.println("Link-local presens name set to '" + name + "'");
+            // Create a basic presence (only set name, and status to available)
+            LLPresence presence = new LLPresence(name);
+
+            System.out.println("Initiating Link-local service...");
+            // Create a XMPP Link-local service.
+            service = JmDNSService.create(presence);
+
+            // Adding presence listener.
+            service.addPresenceListener(new MDNSListener());
+
+            System.out.println("Prepering link-local service discovery...");
+            // As we want to play with service discovery, initiate the wrapper
+            LLServiceDiscoveryManager disco = new LLServiceDiscoveryManager(service);
+
+            // Start listen for Link-local chats
+            service.addLLChatListener(new MyChatListener());
+
+            // Add hook for doing a clean shut down
+            Runtime.getRuntime().addShutdownHook(new CloseDownService(service));
+
+
+            // Implement a user friendly interface.
+            String line;
+            boolean done = false;
+
+            System.out.println("Welcome to the Smack Link-local sample client interface!");
+            System.out.println("========================================================");
+            while (!done) {
+                try {
+                    System.out.print("> ");
+                    line = stdIn.readLine();
+                    if ("quit".equals(line))
+                        done = true;
+                    else if ("spam".equals(line))
+                        service.spam();
+                    else if ("msg".equals(line)) {
+                        System.out.print("Enter user: ");
+                        String user = stdIn.readLine();
+                        System.out.print("Enter message: ");
+                        String message = stdIn.readLine();
+                        LLChat chat = service.getChat(user);
+                        chat.sendMessage(message);
+                        System.out.println("Message sent.");
+                    }
+                    else if ("disco".equals(line)) {
+                        System.out.print("Enter user: ");
+                        String user = stdIn.readLine();
+                        DiscoverInfo info = disco.discoverInfo(user);
+                        System.out.println(" # Discovered: " + info.toXML());
+                    }
+                    else if ("status".equals(line)) {
+                        System.out.print("Enter new status: ");
+                        String status = stdIn.readLine();
+                        try {
+                            presence.setStatus(LLPresence.Mode.valueOf(status));
+                            service.updatePresence(presence);
+                        }
+                        catch (IllegalArgumentException iae) {
+                            System.err.println("Illegal status: " + status);
+                        }
+                    }
+                }
+                catch (XMPPException xe) {
+                    System.out.println("Cought XMPPException: " + xe);
+                    xe.printStackTrace();
+                    //done = true;
+                }
+                catch (IOException ioe) {
+                    System.out.println("Cought IOException: " + ioe);
+                    ioe.printStackTrace();
+                    done = true;
+                }
+            }
+            System.exit(0);
+        } catch (XMPPException xe) {
+            System.err.println(xe);
+        }
+    }
+
+    private class CloseDownService extends Thread {
+        LLService service;
+        
+        public CloseDownService(LLService service) {
+            this.service = service;
+        }
+
+        public void run () {
+            System.out.println("### Unregistering service....");
+            //service.makeUnavailable();
+            System.out.println("### Done, now closing daemon...");
+
+            try { Thread.sleep(1000); } catch (Exception e) { }
+            service.close();
+            System.out.println("### Done.");
+            try { Thread.sleep(2000); } catch (Exception e) { }
+            Thread.currentThread().getThreadGroup().list();
+        }
+    }
+
+    private class MyChatListener implements LLChatListener {
+        public MyChatListener() {}
+
+        public void newChat(LLChat chat) {
+            System.out.println("Discovered new chat being created.");
+            chat.addMessageListener(new MyMessageListener(chat));
+        }
+    }
+
+    private class MyMessageListener implements LLMessageListener {
+        LLChat chat;
+
+        MyMessageListener(LLChat chat) {
+            this.chat = chat;
+        }
+
+        public void processMessage(LLChat chat, Message message) {
+            try {
+                if (message.getBody().equals("ping")) {
+                    chat.sendMessage("pong");
+                    System.out.println("### received a ping, replied with pong.");
+                }
+                else if (message.getBody().equals("spam")) {
+                    service.spam();
+                }
+                else {
+                    System.out.println("### <" + chat.getServiceName() +
+                            "> " + message.getBody());
+                }
+            }
+            catch (XMPPException xe) {
+                System.out.println("Cought exception in message listener: " + xe);
+                xe.printStackTrace();
+            }
+        }
+    }
+}
