Add android smack source.

Change-Id: I49ce97136c17173c4ae3965c694af6e7bc49897d
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..b503e0e
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,28 @@
+# Copyright 2011, The Android Open Source Project
+#
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+##################################################
+# Static library
+##################################################
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := smackxmpp
+LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := 9
+
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/README b/README
new file mode 100644
index 0000000..c986de5
--- /dev/null
+++ b/README
@@ -0,0 +1,7 @@
+The directory contains the build environment of smack for Android (located in asmack-master/)
+and the generated source files (located in src/).
+
+To update to the latest smack source. Please run the following:
+
+./update.sh
+m -j24 smackxmpp
diff --git a/asmack-master/build.bash b/asmack-master/build.bash
index 874f238..d291220 100755
--- a/asmack-master/build.bash
+++ b/asmack-master/build.bash
@@ -542,6 +542,16 @@
 createVersionTag
 createbuildsrc
 patchsrc "patch"
+
+##
+## BEGIN Modification for android platform build system
+##
+echo done with android modifications
+exit
+##
+## END Modification for android platform build system
+##
+
 if $BUILD_JINGLE ; then
   patchsrc "jingle"
   JINGLE_ARGS="-Djingle=lib/jstun.jar"
diff --git a/src/META-INF/services/com.kenai.jbosh.HTTPSender b/src/META-INF/services/com.kenai.jbosh.HTTPSender
new file mode 100644
index 0000000..3608d8e
--- /dev/null
+++ b/src/META-INF/services/com.kenai.jbosh.HTTPSender
@@ -0,0 +1 @@
+com.kenai.jbosh.ApacheHTTPSender
diff --git a/src/com/kenai/jbosh/AbstractAttr.java b/src/com/kenai/jbosh/AbstractAttr.java
new file mode 100644
index 0000000..0d6f84c
--- /dev/null
+++ b/src/com/kenai/jbosh/AbstractAttr.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Abstract base class for creating BOSH attribute classes.  Concrete
+ * implementations of this class will naturally inherit the underlying
+ * type's behavior for {@code equals()}, {@code hashCode()},
+ * {@code toString()}, and {@code compareTo()}, allowing for the easy
+ * creation of objects which extend existing trivial types.  This was done
+ * to comply with the prefactoring rule declaring, "when you are being
+ * abstract, be abstract all the way".
+ *
+ * @param <T> type of the extension object
+ */
+abstract class AbstractAttr<T extends Comparable>
+    implements Comparable {
+
+    /**
+     * Captured value.
+     */
+    private final T value;
+
+    /**
+     * Creates a new encapsulated object instance.
+     *
+     * @param aValue encapsulated getValue
+     */
+    protected AbstractAttr(final T aValue) {
+        value = aValue;
+    }
+
+    /**
+     * Gets the encapsulated data value.
+     *
+     * @return data value
+     */
+    public final T getValue() {
+        return value;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Object method overrides:
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param otherObj object to compare to
+     * @return true if the objects are equal, false otherwise
+     */
+    @Override
+    public boolean equals(final Object otherObj) {
+        if (otherObj == null) {
+            return false;
+        } else if (otherObj instanceof AbstractAttr) {
+            AbstractAttr other =
+                    (AbstractAttr) otherObj;
+            return value.equals(other.value);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return hashCode of the encapsulated object
+     */
+    @Override
+    public int hashCode() {
+        return value.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return string representation of the encapsulated object
+     */
+    @Override
+    public String toString() {
+        return value.toString();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Comparable interface:
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param otherObj object to compare to
+     * @return -1, 0, or 1
+     */
+    @SuppressWarnings("unchecked")
+    public int compareTo(final Object otherObj) {
+        if (otherObj == null) {
+            return 1;
+        } else {
+            return value.compareTo(otherObj);
+        }
+    }
+
+}
diff --git a/src/com/kenai/jbosh/AbstractBody.java b/src/com/kenai/jbosh/AbstractBody.java
new file mode 100644
index 0000000..4d66c8c
--- /dev/null
+++ b/src/com/kenai/jbosh/AbstractBody.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Class representing a single message to or from the BOSH connection
+ * manager (CM).
+ * <p/>
+ * These messages consist of a single {@code body} element
+ * (qualified within the BOSH namespace:
+ * {@code http://jabber.org/protocol/httpbind}) and contain zero or more
+ * child elements (of any namespace).  These child elements constitute the
+ * message payload.
+ * <p/>
+ * In addition to the message payload, the attributes of the wrapper
+ * {@code body} element may also need to be used as part of the communication
+ * protocol being implemented on top of BOSH, or to define additional
+ * namespaces used by the child "payload" elements.  These attributes are
+ * exposed via accessors.
+ */
+public abstract class AbstractBody {
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constructor:
+
+    /**
+     * Restrict subclasses to the local package.
+     */
+    AbstractBody() {
+        // Empty
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Public methods:
+
+    /**
+     * Get a set of all defined attribute names.
+     *
+     * @return set of qualified attribute names
+     */
+    public final Set<BodyQName> getAttributeNames() {
+        Map<BodyQName, String> attrs = getAttributes();
+        return Collections.unmodifiableSet(attrs.keySet());
+    }
+
+    /**
+     * Get the value of the specified attribute.
+     *
+     * @param attr name of the attribute to retriece
+     * @return attribute value, or {@code null} if not defined
+     */
+    public final String getAttribute(final BodyQName attr) {
+        Map<BodyQName, String> attrs = getAttributes();
+        return attrs.get(attr);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Abstract methods:
+
+    /**
+     * Get a map of all defined attribute names with their corresponding values.
+     *
+     * @return map of qualified attributes
+     */
+    public abstract Map<BodyQName, String> getAttributes();
+
+    /**
+     * Get an XML String representation of this message. 
+     *
+     * @return XML string representing the body message
+     */
+    public abstract String toXML();
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Package-private methods:
+
+    /**
+     * Returns the qualified name of the root/wrapper element.
+     *
+     * @return qualified name
+     */
+    static BodyQName getBodyQName() {
+        return BodyQName.createBOSH("body");
+    }
+
+}
diff --git a/src/com/kenai/jbosh/AbstractIntegerAttr.java b/src/com/kenai/jbosh/AbstractIntegerAttr.java
new file mode 100644
index 0000000..1b827f9
--- /dev/null
+++ b/src/com/kenai/jbosh/AbstractIntegerAttr.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Abstract base class for attribute implementations based on {@code Integer}
+ * types.  Additional support for parsing of integer values from their
+ * {@code String} representations as well as callback handling of value
+ * validity checks are also provided.
+ */
+abstract class AbstractIntegerAttr extends AbstractAttr<Integer> {
+    
+    /**
+     * Creates a new attribute object.
+     * 
+     * @param val attribute value
+     * @throws BOSHException on parse or validation failure
+     */
+    protected AbstractIntegerAttr(final int val) throws BOSHException {
+        super(Integer.valueOf(val));
+    }
+    
+    /**
+     * Creates a new attribute object.
+     *
+     * @param val attribute value in string form
+     * @throws BOSHException on parse or validation failure
+     */
+    protected AbstractIntegerAttr(final String val) throws BOSHException {
+        super(parseInt(val));
+    }
+
+    /**
+     * Utility method intended to be called by concrete implementation
+     * classes from within the {@code check()} method when the concrete
+     * class needs to ensure that the integer value does not drop below
+     * the specified minimum value.
+     *
+     * @param minVal minimum value to allow
+     * @throws BOSHException if the integer value is below the specific
+     *  minimum
+     */
+    protected final void checkMinValue(int minVal) throws BOSHException {
+        int intVal = getValue();
+        if (intVal < minVal) {
+            throw(new BOSHException(
+                    "Illegal attribute value '" + intVal + "' provided.  "
+                    + "Must be >= " + minVal));
+        }
+    }
+
+    /**
+     * Utility method to parse a {@code String} into an {@code Integer},
+     * converting any possible {@code NumberFormatException} thrown into
+     * a {@code BOSHException}.
+     *
+     * @param str string to parse
+     * @return integer value
+     * @throws BOSHException on {@code NumberFormatException}
+     */
+    private static int parseInt(final String str) throws BOSHException {
+        try {
+            return Integer.parseInt(str);
+        } catch (NumberFormatException nfx) {
+            throw(new BOSHException(
+                    "Could not parse an integer from the value provided: "
+                    + str,
+                    nfx));
+        }
+    }
+
+    /**
+     * Returns the native {@code int} value of the underlying {@code Integer}.
+     * Will throw {@code NullPointerException} if the underlying
+     * integer was {@code null}.
+     *
+     * @return native {@code int} value
+     */
+    public int intValue() {
+        return getValue().intValue();
+    }
+
+}
diff --git a/src/com/kenai/jbosh/ApacheHTTPResponse.java b/src/com/kenai/jbosh/ApacheHTTPResponse.java
new file mode 100644
index 0000000..9f6731f
--- /dev/null
+++ b/src/com/kenai/jbosh/ApacheHTTPResponse.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2009 Guenther Niess
+ *
+ * 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 com.kenai.jbosh;
+
+import java.io.IOException;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ByteArrayEntity;
+
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+
+final class ApacheHTTPResponse implements HTTPResponse {
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constants:
+
+    /**
+     * Name of the accept encoding header.
+     */
+    private static final String ACCEPT_ENCODING = "Accept-Encoding";
+
+    /**
+     * Value to use for the ACCEPT_ENCODING header.
+     */
+    private static final String ACCEPT_ENCODING_VAL =
+            ZLIBCodec.getID() + ", " + GZIPCodec.getID();
+
+    /**
+     * Name of the character set to encode the body to/from.
+     */
+    private static final String CHARSET = "UTF-8";
+
+    /**
+     * Content type to use when transmitting the body data.
+     */
+    private static final String CONTENT_TYPE = "text/xml; charset=utf-8";
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Class variables:
+
+    /**
+     * Lock used for internal synchronization.
+     */
+    private final Lock lock = new ReentrantLock();
+
+    /**
+     * The execution state of an HTTP process.
+     */
+    private final HttpContext context;
+
+    /**
+     * HttpClient instance to use to communicate.
+     */
+    private final HttpClient client;
+
+    /**
+     * The HTTP POST request is sent to the server.
+     */
+    private final HttpPost post;
+
+    /**
+     * A flag which indicates if the transmission was already done.
+     */
+    private boolean sent;
+
+    /**
+     * Exception to throw when the response data is attempted to be accessed,
+     * or {@code null} if no exception should be thrown.
+     */
+    private BOSHException toThrow;
+
+    /**
+     * The response body which was received from the server or {@code null}
+     * if that has not yet happened.
+     */
+    private AbstractBody body;
+
+    /**
+     * The HTTP response status code.
+     */
+    private int statusCode;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constructors:
+
+    /**
+     * Create and send a new request to the upstream connection manager,
+     * providing deferred access to the results to be returned.
+     *
+     * @param client client instance to use when sending the request
+     * @param cfg client configuration
+     * @param params connection manager parameters from the session creation
+     *  response, or {@code null} if the session has not yet been established
+     * @param request body of the client request
+     */
+    ApacheHTTPResponse(
+            final HttpClient client,
+            final BOSHClientConfig cfg,
+            final CMSessionParams params,
+            final AbstractBody request) {
+        super();
+        this.client = client;
+        this.context = new BasicHttpContext();
+        this.post = new HttpPost(cfg.getURI().toString());
+        this.sent = false;
+
+        try {
+            String xml = request.toXML();
+            byte[] data = xml.getBytes(CHARSET);
+
+            String encoding = null;
+            if (cfg.isCompressionEnabled() && params != null) {
+                AttrAccept accept = params.getAccept();
+                if (accept != null) {
+                    if (accept.isAccepted(ZLIBCodec.getID())) {
+                        encoding = ZLIBCodec.getID();
+                        data = ZLIBCodec.encode(data);
+                    } else if (accept.isAccepted(GZIPCodec.getID())) {
+                        encoding = GZIPCodec.getID();
+                        data = GZIPCodec.encode(data);
+                    }
+                }
+            }
+
+            ByteArrayEntity entity = new ByteArrayEntity(data);
+            entity.setContentType(CONTENT_TYPE);
+            if (encoding != null) {
+                entity.setContentEncoding(encoding);
+            }
+            post.setEntity(entity);
+            if (cfg.isCompressionEnabled()) {
+                post.setHeader(ACCEPT_ENCODING, ACCEPT_ENCODING_VAL);
+            }
+        } catch (Exception e) {
+            toThrow = new BOSHException("Could not generate request", e);
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // HTTPResponse interface methods:
+
+    /**
+     * Abort the client transmission and response processing.
+     */
+    public void abort() {
+        if (post != null) {
+            post.abort();
+            toThrow = new BOSHException("HTTP request aborted");
+        }
+    }
+
+    /**
+     * Wait for and then return the response body.
+     *
+     * @return body of the response
+     * @throws InterruptedException if interrupted while awaiting the response
+     * @throws BOSHException on communication failure
+     */
+    public AbstractBody getBody() throws InterruptedException, BOSHException {
+        if (toThrow != null) {
+            throw(toThrow);
+        }
+        lock.lock();
+        try {
+            if (!sent) {
+                awaitResponse();
+            }
+        } finally {
+            lock.unlock();
+        }
+        return body;
+    }
+
+    /**
+     * Wait for and then return the response HTTP status code.
+     *
+     * @return HTTP status code of the response
+     * @throws InterruptedException if interrupted while awaiting the response
+     * @throws BOSHException on communication failure
+     */
+    public int getHTTPStatus() throws InterruptedException, BOSHException {
+        if (toThrow != null) {
+            throw(toThrow);
+        }
+        lock.lock();
+        try {
+            if (!sent) {
+                awaitResponse();
+            }
+        } finally {
+            lock.unlock();
+        }
+        return statusCode;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Package-private methods:
+
+    /**
+     * Await the response, storing the result in the instance variables of
+     * this class when they arrive.
+     *
+     * @throws InterruptedException if interrupted while awaiting the response
+     * @throws BOSHException on communication failure
+     */
+    private synchronized void awaitResponse() throws BOSHException {
+        HttpEntity entity = null;
+        try {
+            HttpResponse httpResp = client.execute(post, context);
+            entity = httpResp.getEntity();
+            byte[] data = EntityUtils.toByteArray(entity);
+            String encoding = entity.getContentEncoding() != null ?
+                    entity.getContentEncoding().getValue() :
+                    null;
+            if (ZLIBCodec.getID().equalsIgnoreCase(encoding)) {
+                data = ZLIBCodec.decode(data);
+            } else if (GZIPCodec.getID().equalsIgnoreCase(encoding)) {
+                data = GZIPCodec.decode(data);
+            }
+            body = StaticBody.fromString(new String(data, CHARSET));
+            statusCode = httpResp.getStatusLine().getStatusCode();
+            sent = true;
+        } catch (IOException iox) {
+            abort();
+            toThrow = new BOSHException("Could not obtain response", iox);
+            throw(toThrow);
+        } catch (RuntimeException ex) {
+            abort();
+            throw(ex);
+        }
+    }
+}
diff --git a/src/com/kenai/jbosh/ApacheHTTPSender.java b/src/com/kenai/jbosh/ApacheHTTPSender.java
new file mode 100644
index 0000000..b3d3c93
--- /dev/null
+++ b/src/com/kenai/jbosh/ApacheHTTPSender.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2009 Guenther Niess
+ *
+ * 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 com.kenai.jbosh;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.http.HttpHost;
+import org.apache.http.HttpVersion;
+import org.apache.http.client.HttpClient;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.params.ConnManagerParams;
+import org.apache.http.conn.params.ConnRoutePNames;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+
+/**
+ * Implementation of the {@code HTTPSender} interface which uses the
+ * Apache HttpClient API to send messages to the connection manager.
+ */
+final class ApacheHTTPSender implements HTTPSender {
+
+    /**
+     * Lock used for internal synchronization.
+     */
+    private final Lock lock = new ReentrantLock();
+
+    /**
+     * Session configuration.
+     */
+    private BOSHClientConfig cfg;
+
+    /**
+     * HttpClient instance to use to communicate.
+     */
+    private HttpClient httpClient;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constructors:
+
+    /**
+     * Prevent construction apart from our package.
+     */
+    ApacheHTTPSender() {
+        // Load Apache HTTP client class
+        HttpClient.class.getName();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // HTTPSender interface methods:
+
+    /**
+     * {@inheritDoc}
+     */
+    public void init(final BOSHClientConfig session) {
+        lock.lock();
+        try {
+            cfg = session;
+            httpClient = initHttpClient(session);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void destroy() {
+        lock.lock();
+        try {
+            if (httpClient != null) {
+                httpClient.getConnectionManager().shutdown();
+            }
+        } finally {
+            cfg = null;
+            httpClient = null;
+            lock.unlock();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public HTTPResponse send(
+            final CMSessionParams params,
+            final AbstractBody body) {
+        HttpClient mClient;
+        BOSHClientConfig mCfg;
+        lock.lock();
+        try {
+            if (httpClient == null) {
+                httpClient = initHttpClient(cfg);
+            }
+            mClient = httpClient;
+            mCfg = cfg;
+        } finally {
+            lock.unlock();
+        }
+        return new ApacheHTTPResponse(mClient, mCfg, params, body);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Package-private methods:
+
+    private synchronized HttpClient initHttpClient(final BOSHClientConfig config) {
+        // Create and initialize HTTP parameters
+        HttpParams params = new BasicHttpParams();
+        ConnManagerParams.setMaxTotalConnections(params, 100);
+        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
+        HttpProtocolParams.setUseExpectContinue(params, false);
+        if (config != null &&
+                config.getProxyHost() != null &&
+                config.getProxyPort() != 0) {
+            HttpHost proxy = new HttpHost(
+                    config.getProxyHost(),
+                    config.getProxyPort());
+            params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
+        }
+
+        // Create and initialize scheme registry 
+        SchemeRegistry schemeRegistry = new SchemeRegistry();
+        schemeRegistry.register(
+                new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
+            SSLSocketFactory sslFactory = SSLSocketFactory.getSocketFactory();
+            sslFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
+            schemeRegistry.register(
+                    new Scheme("https", sslFactory, 443));
+
+        // Create an HttpClient with the ThreadSafeClientConnManager.
+        // This connection manager must be used if more than one thread will
+        // be using the HttpClient.
+        ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
+        return new DefaultHttpClient(cm, params);
+    }
+}
diff --git a/src/com/kenai/jbosh/AttrAccept.java b/src/com/kenai/jbosh/AttrAccept.java
new file mode 100644
index 0000000..4f767df
--- /dev/null
+++ b/src/com/kenai/jbosh/AttrAccept.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Data type representing the getValue of the {@code accept} attribute of the
+ * {@code bosh} element.
+ */
+final class AttrAccept extends AbstractAttr<String> {
+
+    /**
+     * Array of the accepted encodings.
+     */
+    private final String[] encodings;
+
+    /**
+     * Creates a new attribute object.
+     * 
+     * @param val attribute getValue
+     * @throws BOSHException on parse or validation failure
+     */
+    private AttrAccept(final String val) {
+        super(val);
+        encodings = val.split("[\\s,]+");
+    }
+    
+    /**
+     * Creates a new attribute instance from the provided String.
+     * 
+     * @param str string representation of the attribute
+     * @return attribute instance or {@code null} if provided string is
+     *  {@code null}
+     * @throws BOSHException on parse or validation failure
+     */
+    static AttrAccept createFromString(final String str)
+    throws BOSHException {
+        if (str == null) {
+            return null;
+        } else {
+            return new AttrAccept(str);
+        }
+    }
+
+    /**
+     * Determines whether or not the specified encoding is supported.
+     *
+     * @param name encoding name
+     * @result {@code true} if the encoding is accepted, {@code false}
+     *  otherwise
+     */
+    boolean isAccepted(final String name) {
+        for (String str : encodings) {
+            if (str.equalsIgnoreCase(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/src/com/kenai/jbosh/AttrAck.java b/src/com/kenai/jbosh/AttrAck.java
new file mode 100644
index 0000000..6cfe22b
--- /dev/null
+++ b/src/com/kenai/jbosh/AttrAck.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Data type representing the getValue of the {@code ack} attribute of the
+ * {@code bosh} element.
+ */
+final class AttrAck extends AbstractAttr<String> {
+
+    /**
+     * Creates a new attribute object.
+     * 
+     * @param val attribute getValue
+     * @throws BOSHException on parse or validation failure
+     */
+    private AttrAck(final String val) throws BOSHException {
+        super(val);
+    }
+    
+    /**
+     * Creates a new attribute instance from the provided String.
+     * 
+     * @param str string representation of the attribute
+     * @return attribute instance or {@code null} if provided string is
+     *  {@code null}
+     * @throws BOSHException on parse or validation failure
+     */
+    static AttrAck createFromString(final String str)
+    throws BOSHException {
+        if (str == null) {
+            return null;
+        } else {
+            return new AttrAck(str);
+        }
+    }
+
+}
diff --git a/src/com/kenai/jbosh/AttrCharsets.java b/src/com/kenai/jbosh/AttrCharsets.java
new file mode 100644
index 0000000..45ce78c
--- /dev/null
+++ b/src/com/kenai/jbosh/AttrCharsets.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Data type representing the getValue of the {@code charsets} attribute of the
+ * {@code bosh} element.
+ */
+final class AttrCharsets extends AbstractAttr<String> {
+
+    /**
+     * Array of the accepted character sets.
+     */
+    private final String[] charsets;
+
+    /**
+     * Creates a new attribute object.
+     * 
+     * @param val attribute getValue
+     */
+    private AttrCharsets(final String val) {
+        super(val);
+        charsets = val.split("\\ +");
+    }
+    
+    /**
+     * Creates a new attribute instance from the provided String.
+     * 
+     * @param str string representation of the attribute
+     * @return attribute instance or {@code null} if provided string is
+     *  {@code null}
+     */
+    static AttrCharsets createFromString(final String str) {
+        if (str == null) {
+            return null;
+        } else {
+            return new AttrCharsets(str);
+        }
+    }
+
+    /**
+     * Determines whether or not the specified charset is supported.
+     *
+     * @param name encoding name
+     * @result {@code true} if the encoding is accepted, {@code false}
+     *  otherwise
+     */
+    boolean isAccepted(final String name) {
+        for (String str : charsets) {
+            if (str.equalsIgnoreCase(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/src/com/kenai/jbosh/AttrHold.java b/src/com/kenai/jbosh/AttrHold.java
new file mode 100644
index 0000000..56f21dd
--- /dev/null
+++ b/src/com/kenai/jbosh/AttrHold.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Data type representing the getValue of the {@code hold} attribute of the
+ * {@code bosh} element.
+ */
+final class AttrHold extends AbstractIntegerAttr {
+    
+    /**
+     * Creates a new attribute object.
+     * 
+     * @param val attribute getValue
+     * @throws BOSHException on parse or validation failure
+     */
+    private AttrHold(final String val) throws BOSHException {
+        super(val);
+        checkMinValue(0);
+    }
+
+    /**
+     * Creates a new attribute instance from the provided String.
+     * 
+     * @param str string representation of the attribute
+     * @return attribute instance or {@code null} if provided string is
+     *  {@code null}
+     * @throws BOSHException on parse or validation failure
+     */
+    static AttrHold createFromString(final String str)
+    throws BOSHException {
+        if (str == null) {
+            return null;
+        } else {
+            return new AttrHold(str);
+        }
+    }
+    
+}
diff --git a/src/com/kenai/jbosh/AttrInactivity.java b/src/com/kenai/jbosh/AttrInactivity.java
new file mode 100644
index 0000000..14ab7d4
--- /dev/null
+++ b/src/com/kenai/jbosh/AttrInactivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Data type representing the value of the {@code inactivity} attribute of the
+ * {@code bosh} element.
+ */
+final class AttrInactivity extends AbstractIntegerAttr {
+
+    /**
+     * Creates a new attribute object.
+     * 
+     * @param val attribute value
+     * @throws BOSHException on parse or validation failure
+     */
+    private AttrInactivity(final String val) throws BOSHException {
+        super(val);
+        checkMinValue(0);
+    }
+
+    /**
+     * Creates a new attribute instance from the provided String.
+     * 
+     * @param str string representation of the attribute
+     * @return instance of the attribute for the specified string, or
+     *  {@code null} if input string is {@code null}
+     * @throws BOSHException on parse or validation failure
+     */
+    static AttrInactivity createFromString(final String str)
+    throws BOSHException {
+        if (str == null) {
+            return null;
+        } else {
+            return new AttrInactivity(str);
+        }
+    }
+    
+}
diff --git a/src/com/kenai/jbosh/AttrMaxPause.java b/src/com/kenai/jbosh/AttrMaxPause.java
new file mode 100644
index 0000000..8d1d98b
--- /dev/null
+++ b/src/com/kenai/jbosh/AttrMaxPause.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Data type representing the getValue of the {@code maxpause} attribute of the
+ * {@code bosh} element.
+ */
+final class AttrMaxPause extends AbstractIntegerAttr {
+    
+    /**
+     * Creates a new attribute object.
+     * 
+     * @param val attribute getValue
+     * @throws BOSHException on parse or validation failure
+     */
+    private AttrMaxPause(final String val) throws BOSHException {
+        super(val);
+        checkMinValue(1);
+    }
+
+    /**
+     * Creates a new attribute instance from the provided String.
+     * 
+     * @param str string representation of the attribute
+     * @return attribute instance or {@code null} if provided string is
+     *  {@code null}
+     * @throws BOSHException on parse or validation failure
+     */
+    static AttrMaxPause createFromString(final String str)
+    throws BOSHException {
+        if (str == null) {
+            return null;
+        } else {
+            return new AttrMaxPause(str);
+        }
+    }
+    
+    /**
+     * Get the max pause time in milliseconds.
+     *
+     * @return pause tme in milliseconds
+     */
+    public int getInMilliseconds() {
+        return (int) TimeUnit.MILLISECONDS.convert(
+                intValue(), TimeUnit.SECONDS);
+    }
+
+}
diff --git a/src/com/kenai/jbosh/AttrPause.java b/src/com/kenai/jbosh/AttrPause.java
new file mode 100644
index 0000000..5fb3282
--- /dev/null
+++ b/src/com/kenai/jbosh/AttrPause.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Data type representing the getValue of the {@code pause} attribute of the
+ * {@code bosh} element.
+ */
+final class AttrPause extends AbstractIntegerAttr {
+    
+    /**
+     * Creates a new attribute object.
+     * 
+     * @param val attribute getValue
+     * @throws BOSHException on parse or validation failure
+     */
+    private AttrPause(final String val) throws BOSHException {
+        super(val);
+        checkMinValue(1);
+    }
+
+    /**
+     * Creates a new attribute instance from the provided String.
+     * 
+     * @param str string representation of the attribute
+     * @return attribute instance or {@code null} if provided string is
+     *  {@code null}
+     * @throws BOSHException on parse or validation failure
+     */
+    static AttrPause createFromString(final String str)
+    throws BOSHException {
+        if (str == null) {
+            return null;
+        } else {
+            return new AttrPause(str);
+        }
+    }
+    
+    /**
+     * Get the pause time in milliseconds.
+     *
+     * @return pause tme in milliseconds
+     */
+    public int getInMilliseconds() {
+        return (int) TimeUnit.MILLISECONDS.convert(
+                intValue(), TimeUnit.SECONDS);
+    }
+
+}
diff --git a/src/com/kenai/jbosh/AttrPolling.java b/src/com/kenai/jbosh/AttrPolling.java
new file mode 100644
index 0000000..3f0b08d
--- /dev/null
+++ b/src/com/kenai/jbosh/AttrPolling.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Data type representing the getValue of the {@code polling} attribute of the
+ * {@code bosh} element.
+ */
+final class AttrPolling extends AbstractIntegerAttr {
+
+    /**
+     * Creates a new attribute object.
+     * 
+     * @param val attribute getValue
+     * @throws BOSHException on parse or validation failure
+     */
+    private AttrPolling(final String str) throws BOSHException {
+        super(str);
+        checkMinValue(0);
+    }
+
+    /**
+     * Creates a new attribute instance from the provided String.
+     * 
+     * @param str string representation of the attribute
+     * @return instance of the attribute for the specified string, or
+     *  {@code null} if input string is {@code null}
+     * @throws BOSHException on parse or validation failure
+     */
+    static AttrPolling createFromString(final String str)
+    throws BOSHException {
+        if (str == null) {
+            return null;
+        } else {
+            return new AttrPolling(str);
+        }
+    }
+
+    /**
+     * Get the polling interval in milliseconds.
+     *
+     * @return polling interval in milliseconds
+     */
+    public int getInMilliseconds() {
+        return (int) TimeUnit.MILLISECONDS.convert(
+                intValue(), TimeUnit.SECONDS);
+    }
+    
+}
diff --git a/src/com/kenai/jbosh/AttrRequests.java b/src/com/kenai/jbosh/AttrRequests.java
new file mode 100644
index 0000000..bfdc529
--- /dev/null
+++ b/src/com/kenai/jbosh/AttrRequests.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Data type representing the value of the {@code requests} attribute of the
+ * {@code bosh} element.
+ */
+final class AttrRequests extends AbstractIntegerAttr {
+    
+    /**
+     * Creates a new attribute object.
+     * 
+     * @param val attribute value
+     * @throws BOSHException on parse or validation failure
+     */
+    private AttrRequests(final String val) throws BOSHException {
+        super(val);
+        checkMinValue(1);
+    }
+
+    /**
+     * Creates a new attribute instance from the provided String.
+     * 
+     * @param str string representation of the attribute
+     * @return instance of the attribute for the specified string, or
+     *  {@code null} if input string is {@code null}
+     * @throws BOSHException on parse or validation failure
+     */
+    static AttrRequests createFromString(final String str)
+    throws BOSHException {
+        if (str == null) {
+            return null;
+        } else {
+            return new AttrRequests(str);
+        }
+    }
+    
+}
diff --git a/src/com/kenai/jbosh/AttrSessionID.java b/src/com/kenai/jbosh/AttrSessionID.java
new file mode 100644
index 0000000..1998968
--- /dev/null
+++ b/src/com/kenai/jbosh/AttrSessionID.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Data type representing the getValue of the {@code sid} attribute of the
+ * {@code bosh} element.
+ */
+final class AttrSessionID extends AbstractAttr<String> {
+    
+    /**
+     * Creates a new attribute object.
+     * 
+     * @param val attribute getValue
+     */
+    private AttrSessionID(final String val) {
+        super(val);
+    }
+    
+    /**
+     * Creates a new attribute instance from the provided String.
+     *
+     * @param str string representation of the attribute
+     * @return attribute instance
+     */
+    static AttrSessionID createFromString(final String str) {
+        return new AttrSessionID(str);
+    }
+    
+}
diff --git a/src/com/kenai/jbosh/AttrVersion.java b/src/com/kenai/jbosh/AttrVersion.java
new file mode 100644
index 0000000..9396e3b
--- /dev/null
+++ b/src/com/kenai/jbosh/AttrVersion.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Data type representing the getValue of the {@code ver} attribute of the
+ * {@code bosh} element.
+ */
+final class AttrVersion extends AbstractAttr<String> implements Comparable {
+
+    /**
+     * Default value if none is provided.
+     */
+    private static final AttrVersion DEFAULT;
+    static {
+        try {
+            DEFAULT = createFromString("1.8");
+        } catch (BOSHException boshx) {
+            throw(new IllegalStateException(boshx));
+        }
+    }
+
+    /**
+     * Major portion of the version.
+     */
+    private final int major;
+    
+    /**
+     * Minor portion of the version.
+     */
+    private final int minor;
+
+    /**
+     * Creates a new attribute object.
+     * 
+     * @param val attribute getValue
+     * @throws BOSHException on parse or validation failure
+     */
+    private AttrVersion(final String val) throws BOSHException {
+        super(val);
+
+        int idx = val.indexOf('.');
+        if (idx <= 0) {
+            throw(new BOSHException(
+                    "Illegal ver attribute value (not in major.minor form): "
+                    + val));
+        }
+
+        String majorStr = val.substring(0, idx);
+        try {
+            major = Integer.parseInt(majorStr);
+        } catch (NumberFormatException nfx) {
+            throw(new BOSHException(
+                    "Could not parse ver attribute value (major ver): "
+                    + majorStr,
+                    nfx));
+        }
+        if (major < 0) {
+            throw(new BOSHException(
+                    "Major version may not be < 0"));
+        }
+
+        String minorStr = val.substring(idx + 1);
+        try {
+            minor = Integer.parseInt(minorStr);
+        } catch (NumberFormatException nfx) {
+            throw(new BOSHException(
+                    "Could not parse ver attribute value (minor ver): "
+                    + minorStr,
+                    nfx));
+        }
+        if (minor < 0) {
+            throw(new BOSHException(
+                    "Minor version may not be < 0"));
+        }
+    }
+    
+    /**
+     * Get the version of specifcation that we support.
+     *
+     * @return max spec version the code supports
+     */
+    static AttrVersion getSupportedVersion() {
+        return DEFAULT;
+    }
+
+    /**
+     * Creates a new attribute instance from the provided String.
+     * 
+     * @param str string representation of the attribute
+     * @return attribute instance or {@code null} if provided string is
+     *  {@code null}
+     * @throws BOSHException on parse or validation failure
+     */
+    static AttrVersion createFromString(final String str)
+    throws BOSHException {
+        if (str == null) {
+            return null;
+        } else {
+            return new AttrVersion(str);
+        }
+    }
+
+    /**
+     * Returns the 'major' portion of the version number.
+     *
+     * @return major digits only
+     */
+    int getMajor() {
+        return major;
+    }
+
+    /**
+     * Returns the 'minor' portion of the version number.
+     *
+     * @return minor digits only
+     */
+    int getMinor() {
+        return minor;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Comparable interface:
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param otherObj object to compare to
+     * @return -1, 0, or 1
+     */
+    @Override
+    public int compareTo(final Object otherObj) {
+        if (otherObj instanceof AttrVersion) {
+            AttrVersion other = (AttrVersion) otherObj;
+            if (major < other.major) {
+                return -1;
+            } else if (major > other.major) {
+                return 1;
+            } else if (minor < other.minor) {
+                return -1;
+            } else if (minor > other.minor) {
+                return 1;
+            } else {
+                return 0;
+            }
+        } else {
+            return 0;
+        }
+    }
+
+}
diff --git a/src/com/kenai/jbosh/AttrWait.java b/src/com/kenai/jbosh/AttrWait.java
new file mode 100644
index 0000000..d2c95f7
--- /dev/null
+++ b/src/com/kenai/jbosh/AttrWait.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Data type representing the getValue of the {@code wait} attribute of the
+ * {@code bosh} element.
+ */
+final class AttrWait extends AbstractIntegerAttr {
+    
+    /**
+     * Creates a new attribute object.
+     * 
+     * @param val attribute getValue
+     * @throws BOSHException on parse or validation failure
+     */
+    private AttrWait(final String val) throws BOSHException {
+        super(val);
+        checkMinValue(1);
+    }
+
+    /**
+     * Creates a new attribute instance from the provided String.
+     * 
+     * @param str string representation of the attribute
+     * @return attribute instance or {@code null} if provided string is
+     *  {@code null}
+     * @throws BOSHException on parse or validation failure
+     */
+    static AttrWait createFromString(final String str)
+    throws BOSHException {
+        if (str == null) {
+            return null;
+        } else {
+            return new AttrWait(str);
+        }
+    }
+    
+}
diff --git a/src/com/kenai/jbosh/Attributes.java b/src/com/kenai/jbosh/Attributes.java
new file mode 100644
index 0000000..d01541e
--- /dev/null
+++ b/src/com/kenai/jbosh/Attributes.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import javax.xml.XMLConstants;
+
+/**
+ * Class containing constants for attribute definitions used by the
+ * XEP-0124 specification.  We shouldn't need to expose these outside
+ * our package, since nobody else should be needing to worry about
+ * them.
+ */
+final class Attributes {
+
+    /**
+     * Private constructor to prevent construction of library class.
+     */
+    private Attributes() {
+        super();
+    }
+
+    static final BodyQName ACCEPT = BodyQName.createBOSH("accept");
+    static final BodyQName AUTHID = BodyQName.createBOSH("authid");
+    static final BodyQName ACK = BodyQName.createBOSH("ack");
+    static final BodyQName CHARSETS = BodyQName.createBOSH("charsets");
+    static final BodyQName CONDITION = BodyQName.createBOSH("condition");
+    static final BodyQName CONTENT = BodyQName.createBOSH("content");
+    static final BodyQName FROM = BodyQName.createBOSH("from");
+    static final BodyQName HOLD = BodyQName.createBOSH("hold");
+    static final BodyQName INACTIVITY = BodyQName.createBOSH("inactivity");
+    static final BodyQName KEY = BodyQName.createBOSH("key");
+    static final BodyQName MAXPAUSE = BodyQName.createBOSH("maxpause");
+    static final BodyQName NEWKEY = BodyQName.createBOSH("newkey");
+    static final BodyQName PAUSE = BodyQName.createBOSH("pause");
+    static final BodyQName POLLING = BodyQName.createBOSH("polling");
+    static final BodyQName REPORT = BodyQName.createBOSH("report");
+    static final BodyQName REQUESTS = BodyQName.createBOSH("requests");
+    static final BodyQName RID = BodyQName.createBOSH("rid");
+    static final BodyQName ROUTE = BodyQName.createBOSH("route");
+    static final BodyQName SECURE = BodyQName.createBOSH("secure");
+    static final BodyQName SID = BodyQName.createBOSH("sid");
+    static final BodyQName STREAM = BodyQName.createBOSH("stream");
+    static final BodyQName TIME = BodyQName.createBOSH("time");
+    static final BodyQName TO = BodyQName.createBOSH("to");
+    static final BodyQName TYPE = BodyQName.createBOSH("type");
+    static final BodyQName VER = BodyQName.createBOSH("ver");
+    static final BodyQName WAIT = BodyQName.createBOSH("wait");
+    static final BodyQName XML_LANG =
+            BodyQName.createWithPrefix(XMLConstants.XML_NS_URI, "lang", "xml");
+}
diff --git a/src/com/kenai/jbosh/BOSHClient.java b/src/com/kenai/jbosh/BOSHClient.java
new file mode 100644
index 0000000..b96d188
--- /dev/null
+++ b/src/com/kenai/jbosh/BOSHClient.java
@@ -0,0 +1,1536 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import com.kenai.jbosh.ComposableBody.Builder;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * BOSH Client session instance.  Each communication session with a remote
+ * connection manager is represented and handled by an instance of this
+ * class.  This is the main entry point for client-side communications.
+ * To create a new session, a client configuration must first be created
+ * and then used to create a client instance:
+ * <pre>
+ * BOSHClientConfig cfg = BOSHClientConfig.Builder.create(
+ *         "http://server:1234/httpbind", "jabber.org")
+ *     .setFrom("user@jabber.org")
+ *     .build();
+ * BOSHClient client = BOSHClient.create(cfg);
+ * </pre>
+ * Additional client configuration options are available.  See the
+ * {@code BOSHClientConfig.Builder} class for more information.
+ * <p/>
+ * Once a {@code BOSHClient} instance has been created, communication with
+ * the remote connection manager can begin.  No attempt will be made to
+ * establish a connection to the connection manager until the first call
+ * is made to the {@code send(ComposableBody)} method.  Note that it is
+ * possible to send an empty body to cause an immediate connection attempt
+ * to the connection manager.  Sending an empty message would look like
+ * the following:
+ * <pre>
+ * client.send(ComposableBody.builder().build());
+ * </pre>
+ * For more information on creating body messages with content, see the
+ * {@code ComposableBody.Builder} class documentation.
+ * <p/>
+ * Once a session has been successfully started, the client instance can be
+ * used to send arbitrary payload data.  All aspects of the BOSH
+ * protocol involving setting and processing attributes in the BOSH
+ * namespace will be handled by the client code transparently and behind the
+ * scenes.  The user of the client instance can therefore concentrate
+ * entirely on the content of the message payload, leaving the semantics of
+ * the BOSH protocol to the client implementation.
+ * <p/>
+ * To be notified of incoming messages from the remote connection manager,
+ * a {@code BOSHClientResponseListener} should be added to the client instance.
+ * All incoming messages will be published to all response listeners as they
+ * arrive and are processed.  As with the transmission of payload data via
+ * the {@code send(ComposableBody)} method, there is no need to worry about
+ * handling of the BOSH attributes, since this is handled behind the scenes.
+ * <p/>
+ * If the connection to the remote connection manager is terminated (either
+ * explicitly or due to a terminal condition of some sort), all connection
+ * listeners will be notified.  After the connection has been closed, the
+ * client instance is considered dead and a new one must be created in order
+ * to resume communications with the remote server.
+ * <p/>
+ * Instances of this class are thread-safe.
+ *
+ * @see BOSHClientConfig.Builder
+ * @see BOSHClientResponseListener
+ * @see BOSHClientConnListener
+ * @see ComposableBody.Builder
+ */
+public final class BOSHClient {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = Logger.getLogger(
+            BOSHClient.class.getName());
+
+    /**
+     * Value of the 'type' attribute used for session termination.
+     */
+    private static final String TERMINATE = "terminate";
+    
+    /**
+     * Value of the 'type' attribute used for recoverable errors.
+     */
+    private static final String ERROR = "error";
+
+    /**
+     * Message to use for interrupted exceptions.
+     */
+    private static final String INTERRUPTED = "Interrupted";
+
+    /**
+     * Message used for unhandled exceptions.
+     */
+    private static final String UNHANDLED = "Unhandled Exception";
+
+    /**
+     * Message used whena null listener is detected.
+     */
+    private static final String NULL_LISTENER = "Listener may not b enull";
+
+    /**
+     * Default empty request delay.
+     */
+    private static final int DEFAULT_EMPTY_REQUEST_DELAY = 100;
+
+    /**
+     * Amount of time to wait before sending an empty request, in
+     * milliseconds.
+     */
+    private static final int EMPTY_REQUEST_DELAY = Integer.getInteger(
+            BOSHClient.class.getName() + ".emptyRequestDelay",
+            DEFAULT_EMPTY_REQUEST_DELAY);
+
+    /**
+     * Default value for the pause margin.
+     */
+    private static final int DEFAULT_PAUSE_MARGIN = 500;
+
+    /**
+     * The amount of time in milliseconds which will be reserved as a
+     * safety margin when scheduling empty requests against a maxpause
+     * value.   This should give us enough time to build the message
+     * and transport it to the remote host.
+     */
+    private static final int PAUSE_MARGIN = Integer.getInteger(
+            BOSHClient.class.getName() + ".pauseMargin",
+            DEFAULT_PAUSE_MARGIN);
+    
+    /**
+     * Flag indicating whether or not we want to perform assertions.
+     */
+    private static final boolean ASSERTIONS;
+
+    /**
+     * Connection listeners.
+     */
+    private final Set<BOSHClientConnListener> connListeners =
+            new CopyOnWriteArraySet<BOSHClientConnListener>();
+
+    /**
+     * Request listeners.
+     */
+    private final Set<BOSHClientRequestListener> requestListeners =
+            new CopyOnWriteArraySet<BOSHClientRequestListener>();
+
+    /**
+     * Response listeners.
+     */
+    private final Set<BOSHClientResponseListener> responseListeners =
+            new CopyOnWriteArraySet<BOSHClientResponseListener>();
+
+    /**
+     * Lock instance.
+     */
+    private final ReentrantLock lock = new ReentrantLock();
+
+    /**
+     * Condition indicating that there are messages to be exchanged.
+     */
+    private final Condition notEmpty = lock.newCondition();
+
+    /**
+     * Condition indicating that there are available slots for sending
+     * messages.
+     */
+    private final Condition notFull = lock.newCondition();
+
+    /**
+     * Condition indicating that there are no outstanding connections.
+     */
+    private final Condition drained = lock.newCondition();
+
+    /**
+     * Session configuration.
+     */
+    private final BOSHClientConfig cfg;
+
+    /**
+     * Processor thread runnable instance.
+     */
+    private final Runnable procRunnable = new Runnable() {
+        /**
+         * Process incoming messages.
+         */
+        public void run() {
+            processMessages();
+        }
+    };
+
+    /**
+     * Processor thread runnable instance.
+     */
+    private final Runnable emptyRequestRunnable = new Runnable() {
+        /**
+         * Process incoming messages.
+         */
+        public void run() {
+            sendEmptyRequest();
+        }
+    };
+
+    /**
+     * HTTPSender instance.
+     */
+    private final HTTPSender httpSender =
+            new ApacheHTTPSender();
+
+    /**
+     * Storage for test hook implementation.
+     */
+    private final AtomicReference<ExchangeInterceptor> exchInterceptor =
+            new AtomicReference<ExchangeInterceptor>();
+
+    /**
+     * Request ID sequence to use for the session.
+     */
+    private final RequestIDSequence requestIDSeq = new RequestIDSequence();
+
+    /**
+     * ScheduledExcecutor to use for deferred tasks.
+     */
+    private final ScheduledExecutorService schedExec =
+            Executors.newSingleThreadScheduledExecutor();
+
+    /************************************************************
+     * The following vars must be accessed via the lock instance.
+     */
+
+    /**
+     * Thread which is used to process responses from the connection
+     * manager.  Becomes null when session is terminated.
+     */
+    private Thread procThread;
+
+    /**
+     * Future for sending a deferred empty request, if needed.
+     */
+    private ScheduledFuture emptyRequestFuture;
+
+    /**
+     * Connection Manager session parameters.  Only available when in a
+     * connected state.
+     */
+    private CMSessionParams cmParams;
+
+    /**
+     * List of active/outstanding requests.
+     */
+    private Queue<HTTPExchange> exchanges = new LinkedList<HTTPExchange>();
+
+    /**
+     * Set of RIDs which have been received, for the purpose of sending
+     * response acknowledgements.
+     */
+    private SortedSet<Long> pendingResponseAcks = new TreeSet<Long>();
+    
+    /**
+     * The highest RID that we've already received a response for.  This value
+     * is used to implement response acks.
+     */
+    private Long responseAck = Long.valueOf(-1L);
+
+    /**
+     * List of requests which have been made but not yet acknowledged.  This
+     * list remains unpopulated if the CM is not acking requests.
+     */
+    private List<ComposableBody> pendingRequestAcks =
+            new ArrayList<ComposableBody>();
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Classes:
+
+    /**
+     * Class used in testing to dynamically manipulate received exchanges
+     * at test runtime.
+     */
+    abstract static class ExchangeInterceptor {
+        /**
+         * Limit construction.
+         */
+        ExchangeInterceptor() {
+            // Empty;
+        }
+
+        /**
+         * Hook to manipulate an HTTPExchange as is is about to be processed.
+         *
+         * @param exch original exchange that would be processed
+         * @return replacement exchange instance, or {@code null} to skip
+         *  processing of this exchange
+         */
+        abstract HTTPExchange interceptExchange(final HTTPExchange exch);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constructors:
+
+    /**
+     * Determine whether or not we should perform assertions.  Assertions
+     * can be specified via system property explicitly, or defaulted to
+     * the JVM assertions status.
+     */
+    static {
+        final String prop =
+                BOSHClient.class.getSimpleName() + ".assertionsEnabled";
+        boolean enabled = false;
+        if (System.getProperty(prop) == null) {
+            assert enabled = true;
+        } else {
+            enabled = Boolean.getBoolean(prop);
+        }
+        ASSERTIONS = enabled;
+    }
+
+    /**
+     * Prevent direct construction.
+     */
+    private BOSHClient(final BOSHClientConfig sessCfg) {
+        cfg = sessCfg;
+        init();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Public methods:
+
+    /**
+     * Create a new BOSH client session using the client configuration
+     * information provided.
+     *
+     * @param clientCfg session configuration
+     * @return BOSH session instance
+     */
+    public static BOSHClient create(final BOSHClientConfig clientCfg) {
+        if (clientCfg == null) {
+            throw(new IllegalArgumentException(
+                    "Client configuration may not be null"));
+        }
+        return new BOSHClient(clientCfg);
+    }
+
+    /**
+     * Get the client configuration that was used to create this client
+     * instance.
+     *
+     * @return client configuration
+     */
+    public BOSHClientConfig getBOSHClientConfig() {
+        return cfg;
+    }
+
+    /**
+     * Adds a connection listener to the session.
+     *
+     * @param listener connection listener to add, if not already added
+     */
+    public void addBOSHClientConnListener(
+            final BOSHClientConnListener listener) {
+        if (listener == null) {
+            throw(new IllegalArgumentException(NULL_LISTENER));
+        }
+        connListeners.add(listener);
+    }
+
+    /**
+     * Removes a connection listener from the session.
+     *
+     * @param listener connection listener to remove, if previously added
+     */
+    public void removeBOSHClientConnListener(
+            final BOSHClientConnListener listener) {
+        if (listener == null) {
+            throw(new IllegalArgumentException(NULL_LISTENER));
+        }
+        connListeners.remove(listener);
+    }
+
+    /**
+     * Adds a request message listener to the session.
+     *
+     * @param listener request listener to add, if not already added
+     */
+    public void addBOSHClientRequestListener(
+            final BOSHClientRequestListener listener) {
+        if (listener == null) {
+            throw(new IllegalArgumentException(NULL_LISTENER));
+        }
+        requestListeners.add(listener);
+    }
+
+    /**
+     * Removes a request message listener from the session, if previously
+     * added.
+     *
+     * @param listener instance to remove
+     */
+    public void removeBOSHClientRequestListener(
+            final BOSHClientRequestListener listener) {
+        if (listener == null) {
+            throw(new IllegalArgumentException(NULL_LISTENER));
+        }
+        requestListeners.remove(listener);
+    }
+
+    /**
+     * Adds a response message listener to the session.
+     *
+     * @param listener response listener to add, if not already added
+     */
+    public void addBOSHClientResponseListener(
+            final BOSHClientResponseListener listener) {
+        if (listener == null) {
+            throw(new IllegalArgumentException(NULL_LISTENER));
+        }
+        responseListeners.add(listener);
+    }
+
+    /**
+     * Removes a response message listener from the session, if previously
+     * added.
+     *
+     * @param listener instance to remove
+     */
+    public void removeBOSHClientResponseListener(
+            final BOSHClientResponseListener listener) {
+        if (listener == null) {
+            throw(new IllegalArgumentException(NULL_LISTENER));
+        }
+        responseListeners.remove(listener);
+    }
+
+    /**
+     * Send the provided message data to the remote connection manager.  The
+     * provided message body does not need to have any BOSH-specific attribute
+     * information set.  It only needs to contain the actual message payload
+     * that should be delivered to the remote server.
+     * <p/>
+     * The first call to this method will result in a connection attempt
+     * to the remote connection manager.  Subsequent calls to this method
+     * will block until the underlying session state allows for the message
+     * to be transmitted.  In certain scenarios - such as when the maximum
+     * number of outbound connections has been reached - calls to this method
+     * will block for short periods of time.
+     *
+     * @param body message data to send to remote server
+     * @throws BOSHException on message transmission failure
+     */
+    public void send(final ComposableBody body) throws BOSHException {
+        assertUnlocked();
+        if (body == null) {
+            throw(new IllegalArgumentException(
+                    "Message body may not be null"));
+        }
+
+        HTTPExchange exch;
+        CMSessionParams params;
+        lock.lock();
+        try {
+            blockUntilSendable(body);
+            if (!isWorking() && !isTermination(body)) {
+                throw(new BOSHException(
+                        "Cannot send message when session is closed"));
+            }
+            
+            long rid = requestIDSeq.getNextRID();
+            ComposableBody request = body;
+            params = cmParams;
+            if (params == null && exchanges.isEmpty()) {
+                // This is the first message being sent
+                request = applySessionCreationRequest(rid, body);
+            } else {
+                request = applySessionData(rid, body);
+                if (cmParams.isAckingRequests()) {
+                    pendingRequestAcks.add(request);
+                }
+            }
+            exch = new HTTPExchange(request);
+            exchanges.add(exch);
+            notEmpty.signalAll();
+            clearEmptyRequest();
+        } finally {
+            lock.unlock();
+        }
+        AbstractBody finalReq = exch.getRequest();
+        HTTPResponse resp = httpSender.send(params, finalReq);
+        exch.setHTTPResponse(resp);
+        fireRequestSent(finalReq);
+    }
+
+    /**
+     * Attempt to pause the current session.  When supported by the remote
+     * connection manager, pausing the session will result in the connection
+     * manager closing out all outstanding requests (including the pause
+     * request) and increases the inactivity timeout of the session.  The
+     * exact value of the temporary timeout is dependent upon the connection
+     * manager.  This method should be used if a client encounters an
+     * exceptional temporary situation during which it will be unable to send
+     * requests to the connection manager for a period of time greater than
+     * the maximum inactivity period.
+     *
+     * The session will revert back to it's normal, unpaused state when the
+     * client sends it's next message.
+     *
+     * @return {@code true} if the connection manager supports session pausing,
+     *  {@code false} if the connection manager does not support session
+     *  pausing or if the session has not yet been established
+     */
+    public boolean pause() {
+        assertUnlocked();
+        lock.lock();
+        AttrMaxPause maxPause = null;
+        try {
+            if (cmParams == null) {
+                return false;
+            }
+
+            maxPause = cmParams.getMaxPause();
+            if (maxPause == null) {
+                return false;
+            }
+        } finally {
+            lock.unlock();
+        }
+        try {
+            send(ComposableBody.builder()
+                    .setAttribute(Attributes.PAUSE, maxPause.toString())
+                    .build());
+        } catch (BOSHException boshx) {
+            LOG.log(Level.FINEST, "Could not send pause", boshx);
+        }
+        return true;
+    }
+
+    /**
+     * End the BOSH session by disconnecting from the remote BOSH connection
+     * manager.
+     *
+     * @throws BOSHException when termination message cannot be sent
+     */
+    public void disconnect() throws BOSHException {
+        disconnect(ComposableBody.builder().build());
+    }
+
+    /**
+     * End the BOSH session by disconnecting from the remote BOSH connection
+     * manager, sending the provided content in the final connection
+     * termination message.
+     *
+     * @param msg final message to send
+     * @throws BOSHException when termination message cannot be sent
+     */
+    public void disconnect(final ComposableBody msg) throws BOSHException {
+        if (msg == null) {
+            throw(new IllegalArgumentException(
+                    "Message body may not be null"));
+        }
+
+        Builder builder = msg.rebuild();
+        builder.setAttribute(Attributes.TYPE, TERMINATE);
+        send(builder.build());
+    }
+
+    /**
+     * Forcibly close this client session instance.  The preferred mechanism
+     * to close the connection is to send a disconnect message and wait for
+     * organic termination.  Calling this method simply shuts down the local
+     * session without sending a termination message, releasing all resources
+     * associated with the session.
+     */
+    public void close() {
+        dispose(new BOSHException("Session explicitly closed by caller"));
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Package-private methods:
+
+    /**
+     * Get the current CM session params.
+     *
+     * @return current session params, or {@code null}
+     */
+    CMSessionParams getCMSessionParams() {
+        lock.lock();
+        try {
+            return cmParams;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Wait until no more messages are waiting to be processed.
+     */
+    void drain() {
+        lock.lock();
+        try {
+            LOG.finest("Waiting while draining...");
+            while (isWorking()
+                    && (emptyRequestFuture == null
+                    || emptyRequestFuture.isDone())) {
+                try {
+                    drained.await();
+                } catch (InterruptedException intx) {
+                    LOG.log(Level.FINEST, INTERRUPTED, intx);
+                }
+            }
+            LOG.finest("Drained");
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Test method used to forcibly discard next exchange.
+     *
+     * @param interceptor exchange interceptor
+     */
+    void setExchangeInterceptor(final ExchangeInterceptor interceptor) {
+        exchInterceptor.set(interceptor);
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Private methods:
+
+    /**
+     * Initialize the session.  This initializes the underlying HTTP
+     * transport implementation and starts the receive thread.
+     */
+    private void init() {
+        assertUnlocked();
+        
+        lock.lock();
+        try {
+            httpSender.init(cfg);
+            procThread = new Thread(procRunnable);
+            procThread.setDaemon(true);
+            procThread.setName(BOSHClient.class.getSimpleName()
+                    + "[" + System.identityHashCode(this)
+                    + "]: Receive thread");
+            procThread.start();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Destroy this session.
+     *
+     * @param cause the reason for the session termination, or {@code null}
+     *  for normal termination
+     */
+    private void dispose(final Throwable cause) {
+        assertUnlocked();
+        
+        lock.lock();
+        try {
+            if (procThread == null) {
+                // Already disposed
+                return;
+            }
+            procThread = null;
+        } finally {
+            lock.unlock();
+        }
+
+        if (cause == null) {
+            fireConnectionClosed();
+        } else {
+            fireConnectionClosedOnError(cause);
+        }
+
+        lock.lock();
+        try {
+            clearEmptyRequest();
+            exchanges = null;
+            cmParams = null;
+            pendingResponseAcks = null;
+            pendingRequestAcks = null;
+            notEmpty.signalAll();
+            notFull.signalAll();
+            drained.signalAll();
+        } finally {
+            lock.unlock();
+        }
+        
+        httpSender.destroy();
+        schedExec.shutdownNow();
+    }
+
+    /**
+     * Determines if the message body specified indicates a request to
+     * pause the session.
+     *
+     * @param msg message to evaluate
+     * @return {@code true} if the message is a pause request, {@code false}
+     *  otherwise
+     */
+    private static boolean isPause(final AbstractBody msg) {
+        return msg.getAttribute(Attributes.PAUSE) != null;
+    }
+    
+    /**
+     * Determines if the message body specified indicates a termination of
+     * the session.
+     *
+     * @param msg message to evaluate
+     * @return {@code true} if the message is a session termination,
+     *  {@code false} otherwise
+     */
+    private static boolean isTermination(final AbstractBody msg) {
+        return TERMINATE.equals(msg.getAttribute(Attributes.TYPE));
+    }
+
+    /**
+     * Evaluates the HTTP response code and response message and returns the
+     * terminal binding condition that it describes, if any.
+     *
+     * @param respCode HTTP response code
+     * @param respBody response body
+     * @return terminal binding condition, or {@code null} if not a terminal
+     *  binding condition message
+     */
+    private TerminalBindingCondition getTerminalBindingCondition(
+            final int respCode,
+            final AbstractBody respBody) {
+        assertLocked();
+
+        if (isTermination(respBody)) {
+            String str = respBody.getAttribute(Attributes.CONDITION);
+            return TerminalBindingCondition.forString(str);
+        }
+        // Check for deprecated HTTP Error Conditions
+        if (cmParams != null && cmParams.getVersion() == null) {
+            return TerminalBindingCondition.forHTTPResponseCode(respCode);
+        }
+        return null;
+    }
+
+    /**
+     * Determines if the message specified is immediately sendable or if it
+     * needs to block until the session state changes.
+     *
+     * @param msg message to evaluate
+     * @return {@code true} if the message can be immediately sent,
+     *  {@code false} otherwise
+     */
+    private boolean isImmediatelySendable(final AbstractBody msg) {
+        assertLocked();
+
+        if (cmParams == null) {
+            // block if we're waiting for a response to our first request
+            return exchanges.isEmpty();
+        }
+
+        AttrRequests requests = cmParams.getRequests();
+        if (requests == null) {
+            return true;
+        }
+        int maxRequests = requests.intValue();
+        if (exchanges.size() < maxRequests) {
+            return true;
+        }
+        if (exchanges.size() == maxRequests
+                && (isTermination(msg) || isPause(msg))) {
+            // One additional terminate or pause message is allowed
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Determines whether or not the session is still active.
+     *
+     * @return {@code true} if it is, {@code false} otherwise
+     */
+    private boolean isWorking() {
+        assertLocked();
+
+        return procThread != null;
+    }
+
+    /**
+     * Blocks until either the message provided becomes immediately
+     * sendable or until the session is terminated.
+     *
+     * @param msg message to evaluate
+     */
+    private void blockUntilSendable(final AbstractBody msg) {
+        assertLocked();
+
+        while (isWorking() && !isImmediatelySendable(msg)) {
+            try {
+                notFull.await();
+            } catch (InterruptedException intx) {
+                LOG.log(Level.FINEST, INTERRUPTED, intx);
+            }
+        }
+    }
+
+    /**
+     * Modifies the specified body message such that it becomes a new
+     * BOSH session creation request.
+     *
+     * @param rid request ID to use
+     * @param orig original body to modify
+     * @return modified message which acts as a session creation request
+     */
+    private ComposableBody applySessionCreationRequest(
+            final long rid, final ComposableBody orig) throws BOSHException {
+        assertLocked();
+        
+        Builder builder = orig.rebuild();
+        builder.setAttribute(Attributes.TO, cfg.getTo());
+        builder.setAttribute(Attributes.XML_LANG, cfg.getLang());
+        builder.setAttribute(Attributes.VER,
+                AttrVersion.getSupportedVersion().toString());
+        builder.setAttribute(Attributes.WAIT, "60");
+        builder.setAttribute(Attributes.HOLD, "1");
+        builder.setAttribute(Attributes.RID, Long.toString(rid));
+        applyRoute(builder);
+        applyFrom(builder);
+        builder.setAttribute(Attributes.ACK, "1");
+
+        // Make sure the following are NOT present (i.e., during retries)
+        builder.setAttribute(Attributes.SID, null);
+        return builder.build();
+    }
+
+    /**
+     * Applies routing information to the request message who's builder has
+     * been provided.
+     *
+     * @param builder builder instance to add routing information to
+     */
+    private void applyRoute(final Builder builder) {
+        assertLocked();
+        
+        String route = cfg.getRoute();
+        if (route != null) {
+            builder.setAttribute(Attributes.ROUTE, route);
+        }
+    }
+
+    /**
+     * Applies the local station ID information to the request message who's
+     * builder has been provided.
+     *
+     * @param builder builder instance to add station ID information to
+     */
+    private void applyFrom(final Builder builder) {
+        assertLocked();
+
+        String from = cfg.getFrom();
+        if (from != null) {
+            builder.setAttribute(Attributes.FROM, from);
+        }
+    }
+
+    /**
+     * Applies existing session data to the outbound request, returning the
+     * modified request.
+     *
+     * This method assumes the lock is currently held.
+     *
+     * @param rid request ID to use
+     * @param orig original/raw request
+     * @return modified request with session information applied
+     */
+    private ComposableBody applySessionData(
+            final long rid,
+            final ComposableBody orig) throws BOSHException {
+        assertLocked();
+
+        Builder builder = orig.rebuild();
+        builder.setAttribute(Attributes.SID,
+                cmParams.getSessionID().toString());
+        builder.setAttribute(Attributes.RID, Long.toString(rid));
+        applyResponseAcknowledgement(builder, rid);
+        return builder.build();
+    }
+
+    /**
+     * Sets the 'ack' attribute of the request to the value of the highest
+     * 'rid' of a request for which it has already received a response in the
+     * case where it has also received all responses associated with lower
+     * 'rid' values.  The only exception is that, after its session creation
+     * request, the client SHOULD NOT include an 'ack' attribute in any request
+     * if it has received responses to all its previous requests.
+     *
+     * @param builder message builder
+     * @param rid current request RID
+     */
+    private void applyResponseAcknowledgement(
+            final Builder builder,
+            final long rid) {
+        assertLocked();
+
+        if (responseAck.equals(Long.valueOf(-1L))) {
+            // We have not received any responses yet
+            return;
+        }
+
+        Long prevRID = Long.valueOf(rid - 1L);
+        if (responseAck.equals(prevRID)) {
+            // Implicit ack
+            return;
+        }
+        
+        builder.setAttribute(Attributes.ACK, responseAck.toString());
+    }
+
+    /**
+     * While we are "connected", process received responses.
+     *
+     * This method is run in the processing thread.
+     */
+    private void processMessages() {
+        LOG.log(Level.FINEST, "Processing thread starting");
+        try {
+            HTTPExchange exch;
+            do {
+                exch = nextExchange();
+                if (exch == null) {
+                    break;
+                }
+
+                // Test hook to manipulate what the client sees:
+                ExchangeInterceptor interceptor = exchInterceptor.get();
+                if (interceptor != null) {
+                    HTTPExchange newExch = interceptor.interceptExchange(exch);
+                    if (newExch == null) {
+                        LOG.log(Level.FINE, "Discarding exchange on request "
+                                + "of test hook: RID="
+                                + exch.getRequest().getAttribute(
+                                    Attributes.RID));
+                        lock.lock();
+                        try {
+                            exchanges.remove(exch);
+                        } finally {
+                            lock.unlock();
+                        }
+                        continue;
+                    }
+                    exch = newExch;
+                }
+
+                processExchange(exch);
+            } while (true);
+        } finally {
+            LOG.log(Level.FINEST, "Processing thread exiting");
+        }
+
+    }
+
+    /**
+     * Get the next message exchange to process, blocking until one becomes
+     * available if nothing is already waiting for processing.
+     *
+     * @return next available exchange to process, or {@code null} if no
+     *  exchanges are immediately available
+     */
+    private HTTPExchange nextExchange() {
+        assertUnlocked();
+
+        final Thread thread = Thread.currentThread();
+        HTTPExchange exch = null;
+        lock.lock();
+        try {
+            do {
+                if (!thread.equals(procThread)) {
+                    break;
+                }
+                exch = exchanges.peek();
+                if (exch == null) {
+                    try {
+                        notEmpty.await();
+                    } catch (InterruptedException intx) {
+                        LOG.log(Level.FINEST, INTERRUPTED, intx);
+                    }
+                }
+            } while (exch == null);
+        } finally {
+            lock.unlock();
+        }
+        return exch;
+    }
+
+    /**
+     * Process the next, provided exchange.  This is the main processing
+     * method of the receive thread.
+     *
+     * @param exch message exchange to process
+     */
+    private void processExchange(final HTTPExchange exch) {
+        assertUnlocked();
+
+        HTTPResponse resp;
+        AbstractBody body;
+        int respCode;
+        try {
+            resp = exch.getHTTPResponse();
+            body = resp.getBody();
+            respCode = resp.getHTTPStatus();
+        } catch (BOSHException boshx) {
+            LOG.log(Level.FINEST, "Could not obtain response", boshx);
+            dispose(boshx);
+            return;
+        } catch (InterruptedException intx) {
+            LOG.log(Level.FINEST, INTERRUPTED, intx);
+            dispose(intx);
+            return;
+        }
+        fireResponseReceived(body);
+
+        // Process the message with the current session state
+        AbstractBody req = exch.getRequest();
+        CMSessionParams params;
+        List<HTTPExchange> toResend = null;
+        lock.lock();
+        try {
+            // Check for session creation response info, if needed
+            if (cmParams == null) {
+                cmParams = CMSessionParams.fromSessionInit(req, body);
+
+                // The following call handles the lock. It's not an escape.
+                fireConnectionEstablished();
+            }
+            params = cmParams;
+
+            checkForTerminalBindingConditions(body, respCode);
+            if (isTermination(body)) {
+                // Explicit termination
+                lock.unlock();
+                dispose(null);
+                return;
+            }
+            
+            if (isRecoverableBindingCondition(body)) {
+                // Retransmit outstanding requests
+                if (toResend == null) {
+                    toResend = new ArrayList<HTTPExchange>(exchanges.size());
+                }
+                for (HTTPExchange exchange : exchanges) {
+                    HTTPExchange resendExch =
+                            new HTTPExchange(exchange.getRequest());
+                    toResend.add(resendExch);
+                }
+                for (HTTPExchange exchange : toResend) {
+                    exchanges.add(exchange);
+                }
+            } else {
+                // Process message as normal
+                processRequestAcknowledgements(req, body);
+                processResponseAcknowledgementData(req);
+                HTTPExchange resendExch =
+                        processResponseAcknowledgementReport(body);
+                if (resendExch != null && toResend == null) {
+                    toResend = new ArrayList<HTTPExchange>(1);
+                    toResend.add(resendExch);
+                    exchanges.add(resendExch);
+                }
+            }
+        } catch (BOSHException boshx) {
+            LOG.log(Level.FINEST, "Could not process response", boshx);
+            lock.unlock();
+            dispose(boshx);
+            return;
+        } finally {
+            if (lock.isHeldByCurrentThread()) {
+                try {
+                    exchanges.remove(exch);
+                    if (exchanges.isEmpty()) {
+                        scheduleEmptyRequest(processPauseRequest(req));
+                    }
+                    notFull.signalAll();
+                } finally {
+                    lock.unlock();
+                }
+            }
+        }
+
+        if (toResend != null) {
+            for (HTTPExchange resend : toResend) {
+                HTTPResponse response =
+                        httpSender.send(params, resend.getRequest());
+                resend.setHTTPResponse(response);
+                fireRequestSent(resend.getRequest());
+            }
+        }
+    }
+    
+    /**
+     * Clears any scheduled empty requests.
+     */
+    private void clearEmptyRequest() {
+        assertLocked();
+
+        if (emptyRequestFuture != null) {
+            emptyRequestFuture.cancel(false);
+            emptyRequestFuture = null;
+        }
+    }
+
+    /**
+     * Calculates the default empty request delay/interval to use for the
+     * active session.
+     *
+     * @return delay in milliseconds
+     */
+    private long getDefaultEmptyRequestDelay() {
+        assertLocked();
+        
+        // Figure out how long we should wait before sending an empty request
+        AttrPolling polling = cmParams.getPollingInterval();
+        long delay;
+        if (polling == null) {
+            delay = EMPTY_REQUEST_DELAY;
+        } else {
+            delay = polling.getInMilliseconds();
+        }
+        return delay;
+    }
+
+    /**
+     * Schedule an empty request to be sent if no other requests are
+     * sent in a reasonable amount of time.
+     */
+    private void scheduleEmptyRequest(long delay) {
+        assertLocked();
+        if (delay < 0L) {
+            throw(new IllegalArgumentException(
+                    "Empty request delay must be >= 0 (was: " + delay + ")"));
+        }
+
+        clearEmptyRequest();
+        if (!isWorking()) {
+            return;
+        }
+        
+        // Schedule the transmission
+        if (LOG.isLoggable(Level.FINER)) {
+            LOG.finer("Scheduling empty request in " + delay + "ms");
+        }
+        try {
+            emptyRequestFuture = schedExec.schedule(emptyRequestRunnable,
+                    delay, TimeUnit.MILLISECONDS);
+        } catch (RejectedExecutionException rex) {
+            LOG.log(Level.FINEST, "Could not schedule empty request", rex);
+        }
+        drained.signalAll();
+    }
+
+    /**
+     * Sends an empty request to maintain session requirements.  If a request
+     * is sent within a reasonable time window, the empty request transmission
+     * will be cancelled.
+     */
+    private void sendEmptyRequest() {
+        assertUnlocked();
+        // Send an empty request
+        LOG.finest("Sending empty request");
+        try {
+            send(ComposableBody.builder().build());
+        } catch (BOSHException boshx) {
+            dispose(boshx);
+        }
+    }
+
+    /**
+     * Assert that the internal lock is held.
+     */
+    private void assertLocked() {
+        if (ASSERTIONS) {
+            if (!lock.isHeldByCurrentThread()) {
+                throw(new AssertionError("Lock is not held by current thread"));
+            }
+            return;
+        }
+    }
+
+    /**
+     * Assert that the internal lock is *not* held.
+     */
+    private void assertUnlocked() {
+        if (ASSERTIONS) {
+            if (lock.isHeldByCurrentThread()) {
+                throw(new AssertionError("Lock is held by current thread"));
+            }
+            return;
+        }
+    }
+
+    /**
+     * Checks to see if the response indicates a terminal binding condition
+     * (as per XEP-0124 section 17).  If it does, an exception is thrown.
+     *
+     * @param body response body to evaluate
+     * @param code HTTP response code
+     * @throws BOSHException if a terminal binding condition is detected
+     */
+    private void checkForTerminalBindingConditions(
+            final AbstractBody body,
+            final int code)
+            throws BOSHException {
+        TerminalBindingCondition cond =
+                getTerminalBindingCondition(code, body);
+        if (cond != null) {
+            throw(new BOSHException(
+                    "Terminal binding condition encountered: "
+                    + cond.getCondition() + "  ("
+                    + cond.getMessage() + ")"));
+        }
+    }
+
+    /**
+     * Determines whether or not the response indicates a recoverable
+     * binding condition (as per XEP-0124 section 17).
+     *
+     * @param resp response body
+     * @return {@code true} if it does, {@code false} otherwise
+     */
+    private static boolean isRecoverableBindingCondition(
+            final AbstractBody resp) {
+        return ERROR.equals(resp.getAttribute(Attributes.TYPE));
+    }
+
+    /**
+     * Process the request to determine if the empty request delay
+     * can be determined by looking to see if the request is a pause
+     * request.  If it can, the request's delay is returned, otherwise
+     * the default delay is returned.
+     * 
+     * @return delay in milliseconds that should elapse prior to an
+     *  empty message being sent
+     */
+    private long processPauseRequest(
+            final AbstractBody req) {
+        assertLocked();
+
+        if (cmParams != null && cmParams.getMaxPause() != null) {
+            try {
+                AttrPause pause = AttrPause.createFromString(
+                        req.getAttribute(Attributes.PAUSE));
+                if (pause != null) {
+                    long delay = pause.getInMilliseconds() - PAUSE_MARGIN;
+                    if (delay < 0) {
+                        delay = EMPTY_REQUEST_DELAY;
+                    }
+                    return delay;
+                }
+            } catch (BOSHException boshx) {
+                LOG.log(Level.FINEST, "Could not extract", boshx);
+            }
+        }
+
+        return getDefaultEmptyRequestDelay();
+    }
+
+    /**
+     * Check the response for request acknowledgements and take appropriate
+     * action.
+     *
+     * This method assumes the lock is currently held.
+     *
+     * @param req request
+     * @param resp response
+     */
+    private void processRequestAcknowledgements(
+            final AbstractBody req, final AbstractBody resp) {
+        assertLocked();
+        
+        if (!cmParams.isAckingRequests()) {
+            return;
+        }
+
+        // If a report or time attribute is set, we aren't acking anything
+        if (resp.getAttribute(Attributes.REPORT) != null) {
+            return;
+        }
+
+        // Figure out what the highest acked RID is
+        String acked = resp.getAttribute(Attributes.ACK);
+        Long ackUpTo;
+        if (acked == null) {
+            // Implicit ack of all prior requests up until RID
+            ackUpTo = Long.parseLong(req.getAttribute(Attributes.RID));
+        } else {
+            ackUpTo = Long.parseLong(acked);
+        }
+
+        // Remove the acked requests from the list
+        if (LOG.isLoggable(Level.FINEST)) {
+            LOG.finest("Removing pending acks up to: " + ackUpTo);
+        }
+        Iterator<ComposableBody> iter = pendingRequestAcks.iterator();
+        while (iter.hasNext()) {
+            AbstractBody pending = iter.next();
+            Long pendingRID = Long.parseLong(
+                    pending.getAttribute(Attributes.RID));
+            if (pendingRID.compareTo(ackUpTo) <= 0) {
+                iter.remove();
+            }
+        }
+    }
+
+    /**
+     * Process the response in order to update the response acknowlegement
+     * data.
+     *
+     * This method assumes the lock is currently held.
+     *
+     * @param req request
+     */
+    private void processResponseAcknowledgementData(
+            final AbstractBody req) {
+        assertLocked();
+        
+        Long rid = Long.parseLong(req.getAttribute(Attributes.RID));
+        if (responseAck.equals(Long.valueOf(-1L))) {
+            // This is the first request
+            responseAck = rid;
+        } else {
+            pendingResponseAcks.add(rid);
+            // Remove up until the first missing response (or end of queue)
+            Long whileVal = responseAck;
+            while (whileVal.equals(pendingResponseAcks.first())) {
+                responseAck = whileVal;
+                pendingResponseAcks.remove(whileVal);
+                whileVal = Long.valueOf(whileVal.longValue() + 1);
+            }
+        }
+    }
+
+    /**
+     * Process the response in order to check for and respond to any potential
+     * ack reports.
+     *
+     * This method assumes the lock is currently held.
+     *
+     * @param resp response
+     * @return exchange to transmit if a resend is to be performed, or
+     *  {@code null} if no resend is necessary
+     * @throws BOSHException when a a retry is needed but cannot be performed
+     */
+    private HTTPExchange processResponseAcknowledgementReport(
+            final AbstractBody resp)
+            throws BOSHException {
+        assertLocked();
+        
+        String reportStr = resp.getAttribute(Attributes.REPORT);
+        if (reportStr == null) {
+            // No report on this message
+            return null;
+        }
+        
+        Long report = Long.parseLong(reportStr);
+        Long time = Long.parseLong(resp.getAttribute(Attributes.TIME));
+        if (LOG.isLoggable(Level.FINE)) {
+            LOG.fine("Received report of missing request (RID="
+                    + report + ", time=" + time + "ms)");
+        }
+
+        // Find the missing request
+        Iterator<ComposableBody> iter = pendingRequestAcks.iterator();
+        AbstractBody req = null;
+        while (iter.hasNext() && req == null) {
+            AbstractBody pending = iter.next();
+            Long pendingRID = Long.parseLong(
+                    pending.getAttribute(Attributes.RID));
+            if (report.equals(pendingRID)) {
+                req = pending;
+            }
+        }
+
+        if (req == null) {
+            throw(new BOSHException("Report of missing message with RID '"
+                    + reportStr
+                    + "' but local copy of that request was not found"));
+        }
+
+        // Resend the missing request
+        HTTPExchange exch = new HTTPExchange(req);
+        exchanges.add(exch);
+        notEmpty.signalAll();
+        return exch;
+    }
+
+    /**
+     * Notifies all request listeners that the specified request is being
+     * sent.
+     *
+     * @param request request being sent
+     */
+    private void fireRequestSent(final AbstractBody request) {
+        assertUnlocked();
+
+        BOSHMessageEvent event = null;
+        for (BOSHClientRequestListener listener : requestListeners) {
+            if (event == null) {
+                event = BOSHMessageEvent.createRequestSentEvent(this, request);
+            }
+            try {
+                listener.requestSent(event);
+            } catch (Exception ex) {
+                LOG.log(Level.WARNING, UNHANDLED, ex);
+            }
+        }
+    }
+
+    /**
+     * Notifies all response listeners that the specified response has been
+     * received.
+     *
+     * @param response response received
+     */
+    private void fireResponseReceived(final AbstractBody response) {
+        assertUnlocked();
+
+        BOSHMessageEvent event = null;
+        for (BOSHClientResponseListener listener : responseListeners) {
+            if (event == null) {
+                event = BOSHMessageEvent.createResponseReceivedEvent(
+                        this, response);
+            }
+            try {
+                listener.responseReceived(event);
+            } catch (Exception ex) {
+                LOG.log(Level.WARNING, UNHANDLED, ex);
+            }
+        }
+    }
+
+    /**
+     * Notifies all connection listeners that the session has been successfully
+     * established.
+     */
+    private void fireConnectionEstablished() {
+        final boolean hadLock = lock.isHeldByCurrentThread();
+        if (hadLock) {
+            lock.unlock();
+        }
+        try {
+            BOSHClientConnEvent event = null;
+            for (BOSHClientConnListener listener : connListeners) {
+                if (event == null) {
+                    event = BOSHClientConnEvent
+                            .createConnectionEstablishedEvent(this);
+                }
+                try {
+                    listener.connectionEvent(event);
+                } catch (Exception ex) {
+                    LOG.log(Level.WARNING, UNHANDLED, ex);
+                }
+            }
+        } finally {
+            if (hadLock) {
+                lock.lock();
+            }
+        }
+    }
+
+    /**
+     * Notifies all connection listeners that the session has been
+     * terminated normally.
+     */
+    private void fireConnectionClosed() {
+        assertUnlocked();
+
+        BOSHClientConnEvent event = null;
+        for (BOSHClientConnListener listener : connListeners) {
+            if (event == null) {
+                event = BOSHClientConnEvent.createConnectionClosedEvent(this);
+            }
+            try {
+                listener.connectionEvent(event);
+            } catch (Exception ex) {
+                LOG.log(Level.WARNING, UNHANDLED, ex);
+            }
+        }
+    }
+
+    /**
+     * Notifies all connection listeners that the session has been
+     * terminated due to the exceptional condition provided.
+     *
+     * @param cause cause of the termination
+     */
+    private void fireConnectionClosedOnError(
+            final Throwable cause) {
+        assertUnlocked();
+
+        BOSHClientConnEvent event = null;
+        for (BOSHClientConnListener listener : connListeners) {
+            if (event == null) {
+                event = BOSHClientConnEvent
+                        .createConnectionClosedOnErrorEvent(
+                        this, pendingRequestAcks, cause);
+            }
+            try {
+                listener.connectionEvent(event);
+            } catch (Exception ex) {
+                LOG.log(Level.WARNING, UNHANDLED, ex);
+            }
+        }
+    }
+
+}
diff --git a/src/com/kenai/jbosh/BOSHClientConfig.java b/src/com/kenai/jbosh/BOSHClientConfig.java
new file mode 100644
index 0000000..23915b6
--- /dev/null
+++ b/src/com/kenai/jbosh/BOSHClientConfig.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.net.URI;
+import javax.net.ssl.SSLContext;
+
+/**
+ * BOSH client configuration information.  Instances of this class contain
+ * all information necessary to establish connectivity with a remote
+ * connection manager.
+ * <p/>
+ * Instances of this class are immutable, thread-safe,
+ * and can be re-used to configure multiple client session instances.
+ */
+public final class BOSHClientConfig {
+
+    /**
+     * Connection manager URI.
+     */
+    private final URI uri;
+
+    /**
+     * Target domain.
+     */
+    private final String to;
+
+    /**
+     * Client ID of this station.
+     */
+    private final String from;
+
+    /**
+     * Default XML language.
+     */
+    private final String lang;
+
+    /**
+     * Routing information for messages sent to CM.
+     */
+    private final String route;
+
+    /**
+     * Proxy host.
+     */
+    private final String proxyHost;
+
+    /**
+     * Proxy port.
+     */
+    private final int proxyPort;
+
+    /**
+     * SSL context.
+     */
+    private final SSLContext sslContext;
+
+    /**
+     * Flag indicating that compression should be attempted, if possible.
+     */
+    private final boolean compressionEnabled;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Classes:
+
+    /**
+     * Class instance builder, after the builder pattern.  This allows each
+     * {@code BOSHClientConfig} instance to be immutable while providing
+     * flexibility when building new {@code BOSHClientConfig} instances.
+     * <p/>
+     * Instances of this class are <b>not</b> thread-safe.  If template-style
+     * use is desired, see the {@code create(BOSHClientConfig)} method.
+     */
+    public static final class Builder {
+        // Required args
+        private final URI bURI;
+        private final String bDomain;
+
+        // Optional args
+        private String bFrom;
+        private String bLang;
+        private String bRoute;
+        private String bProxyHost;
+        private int bProxyPort;
+        private SSLContext bSSLContext;
+        private Boolean bCompression;
+
+        /**
+         * Creates a new builder instance, used to create instances of the
+         * {@code BOSHClientConfig} class.
+         *
+         * @param cmURI URI to use to contact the connection manager
+         * @param domain target domain to communicate with
+         */
+        private Builder(final URI cmURI, final String domain) {
+            bURI = cmURI;
+            bDomain = domain;
+        }
+
+        /**
+         * Creates a new builder instance, used to create instances of the
+         * {@code BOSHClientConfig} class.
+         *
+         * @param cmURI URI to use to contact the connection manager
+         * @param domain target domain to communicate with
+         * @return builder instance
+         */
+        public static Builder create(final URI cmURI, final String domain) {
+            if (cmURI == null) {
+                throw(new IllegalArgumentException(
+                        "Connection manager URI must not be null"));
+            }
+            if (domain == null) {
+                throw(new IllegalArgumentException(
+                        "Target domain must not be null"));
+            }
+            String scheme = cmURI.getScheme();
+            if (!("http".equals(scheme) || "https".equals(scheme))) {
+                throw(new IllegalArgumentException(
+                        "Only 'http' and 'https' URI are allowed"));
+            }
+            return new Builder(cmURI, domain);
+        }
+
+        /**
+         * Creates a new builder instance using the existing configuration
+         * provided as a starting point.
+         *
+         * @param cfg configuration to copy
+         * @return builder instance
+         */
+        public static Builder create(final BOSHClientConfig cfg) {
+            Builder result = new Builder(cfg.getURI(), cfg.getTo());
+            result.bFrom = cfg.getFrom();
+            result.bLang = cfg.getLang();
+            result.bRoute = cfg.getRoute();
+            result.bProxyHost = cfg.getProxyHost();
+            result.bProxyPort = cfg.getProxyPort();
+            result.bSSLContext = cfg.getSSLContext();
+            result.bCompression = cfg.isCompressionEnabled();
+            return result;
+        }
+
+        /**
+         * Set the ID of the client station, to be forwarded to the connection
+         * manager when new sessions are created.
+         *
+         * @param id client ID
+         * @return builder instance
+         */
+        public Builder setFrom(final String id) {
+            if (id == null) {
+                throw(new IllegalArgumentException(
+                        "Client ID must not be null"));
+            }
+            bFrom = id;
+            return this;
+        }
+        
+        /**
+         * Set the default language of any human-readable content within the
+         * XML.
+         *
+         * @param lang XML language ID
+         * @return builder instance
+         */
+        public Builder setXMLLang(final String lang) {
+            if (lang == null) {
+                throw(new IllegalArgumentException(
+                        "Default language ID must not be null"));
+            }
+            bLang = lang;
+            return this;
+        }
+
+        /**
+         * Sets the destination server/domain that the client should connect to.
+         * Connection managers may be configured to enable sessions with more
+         * that one server in different domains.  When requesting a session with
+         * such a "proxy" connection manager, a client should use this method to
+         * specify the server with which it wants to communicate.
+         *
+         * @param protocol connection protocol (e.g, "xmpp")
+         * @param host host or domain to be served by the remote server.  Note
+         *  that this is not necessarily the host name or domain name of the
+         *  remote server.
+         * @param port port number of the remote server
+         * @return builder instance
+         */
+        public Builder setRoute(
+                final String protocol,
+                final String host,
+                final int port) {
+            if (protocol == null) {
+                throw(new IllegalArgumentException("Protocol cannot be null"));
+            }
+            if (protocol.contains(":")) {
+                throw(new IllegalArgumentException(
+                        "Protocol cannot contain the ':' character"));
+            }
+            if (host == null) {
+                throw(new IllegalArgumentException("Host cannot be null"));
+            }
+            if (host.contains(":")) {
+                throw(new IllegalArgumentException(
+                        "Host cannot contain the ':' character"));
+            }
+            if (port <= 0) {
+                throw(new IllegalArgumentException("Port number must be > 0"));
+            }
+            bRoute = protocol + ":" + host + ":" + port;
+            return this;
+        }
+
+        /**
+         * Specify the hostname and port of an HTTP proxy to connect through.
+         *
+         * @param hostName proxy hostname
+         * @param port proxy port number
+         * @return builder instance
+         */
+        public Builder setProxy(final String hostName, final int port) {
+            if (hostName == null || hostName.length() == 0) {
+                throw(new IllegalArgumentException(
+                        "Proxy host name cannot be null or empty"));
+            }
+            if (port <= 0) {
+                throw(new IllegalArgumentException(
+                        "Proxy port must be > 0"));
+            }
+            bProxyHost = hostName;
+            bProxyPort = port;
+            return this;
+        }
+
+        /**
+         * Set the SSL context to use for this session.  This can be used
+         * to configure certificate-based authentication, etc..
+         *
+         * @param ctx SSL context
+         * @return builder instance
+         */
+        public Builder setSSLContext(final SSLContext ctx) {
+            if (ctx == null) {
+                throw(new IllegalArgumentException(
+                        "SSL context cannot be null"));
+            }
+            bSSLContext = ctx;
+            return this;
+        }
+
+        /**
+         * Set whether or not compression of the underlying data stream
+         * should be attempted.  By default, compression is disabled.
+         *
+         * @param enabled set to {@code true} if compression should be
+         *  attempted when possible, {@code false} to disable compression
+         * @return builder instance
+         */
+        public Builder setCompressionEnabled(final boolean enabled) {
+            bCompression = Boolean.valueOf(enabled);
+            return this;
+        }
+
+        /**
+         * Build the immutable object instance with the current configuration.
+         *
+         * @return BOSHClientConfig instance
+         */
+        public BOSHClientConfig build() {
+            // Default XML language
+            String lang;
+            if (bLang == null) {
+                lang = "en";
+            } else {
+                lang = bLang;
+            }
+
+            // Default proxy port
+            int port;
+            if (bProxyHost == null) {
+                port = 0;
+            } else {
+                port = bProxyPort;
+            }
+
+            // Default compression
+            boolean compression;
+            if (bCompression == null) {
+                compression = false;
+            } else {
+                compression = bCompression.booleanValue();
+            }
+
+            return new BOSHClientConfig(
+                    bURI,
+                    bDomain,
+                    bFrom,
+                    lang,
+                    bRoute,
+                    bProxyHost,
+                    port,
+                    bSSLContext,
+                    compression);
+        }
+
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constructor:
+
+    /**
+     * Prevent direct construction.
+     *
+     * @param cURI URI of the connection manager to connect to
+     * @param cDomain the target domain of the first stream
+     * @param cFrom client ID
+     * @param cLang default XML language
+     * @param cRoute target route
+     * @param cProxyHost proxy host
+     * @param cProxyPort proxy port
+     * @param cSSLContext SSL context
+     * @param cCompression compression enabled flag
+     */
+    private BOSHClientConfig(
+            final URI cURI,
+            final String cDomain,
+            final String cFrom,
+            final String cLang,
+            final String cRoute,
+            final String cProxyHost,
+            final int cProxyPort,
+            final SSLContext cSSLContext,
+            final boolean cCompression) {
+        uri = cURI;
+        to = cDomain;
+        from = cFrom;
+        lang = cLang;
+        route = cRoute;
+        proxyHost = cProxyHost;
+        proxyPort = cProxyPort;
+        sslContext = cSSLContext;
+        compressionEnabled = cCompression;
+    }
+
+    /**
+     * Get the URI to use to contact the connection manager.
+     *
+     * @return connection manager URI.
+     */
+    public URI getURI() {
+        return uri;
+    }
+
+    /**
+     * Get the ID of the target domain.
+     *
+     * @return domain id
+     */
+    public String getTo() {
+        return to;
+    }
+
+    /**
+     * Get the ID of the local client.
+     *
+     * @return client id, or {@code null}
+     */
+    public String getFrom() {
+        return from;
+    }
+
+    /**
+     * Get the default language of any human-readable content within the
+     * XML.  Defaults to "en".
+     *
+     * @return XML language ID
+     */
+    public String getLang() {
+        return lang;
+    }
+
+    /**
+     * Get the routing information for messages sent to the CM.
+     *
+     * @return route attribute string, or {@code null} if no routing
+     *  info was provided.
+     */
+    public String getRoute() {
+        return route;
+    }
+
+    /**
+     * Get the HTTP proxy host to use.
+     *
+     * @return proxy host, or {@code null} if no proxy information was specified
+     */
+    public String getProxyHost() {
+        return proxyHost;
+    }
+
+    /**
+     * Get the HTTP proxy port to use.
+     *
+     * @return proxy port, or 0 if no proxy information was specified
+     */
+    public int getProxyPort() {
+        return proxyPort;
+    }
+
+    /**
+     * Get the SSL context to use for this session.
+     *
+     * @return SSL context instance to use, or {@code null} if no
+     *  context instance was provided.
+     */
+    public SSLContext getSSLContext() {
+        return sslContext;
+    }
+
+    /**
+     * Determines whether or not compression of the underlying data stream
+     * should be attempted/allowed.  Defaults to {@code false}.
+     *
+     * @return {@code true} if compression should be attempted, {@code false}
+     *  if compression is disabled or was not specified
+     */
+    boolean isCompressionEnabled() {
+        return compressionEnabled;
+    }
+
+}
diff --git a/src/com/kenai/jbosh/BOSHClientConnEvent.java b/src/com/kenai/jbosh/BOSHClientConnEvent.java
new file mode 100644
index 0000000..0ac7943
--- /dev/null
+++ b/src/com/kenai/jbosh/BOSHClientConnEvent.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EventObject;
+import java.util.List;
+
+/**
+ * Client connection event, notifying of changes in connection state.
+ * <p/>
+ * This class is immutable and thread-safe.
+ */
+public final class BOSHClientConnEvent extends EventObject {
+
+    /**
+     * Serialized version.
+     */
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * Boolean flag indicating whether or not a session has been established
+     * and is currently active.
+     */
+    private final boolean connected;
+
+    /**
+     * List of outstanding requests which may not have been sent and/or
+     * acknowledged by the remote CM.
+     */
+    private final List<ComposableBody> requests;
+
+    /**
+     * Cause of the session termination, or {@code null}.
+     */
+    private final Throwable cause;
+
+    /**
+     * Creates a new connection event instance.
+     *
+     * @param source event source
+     * @param cConnected flag indicating whether or not the session is
+     *  currently active
+     * @param cRequests outstanding requests when an error condition is
+     *  detected, or {@code null} when not an error condition
+     * @param cCause cause of the error condition, or {@code null} when no
+     *  error condition is present
+     */
+    private BOSHClientConnEvent(
+            final BOSHClient source,
+            final boolean cConnected,
+            final List<ComposableBody> cRequests,
+            final Throwable cCause) {
+        super(source);
+        connected = cConnected;
+        cause = cCause;
+
+        if (connected) {
+            if (cCause != null) {
+                throw(new IllegalStateException(
+                        "Cannot be connected and have a cause"));
+            }
+            if (cRequests != null && cRequests.size() > 0) {
+                throw(new IllegalStateException(
+                        "Cannot be connected and have outstanding requests"));
+            }
+        }
+
+        if (cRequests == null) {
+            requests = Collections.emptyList();
+        } else {
+            // Defensive copy:
+            requests = Collections.unmodifiableList(
+                    new ArrayList<ComposableBody>(cRequests));
+        }
+    }
+
+    /**
+     * Creates a new connection establishment event.
+     *
+     * @param source client which has become connected
+     * @return event instance
+     */
+    static BOSHClientConnEvent createConnectionEstablishedEvent(
+            final BOSHClient source) {
+        return new BOSHClientConnEvent(source, true, null, null);
+    }
+
+    /**
+     * Creates a new successful connection closed event.  This represents
+     * a clean termination of the client session.
+     *
+     * @param source client which has been disconnected
+     * @return event instance
+     */
+    static BOSHClientConnEvent createConnectionClosedEvent(
+            final BOSHClient source) {
+        return new BOSHClientConnEvent(source, false, null, null);
+    }
+
+    /**
+     * Creates a connection closed on error event.  This represents
+     * an unexpected termination of the client session.
+     *
+     * @param source client which has been disconnected
+     * @param outstanding list of requests which may not have been received
+     *  by the remote connection manager
+     * @param cause cause of termination
+     * @return event instance
+     */
+    static BOSHClientConnEvent createConnectionClosedOnErrorEvent(
+            final BOSHClient source,
+            final List<ComposableBody> outstanding,
+            final Throwable cause) {
+        return new BOSHClientConnEvent(source, false, outstanding, cause);
+    }
+
+    /**
+     * Gets the client from which this event originated.
+     *
+     * @return client instance
+     */
+    public BOSHClient getBOSHClient() {
+        return (BOSHClient) getSource();
+    }
+
+    /**
+     * Returns whether or not the session has been successfully established
+     * and is currently active.
+     *
+     * @return {@code true} if a session is active, {@code false} otherwise
+     */
+    public boolean isConnected() {
+        return connected;
+    }
+
+    /**
+     * Returns whether or not this event indicates an error condition.  This
+     * will never return {@code true} when {@code isConnected()} returns
+     * {@code true}.
+     *
+     * @return {@code true} if the event indicates a terminal error has
+     *  occurred, {@code false} otherwise.
+     */
+    public boolean isError() {
+        return cause != null;
+    }
+
+    /**
+     * Returns the underlying cause of the error condition.  This method is
+     * guaranteed to return {@code null} when @{code isError()} returns
+     * {@code false}.  Similarly, this method is guaranteed to return
+     * non-@{code null} if {@code isError()} returns {@code true}.
+     *
+     * @return underlying cause of the error condition, or {@code null} if
+     *  this event does not represent an error condition
+     */
+    public Throwable getCause() {
+        return cause;
+    }
+
+    /**
+     * Get the list of requests which may not have been sent or were not
+     * acknowledged by the remote connection manager prior to session
+     * termination.
+     *
+     * @return list of messages which may not have been received by the remote
+     *  connection manager, or an empty list if the session is still connected
+     */
+    public List<ComposableBody> getOutstandingRequests() {
+        return requests;
+    }
+    
+}
diff --git a/src/com/kenai/jbosh/BOSHClientConnListener.java b/src/com/kenai/jbosh/BOSHClientConnListener.java
new file mode 100644
index 0000000..6d646cb
--- /dev/null
+++ b/src/com/kenai/jbosh/BOSHClientConnListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Interface used by parties interested in monitoring the connection state
+ * of a client session.
+ */
+public interface BOSHClientConnListener {
+
+    /**
+     * Called when the connection state of the client which the listener
+     * is registered against has changed.  The event object supplied can
+     * be used to determine the current session state.
+     *
+     * @param connEvent connection event describing the state
+     */
+    void connectionEvent(BOSHClientConnEvent connEvent);
+    
+}
diff --git a/src/com/kenai/jbosh/BOSHClientRequestListener.java b/src/com/kenai/jbosh/BOSHClientRequestListener.java
new file mode 100644
index 0000000..2cc92f3
--- /dev/null
+++ b/src/com/kenai/jbosh/BOSHClientRequestListener.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Interface used by parties interested in monitoring outbound requests made
+ * by the client to the connection manager (CM).  No opportunity is provided
+ * to manipulate the outbound request.
+ * <p/>
+ * The messages being sent are typically modified copies of the message
+ * body provided to the {@code BOSHClient} instance, built from the
+ * originally provided message body plus additional BOSH protocol
+ * state and information.  Messages may also be sent automatically when the
+ * protocol requires it, such as maintaining a minimum number of open
+ * connections to the connection manager.
+ * <p/>
+ * Listeners are executed by the sending thread immediately prior to
+ * message transmission and should not block for any significant amount
+ * of time.
+ */
+public interface BOSHClientRequestListener {
+
+    /**
+     * Called when the listener is being notified that a client request is
+     * about to be sent to the connection manager.
+     *
+     * @param event event instance containing the message being sent
+     */
+    void requestSent(BOSHMessageEvent event);
+
+}
diff --git a/src/com/kenai/jbosh/BOSHClientResponseListener.java b/src/com/kenai/jbosh/BOSHClientResponseListener.java
new file mode 100644
index 0000000..1d86e4f
--- /dev/null
+++ b/src/com/kenai/jbosh/BOSHClientResponseListener.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Interface used by parties interested in monitoring inbound responses
+ * to the client from the connection manager (CM).  No opportunity is provided
+ * to manipulate the response.
+ * <p/>
+ * Listeners are executed by the message processing thread and should not
+ * block for any significant amount of time.
+ */
+public interface BOSHClientResponseListener {
+
+    /**
+     * Called when the listener is being notified that a response has been
+     * received from the connection manager.
+     *
+     * @param event event instance containing the message being sent
+     */
+    void responseReceived(BOSHMessageEvent event);
+
+}
diff --git a/src/com/kenai/jbosh/BOSHException.java b/src/com/kenai/jbosh/BOSHException.java
new file mode 100644
index 0000000..e0bc05b
--- /dev/null
+++ b/src/com/kenai/jbosh/BOSHException.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Exception class used by the BOSH API to minimize the number of checked
+ * exceptions which must be handled by the user of the API.
+ */
+public class BOSHException extends Exception {
+
+    /**
+     * Servial version UID.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Creates a new exception isntance with the specified descriptive message.
+     *
+     * @param msg description of the exceptional condition
+     */
+    public BOSHException(final String msg) {
+        super(msg);
+    }
+
+    /**
+     * Creates a new exception isntance with the specified descriptive
+     * message and the underlying root cause of the exceptional condition.
+     *
+     * @param msg description of the exceptional condition
+     * @param cause root cause or instigator of the condition
+     */
+    public BOSHException(final String msg, final Throwable cause) {
+        super(msg, cause);
+    }
+
+}
diff --git a/src/com/kenai/jbosh/BOSHMessageEvent.java b/src/com/kenai/jbosh/BOSHMessageEvent.java
new file mode 100644
index 0000000..550903e
--- /dev/null
+++ b/src/com/kenai/jbosh/BOSHMessageEvent.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.util.EventObject;
+
+/**
+ * Event representing a message sent to or from a BOSH connection manager.
+ * <p/>
+ * This class is immutable and thread-safe.
+ */
+public final class BOSHMessageEvent extends EventObject {
+
+    /**
+     * Serialized version.
+     */
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * Message which was sent or received.
+     */
+    private final AbstractBody body;
+
+    /**
+     * Creates a new message event instance.
+     *
+     * @param source event source
+     * @param cBody message body
+     */
+    private BOSHMessageEvent(
+            final Object source,
+            final AbstractBody cBody) {
+        super(source);
+        if (cBody == null) {
+            throw(new IllegalArgumentException(
+                    "message body may not be null"));
+        }
+        body = cBody;
+    }
+
+    /**
+     * Creates a new message event for clients sending events to the
+     * connection manager.
+     *
+     * @param source sender of the message
+     * @param body message body
+     * @return event instance
+     */
+    static BOSHMessageEvent createRequestSentEvent(
+            final BOSHClient source,
+            final AbstractBody body) {
+        return new BOSHMessageEvent(source, body);
+    }
+
+    /**
+     * Creates a new message event for clients receiving new messages
+     * from the connection manager.
+     *
+     * @param source receiver of the message
+     * @param body message body
+     * @return event instance
+     */
+    static BOSHMessageEvent createResponseReceivedEvent(
+            final BOSHClient source,
+            final AbstractBody body) {
+        return new BOSHMessageEvent(source, body);
+    }
+
+    /**
+     * Gets the message body which was sent or received.
+     *
+     * @return message body
+     */
+    public AbstractBody getBody() {
+        return body;
+    }
+    
+}
diff --git a/src/com/kenai/jbosh/BodyParser.java b/src/com/kenai/jbosh/BodyParser.java
new file mode 100644
index 0000000..5ef5276
--- /dev/null
+++ b/src/com/kenai/jbosh/BodyParser.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Interface for parser implementations to implement in order to abstract the
+ * business of XML parsing out of the Body class.  This allows us to leverage
+ * a variety of parser implementations to gain performance advantages.
+ */
+interface BodyParser {
+
+    /**
+     * Parses the XML message, extracting the useful data from the initial
+     * body element and returning it in a results object.
+     *
+     * @param xml XML to parse
+     * @return useful data parsed out of the XML
+     * @throws BOSHException on parse error
+     */
+    BodyParserResults parse(String xml) throws BOSHException;
+
+}
diff --git a/src/com/kenai/jbosh/BodyParserResults.java b/src/com/kenai/jbosh/BodyParserResults.java
new file mode 100644
index 0000000..955e4bf
--- /dev/null
+++ b/src/com/kenai/jbosh/BodyParserResults.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Data extracted from a raw XML message by a BodyParser implementation.
+ * Currently, this is limited to the attributes of the wrapper element.
+ */
+final class BodyParserResults {
+
+    /**
+     * Map of qualified names to their values.  This map is defined to
+     * match the requirement of the {@code Body} class to prevent
+     * excessive copying.
+     */
+    private final Map<BodyQName, String> attrs =
+            new HashMap<BodyQName, String>();
+
+    /**
+     * Constructor.
+     */
+    BodyParserResults() {
+        // Empty
+    }
+
+    /**
+     * Add an attribute definition to the results.
+     *
+     * @param name attribute's qualified name
+     * @param value attribute value
+     */
+    void addBodyAttributeValue(
+            final BodyQName name,
+            final String value) {
+        attrs.put(name, value);
+    }
+
+    /**
+     * Returns the map of attributes added by the parser.
+     *
+     * @return map of atributes. Note: This is the live instance, not a copy.
+     */
+    Map<BodyQName, String> getAttributes() {
+        return attrs;
+    }
+
+}
diff --git a/src/com/kenai/jbosh/BodyParserSAX.java b/src/com/kenai/jbosh/BodyParserSAX.java
new file mode 100644
index 0000000..54c6c01
--- /dev/null
+++ b/src/com/kenai/jbosh/BodyParserSAX.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.SoftReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Implementation of the BodyParser interface which uses the SAX API
+ * that is part of the JDK.  Due to the fact that we can cache and reuse
+ * SAXPArser instances, this has proven to be significantly faster than the
+ * use of the javax.xml.stream API introduced in Java 6 while simultaneously
+ * providing an implementation accessible to Java 5 users.
+ */
+final class BodyParserSAX implements BodyParser {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG =
+            Logger.getLogger(BodyParserSAX.class.getName());
+
+    /**
+     * SAX parser factory.
+     */
+    private static final SAXParserFactory SAX_FACTORY;
+    static {
+        SAX_FACTORY = SAXParserFactory.newInstance();
+        SAX_FACTORY.setNamespaceAware(true);
+        SAX_FACTORY.setValidating(false);
+    }
+
+    /**
+     * Thread local to contain a SAX parser instance for each thread that
+     * attempts to use one.  This allows us to gain an order of magnitude of
+     * performance as a result of not constructing parsers for each
+     * invocation while retaining thread safety.
+     */
+    private static final ThreadLocal<SoftReference<SAXParser>> PARSER =
+        new ThreadLocal<SoftReference<SAXParser>>() {
+            @Override protected SoftReference<SAXParser> initialValue() {
+                return new SoftReference<SAXParser>(null);
+            }
+        };
+
+    /**
+     * SAX event handler class.
+     */
+    private static final class Handler extends DefaultHandler {
+        private final BodyParserResults result;
+        private final SAXParser parser;
+        private String defaultNS = null;
+
+        private Handler(SAXParser theParser, BodyParserResults results) {
+            parser = theParser;
+            result = results;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void startElement(
+                final String uri,
+                final String localName,
+                final String qName,
+                final Attributes attributes) {
+            if (LOG.isLoggable(Level.FINEST)) {
+                LOG.finest("Start element: " + qName);
+                LOG.finest("    URI: " + uri);
+                LOG.finest("    local: " + localName);
+            }
+
+            BodyQName bodyName = AbstractBody.getBodyQName();
+            // Make sure the first element is correct
+            if (!(bodyName.getNamespaceURI().equals(uri)
+                    && bodyName.getLocalPart().equals(localName))) {
+                throw(new IllegalStateException(
+                        "Root element was not '" + bodyName.getLocalPart()
+                        + "' in the '" + bodyName.getNamespaceURI()
+                        + "' namespace.  (Was '" + localName + "' in '" + uri
+                        + "')"));
+            }
+
+            // Read in the attributes, making sure to expand the namespaces
+            // as needed.
+            for (int idx=0; idx < attributes.getLength(); idx++) {
+                String attrURI = attributes.getURI(idx);
+                if (attrURI.length() == 0) {
+                    attrURI = defaultNS;
+                }
+                String attrLN = attributes.getLocalName(idx);
+                String attrVal = attributes.getValue(idx);
+                if (LOG.isLoggable(Level.FINEST)) {
+                    LOG.finest("    Attribute: {" + attrURI + "}"
+                            + attrLN + " = '" + attrVal + "'");
+                }
+
+                BodyQName aqn = BodyQName.create(attrURI, attrLN);
+                result.addBodyAttributeValue(aqn, attrVal);
+            }
+            
+            parser.reset();
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * This implementation uses this event hook to keep track of the
+         * default namespace on the body element.
+         */
+        @Override
+        public void startPrefixMapping(
+                final String prefix,
+                final String uri) {
+            if (prefix.length() == 0) {
+                if (LOG.isLoggable(Level.FINEST)) {
+                    LOG.finest("Prefix mapping: <DEFAULT> => " + uri);
+                }
+                defaultNS = uri;
+            } else {
+                if (LOG.isLoggable(Level.FINEST)) {
+                    LOG.info("Prefix mapping: " + prefix + " => " + uri);
+                }
+            }
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // BodyParser interface methods:
+
+    /**
+     * {@inheritDoc}
+     */
+    public BodyParserResults parse(String xml) throws BOSHException {
+        BodyParserResults result = new BodyParserResults();
+        Exception thrown;
+        try {
+            InputStream inStream = new ByteArrayInputStream(xml.getBytes());
+            SAXParser parser = getSAXParser();
+            parser.parse(inStream, new Handler(parser, result));
+            return result;
+        } catch (SAXException saxx) {
+            thrown = saxx;
+        } catch (IOException iox) {
+            thrown = iox;
+        }
+        throw(new BOSHException("Could not parse body:\n" + xml, thrown));
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Private methods:
+
+    /**
+     * Gets a SAXParser for use in parsing incoming messages.
+     *
+     * @return parser instance
+     */
+    private static SAXParser getSAXParser() {
+        SoftReference<SAXParser> ref = PARSER.get();
+        SAXParser result = ref.get();
+        if (result == null) {
+            Exception thrown;
+            try {
+                result = SAX_FACTORY.newSAXParser();
+                ref = new SoftReference<SAXParser>(result);
+                PARSER.set(ref);
+                return result;
+            } catch (ParserConfigurationException ex) {
+                thrown = ex;
+            } catch (SAXException ex) {
+                thrown = ex;
+            }
+            throw(new IllegalStateException(
+                    "Could not create SAX parser", thrown));
+        } else {
+            result.reset();
+            return result;
+        }
+    }
+    
+}
diff --git a/src/com/kenai/jbosh/BodyParserXmlPull.java b/src/com/kenai/jbosh/BodyParserXmlPull.java
new file mode 100644
index 0000000..5f23b06
--- /dev/null
+++ b/src/com/kenai/jbosh/BodyParserXmlPull.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.lang.ref.SoftReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.xml.XMLConstants;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+/**
+ * Implementation of the BodyParser interface which uses the XmlPullParser
+ * API.  When available, this API provides an order of magnitude performance
+ * improvement over the default SAX parser implementation.
+ */
+final class BodyParserXmlPull implements BodyParser {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG =
+            Logger.getLogger(BodyParserXmlPull.class.getName());
+
+    /**
+     * Thread local to contain a XmlPullParser instance for each thread that
+     * attempts to use one.  This allows us to gain an order of magnitude of
+     * performance as a result of not constructing parsers for each
+     * invocation while retaining thread safety.
+     */
+    private static final ThreadLocal<SoftReference<XmlPullParser>> XPP_PARSER =
+        new ThreadLocal<SoftReference<XmlPullParser>>() {
+            @Override protected SoftReference<XmlPullParser> initialValue() {
+                return new SoftReference<XmlPullParser>(null);
+            }
+        };
+
+    ///////////////////////////////////////////////////////////////////////////
+    // BodyParser interface methods:
+
+    /**
+     * {@inheritDoc}
+     */
+    public BodyParserResults parse(final String xml) throws BOSHException {
+        BodyParserResults result = new BodyParserResults();
+        Exception thrown;
+        try {
+            XmlPullParser xpp = getXmlPullParser();
+
+            xpp.setInput(new StringReader(xml));
+            int eventType = xpp.getEventType();
+            while (eventType != XmlPullParser.END_DOCUMENT) {
+                if (eventType == XmlPullParser.START_TAG) {
+                    if (LOG.isLoggable(Level.FINEST)) {
+                        LOG.finest("Start tag: " + xpp.getName());
+                    }
+                } else {
+                    eventType = xpp.next();
+                    continue;
+                }
+
+                String prefix = xpp.getPrefix();
+                if (prefix == null) {
+                    prefix = XMLConstants.DEFAULT_NS_PREFIX;
+                }
+                String uri = xpp.getNamespace();
+                String localName = xpp.getName();
+                QName name = new QName(uri, localName, prefix);
+                if (LOG.isLoggable(Level.FINEST)) {
+                    LOG.finest("Start element: ");
+                    LOG.finest("    prefix: " + prefix);
+                    LOG.finest("    URI: " + uri);
+                    LOG.finest("    local: " + localName);
+                }
+
+                BodyQName bodyName = AbstractBody.getBodyQName();
+                if (!bodyName.equalsQName(name)) {
+                    throw(new IllegalStateException(
+                            "Root element was not '" + bodyName.getLocalPart()
+                            + "' in the '" + bodyName.getNamespaceURI()
+                            + "' namespace.  (Was '" + localName
+                            + "' in '" + uri + "')"));
+                }
+
+                for (int idx=0; idx < xpp.getAttributeCount(); idx++) {
+                    String attrURI = xpp.getAttributeNamespace(idx);
+                    if (attrURI.length() == 0) {
+                        attrURI = xpp.getNamespace(null);
+                    }
+                    String attrPrefix = xpp.getAttributePrefix(idx);
+                    if (attrPrefix == null) {
+                        attrPrefix = XMLConstants.DEFAULT_NS_PREFIX;
+                    }
+                    String attrLN = xpp.getAttributeName(idx);
+                    String attrVal = xpp.getAttributeValue(idx);
+                    BodyQName aqn = BodyQName.createWithPrefix(
+                            attrURI, attrLN, attrPrefix);
+                    if (LOG.isLoggable(Level.FINEST)) {
+                        LOG.finest("        Attribute: {" + attrURI + "}"
+                                + attrLN + " = '" + attrVal + "'");
+                    }
+                    result.addBodyAttributeValue(aqn, attrVal);
+                }
+                break;
+            }
+            return result;
+        } catch (RuntimeException rtx) {
+            thrown = rtx;
+        } catch (XmlPullParserException xmlppx) {
+            thrown = xmlppx;
+        } catch (IOException iox) {
+            thrown = iox;
+        }
+        throw(new BOSHException("Could not parse body:\n" + xml, thrown));
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Private methods:
+
+    /**
+     * Gets a XmlPullParser for use in parsing incoming messages.
+     *
+     * @return parser instance
+     */
+    private static XmlPullParser getXmlPullParser() {
+        SoftReference<XmlPullParser> ref = XPP_PARSER.get();
+        XmlPullParser result = ref.get();
+        if (result == null) {
+            Exception thrown;
+            try {
+                XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+                factory.setNamespaceAware(true);
+                factory.setValidating(false);
+                result = factory.newPullParser();
+                ref = new SoftReference<XmlPullParser>(result);
+                XPP_PARSER.set(ref);
+                return result;
+            } catch (Exception ex) {
+                thrown = ex;
+            }
+            throw(new IllegalStateException(
+                    "Could not create XmlPull parser", thrown));
+        } else {
+            return result;
+        }
+    }
+
+}
diff --git a/src/com/kenai/jbosh/BodyQName.java b/src/com/kenai/jbosh/BodyQName.java
new file mode 100644
index 0000000..83acdf1
--- /dev/null
+++ b/src/com/kenai/jbosh/BodyQName.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Qualified name of an attribute of the wrapper element.  This class is
+ * analagous to the {@code javax.xml.namespace.QName} class.
+ * Each qualified name consists of a namespace URI and a local name.
+ * <p/>
+ * Instances of this class are immutable and thread-safe.
+ */
+public final class BodyQName {
+
+    /**
+     * BOSH namespace URI.
+     */
+    static final String BOSH_NS_URI =
+            "http://jabber.org/protocol/httpbind";
+
+    /**
+     * Namespace URI.
+     */
+    private final QName qname;
+
+    /**
+     * Private constructor to prevent direct construction.
+     *
+     * @param wrapped QName instance to wrap
+     */
+    private BodyQName(
+            final QName wrapped) {
+        qname = wrapped;
+    }
+
+    /**
+     * Creates a new qualified name using a namespace URI and local name.
+     *
+     * @param uri namespace URI
+     * @param local local name
+     * @return BodyQName instance
+     */
+    public static BodyQName create(
+            final String uri,
+            final String local) {
+        return createWithPrefix(uri, local, null);
+    }
+
+    /**
+     * Creates a new qualified name using a namespace URI and local name
+     * along with an optional prefix.
+     *
+     * @param uri namespace URI
+     * @param local local name
+     * @param prefix optional prefix or @{code null} for no prefix
+     * @return BodyQName instance
+     */
+    public static BodyQName createWithPrefix(
+            final String uri,
+            final String local,
+            final String prefix) {
+        if (uri == null || uri.length() == 0) {
+            throw(new IllegalArgumentException(
+                    "URI is required and may not be null/empty"));
+        }
+        if (local == null || local.length() == 0) {
+            throw(new IllegalArgumentException(
+                    "Local arg is required and may not be null/empty"));
+        }
+        if (prefix == null || prefix.length() == 0) {
+            return new BodyQName(new QName(uri, local));
+        } else {
+            return new BodyQName(new QName(uri, local, prefix));
+        }
+    }
+
+    /**
+     * Get the namespace URI of this qualified name.
+     *
+     * @return namespace uri
+     */
+    public String getNamespaceURI() {
+        return qname.getNamespaceURI();
+    }
+
+    /**
+     * Get the local part of this qualified name.
+     *
+     * @return local name
+     */
+    public String  getLocalPart() {
+        return qname.getLocalPart();
+    }
+
+    /**
+     * Get the optional prefix used with this qualified name, or {@code null}
+     * if no prefix has been assiciated.
+     *
+     * @return prefix, or {@code null} if no prefix was supplied
+     */
+    public String getPrefix() {
+        return qname.getPrefix();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof BodyQName) {
+            BodyQName other = (BodyQName) obj;
+            return qname.equals(other.qname);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return qname.hashCode();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Package-private methods:
+
+    /**
+     * Creates a new qualified name using the BOSH namespace URI and local name.
+     *
+     * @param local local name
+     * @return BodyQName instance
+     */
+    static BodyQName createBOSH(
+            final String local) {
+        return createWithPrefix(BOSH_NS_URI, local, null);
+    }
+
+    /**
+     * Convenience method to compare this qualified name with a
+     * {@code javax.xml.namespace.QName}.
+     *
+     * @param otherName QName to compare to
+     * @return @{code true} if the qualified name is the same, {@code false}
+     *  otherwise
+     */
+    boolean equalsQName(final QName otherName) {
+        return qname.equals(otherName);
+    }
+
+}
diff --git a/src/com/kenai/jbosh/CMSessionParams.java b/src/com/kenai/jbosh/CMSessionParams.java
new file mode 100644
index 0000000..bbed628
--- /dev/null
+++ b/src/com/kenai/jbosh/CMSessionParams.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * A BOSH connection manager session instance.  This consolidates the
+ * configuration knowledge related to the CM session and provides a
+ * mechanism by which
+ */
+final class CMSessionParams {
+
+    private final AttrSessionID sid;
+
+    private final AttrWait wait;
+
+    private final AttrVersion ver;
+
+    private final AttrPolling polling;
+
+    private final AttrInactivity inactivity;
+
+    private final AttrRequests requests;
+
+    private final AttrHold hold;
+
+    private final AttrAccept accept;
+
+    private final AttrMaxPause maxPause;
+
+    private final AttrAck ack;
+
+    private final AttrCharsets charsets;
+
+    private final boolean ackingRequests;
+
+    /**
+     * Prevent direct construction.
+     */
+    private CMSessionParams(
+            final AttrSessionID aSid,
+            final AttrWait aWait,
+            final AttrVersion aVer,
+            final AttrPolling aPolling,
+            final AttrInactivity aInactivity,
+            final AttrRequests aRequests,
+            final AttrHold aHold,
+            final AttrAccept aAccept,
+            final AttrMaxPause aMaxPause,
+            final AttrAck aAck,
+            final AttrCharsets aCharsets,
+            final boolean amAckingRequests) {
+        sid = aSid;
+        wait = aWait;
+        ver = aVer;
+        polling = aPolling;
+        inactivity = aInactivity;
+        requests = aRequests;
+        hold = aHold;
+        accept = aAccept;
+        maxPause = aMaxPause;
+        ack = aAck;
+        charsets = aCharsets;
+        ackingRequests = amAckingRequests;
+    }
+
+    static CMSessionParams fromSessionInit(
+            final AbstractBody req,
+            final AbstractBody resp)
+    throws BOSHException {
+        AttrAck aAck = AttrAck.createFromString(
+                resp.getAttribute(Attributes.ACK));
+        String rid = req.getAttribute(Attributes.RID);
+        boolean acking = (aAck != null && aAck.getValue().equals(rid));
+
+        return new CMSessionParams(
+            AttrSessionID.createFromString(
+                getRequiredAttribute(resp, Attributes.SID)),
+            AttrWait.createFromString(
+                getRequiredAttribute(resp, Attributes.WAIT)),
+            AttrVersion.createFromString(
+                resp.getAttribute(Attributes.VER)),
+            AttrPolling.createFromString(
+                resp.getAttribute(Attributes.POLLING)),
+            AttrInactivity.createFromString(
+                resp.getAttribute(Attributes.INACTIVITY)),
+            AttrRequests.createFromString(
+                resp.getAttribute(Attributes.REQUESTS)),
+            AttrHold.createFromString(
+                resp.getAttribute(Attributes.HOLD)),
+            AttrAccept.createFromString(
+                resp.getAttribute(Attributes.ACCEPT)),
+            AttrMaxPause.createFromString(
+                resp.getAttribute(Attributes.MAXPAUSE)),
+            aAck,
+            AttrCharsets.createFromString(
+                resp.getAttribute(Attributes.CHARSETS)),
+            acking
+            );
+    }
+
+    private static String getRequiredAttribute(
+            final AbstractBody body,
+            final BodyQName name)
+    throws BOSHException {
+        String attrStr = body.getAttribute(name);
+        if (attrStr == null) {
+            throw(new BOSHException(
+                    "Connection Manager session creation response did not "
+                    + "include required '" + name.getLocalPart()
+                    + "' attribute"));
+        }
+        return attrStr;
+    }
+
+    AttrSessionID getSessionID() {
+        return sid;
+    }
+
+    AttrWait getWait() {
+        return wait;
+    }
+
+    AttrVersion getVersion() {
+        return ver;
+    }
+
+    AttrPolling getPollingInterval() {
+        return polling;
+    }
+
+    AttrInactivity getInactivityPeriod() {
+        return inactivity;
+    }
+
+    AttrRequests getRequests() {
+        return requests;
+    }
+
+    AttrHold getHold() {
+        return hold;
+    }
+
+    AttrAccept getAccept() {
+        return accept;
+    }
+
+    AttrMaxPause getMaxPause() {
+        return maxPause;
+    }
+
+    AttrAck getAck() {
+        return ack;
+    }
+    
+    AttrCharsets getCharsets() {
+        return charsets;
+    }
+
+    boolean isAckingRequests() {
+        return ackingRequests;
+    }
+
+}
diff --git a/src/com/kenai/jbosh/ComposableBody.java b/src/com/kenai/jbosh/ComposableBody.java
new file mode 100644
index 0000000..d375478
--- /dev/null
+++ b/src/com/kenai/jbosh/ComposableBody.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.xml.XMLConstants;
+
+/**
+ * Implementation of the {@code AbstractBody} class which allows for the
+ * definition of  messages from individual elements of a body.
+ * <p/>
+ * A message is constructed by creating a builder, manipulating the
+ * configuration of the builder, and then building it into a class instance,
+ * as in the following example:
+ * <pre>
+ * ComposableBody body = ComposableBody.builder()
+ *     .setNamespaceDefinition("foo", "http://foo.com/bar")
+ *     .setPayloadXML("<foo:data>Data to send to remote server</foo:data>")
+ *     .build();
+ * </pre>
+ * Class instances can also be "rebuilt", allowing them to be used as templates
+ * when building many similar messages:
+ * <pre>
+ * ComposableBody body2 = body.rebuild()
+ *     .setPayloadXML("<foo:data>More data to send</foo:data>")
+ *     .build();
+ * </pre>
+ * This class does only minimal syntactic and semantic checking with respect
+ * to what the generated XML will look like.  It is up to the developer to
+ * protect against the definition of malformed XML messages when building
+ * instances of this class.
+ * <p/>
+ * Instances of this class are immutable and thread-safe.
+ */
+public final class ComposableBody extends AbstractBody {
+
+    /**
+     * Pattern used to identify the beginning {@code body} element of a
+     * BOSH message.
+     */
+    private static final Pattern BOSH_START =
+            Pattern.compile("<" + "(?:(?:[^:\t\n\r >]+:)|(?:\\{[^\\}>]*?}))?"
+            + "body" + "(?:[\t\n\r ][^>]*?)?" + "(/>|>)");
+
+    /**
+     * Map of all attributes to their values.
+     */
+    private final Map<BodyQName, String> attrs;
+
+    /**
+     * Payload XML.
+     */
+    private final String payload;
+
+    /**
+     * Computed raw XML.
+     */
+    private final AtomicReference<String> computed =
+            new AtomicReference<String>();
+
+    /**
+     * Class instance builder, after the builder pattern.  This allows each
+     * message instance to be immutable while providing flexibility when
+     * building new messages.
+     * <p/>
+     * Instances of this class are <b>not</b> thread-safe.
+     */
+    public static final class Builder {
+        private Map<BodyQName, String> map;
+        private boolean doMapCopy;
+        private String payloadXML;
+
+        /**
+         * Prevent direct construction.
+         */
+        private Builder() {
+            // Empty
+        }
+
+        /**
+         * Creates a builder which is initialized to the values of the
+         * provided {@code ComposableBody} instance.  This allows an
+         * existing {@code ComposableBody} to be used as a
+         * template/starting point.
+         *
+         * @param source body template
+         * @return builder instance
+         */
+        private static Builder fromBody(final ComposableBody source) {
+            Builder result = new Builder();
+            result.map = source.getAttributes();
+            result.doMapCopy = true;
+            result.payloadXML = source.payload;
+            return result;
+        }
+
+        /**
+         * Set the body message's wrapped payload content.  Any previous
+         * content will be replaced.
+         *
+         * @param xml payload XML content
+         * @return builder instance
+         */
+        public Builder setPayloadXML(final String xml) {
+            if (xml == null) {
+                throw(new IllegalArgumentException(
+                        "payload XML argument cannot be null"));
+            }
+            payloadXML = xml;
+            return this;
+        }
+
+        /**
+         * Set an attribute on the message body / wrapper element.
+         *
+         * @param name qualified name of the attribute
+         * @param value value of the attribute
+         * @return builder instance
+         */
+        public Builder setAttribute(
+                final BodyQName name, final String value) {
+            if (map == null) {
+                map = new HashMap<BodyQName, String>();
+            } else if (doMapCopy) {
+                map = new HashMap<BodyQName, String>(map);
+                doMapCopy = false;
+            }
+            if (value == null) {
+                map.remove(name);
+            } else {
+                map.put(name, value);
+            }
+            return this;
+        }
+
+        /**
+         * Convenience method to set a namespace definition. This would result
+         * in a namespace prefix definition similar to:
+         * {@code <body xmlns:prefix="uri"/>}
+         *
+         * @param prefix prefix to define
+         * @param uri namespace URI to associate with the prefix
+         * @return builder instance
+         */
+        public Builder setNamespaceDefinition(
+                final String prefix, final String uri) {
+            BodyQName qname = BodyQName.createWithPrefix(
+                    XMLConstants.XML_NS_URI, prefix,
+                    XMLConstants.XMLNS_ATTRIBUTE);
+            return setAttribute(qname, uri);
+        }
+
+        /**
+         * Build the immutable object instance with the current configuration.
+         *
+         * @return composable body instance
+         */
+        public ComposableBody build() {
+            if (map == null) {
+                map = new HashMap<BodyQName, String>();
+            }
+            if (payloadXML == null) {
+                payloadXML = "";
+            }
+            return new ComposableBody(map, payloadXML);
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constructors:
+
+    /**
+     * Prevent direct construction.  This constructor is for body messages
+     * which are dynamically assembled.
+     */
+    private ComposableBody(
+            final Map<BodyQName, String> attrMap,
+            final String payloadXML) {
+        super();
+        attrs = attrMap;
+        payload = payloadXML;
+    }
+
+    /**
+     * Parse a static body instance into a composable instance.  This is an
+     * expensive operation and should not be used lightly.
+     * <p/>
+     * The current implementation does not obtain the payload XML by means of
+     * a proper XML parser.  It uses some string pattern searching to find the
+     * first @{code body} element and the last element's closing tag.  It is
+     * assumed that the static body's XML is well formed, etc..  This
+     * implementation may change in the future.
+     *
+     * @param body static body instance to convert
+     * @return composable bosy instance
+     * @throws BOSHException
+     */
+    static ComposableBody fromStaticBody(final StaticBody body)
+    throws BOSHException {
+        String raw = body.toXML();
+        Matcher matcher = BOSH_START.matcher(raw);
+        if (!matcher.find()) {
+            throw(new BOSHException(
+                    "Could not locate 'body' element in XML.  The raw XML did"
+                    + " not match the pattern: " + BOSH_START));
+        }
+        String payload;
+        if (">".equals(matcher.group(1))) {
+            int first = matcher.end();
+            int last = raw.lastIndexOf("</");
+            if (last < first) {
+                last = first;
+            }
+            payload = raw.substring(first, last);
+        } else {
+            payload = "";
+        }
+
+        return new ComposableBody(body.getAttributes(), payload);
+    }
+
+    /**
+     * Create a builder instance to build new instances of this class.
+     *
+     * @return AbstractBody instance
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * If this {@code ComposableBody} instance is a dynamic instance, uses this
+     * {@code ComposableBody} instance as a starting point, create a builder
+     * which can be used to create another {@code ComposableBody} instance
+     * based on this one. This allows a {@code ComposableBody} instance to be
+     * used as a template.  Note that the use of the returned builder in no
+     * way modifies or manipulates the current {@code ComposableBody} instance.
+     *
+     * @return builder instance which can be used to build similar
+     *  {@code ComposableBody} instances
+     */
+    public Builder rebuild() {
+        return Builder.fromBody(this);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Accessors:
+    
+    /**
+     * {@inheritDoc}
+     */
+    public Map<BodyQName, String> getAttributes() {
+        return Collections.unmodifiableMap(attrs);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toXML() {
+        String comp = computed.get();
+        if (comp == null) {
+            comp = computeXML();
+            computed.set(comp);
+        }
+        return comp;
+    }
+
+    /**
+     * Get the paylaod XML in String form.
+     *
+     * @return payload XML
+     */
+    public String getPayloadXML() {
+        return payload;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Private methods:
+
+    /**
+     * Escape the value of an attribute to ensure we maintain valid
+     * XML syntax.
+     *
+     * @param value value to escape
+     * @return escaped value
+     */
+    private String escape(final String value) {
+        return value.replace("'", "&apos;");
+    }
+
+    /**
+     * Generate a String representation of the message body.
+     *
+     * @return XML string representation of the body
+     */
+    private String computeXML() {
+        BodyQName bodyName = getBodyQName();
+        StringBuilder builder = new StringBuilder();
+        builder.append("<");
+        builder.append(bodyName.getLocalPart());
+        for (Map.Entry<BodyQName, String> entry : attrs.entrySet()) {
+            builder.append(" ");
+            BodyQName name = entry.getKey();
+            String prefix = name.getPrefix();
+            if (prefix != null && prefix.length() > 0) {
+                builder.append(prefix);
+                builder.append(":");
+            }
+            builder.append(name.getLocalPart());
+            builder.append("='");
+            builder.append(escape(entry.getValue()));
+            builder.append("'");
+        }
+        builder.append(" ");
+        builder.append(XMLConstants.XMLNS_ATTRIBUTE);
+        builder.append("='");
+        builder.append(bodyName.getNamespaceURI());
+        builder.append("'>");
+        if (payload != null) {
+            builder.append(payload);
+        }
+        builder.append("</body>");
+        return builder.toString();
+    }
+
+}
diff --git a/src/com/kenai/jbosh/GZIPCodec.java b/src/com/kenai/jbosh/GZIPCodec.java
new file mode 100644
index 0000000..988f27f
--- /dev/null
+++ b/src/com/kenai/jbosh/GZIPCodec.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Codec methods for compressing and uncompressing using GZIP.
+ */
+final class GZIPCodec {
+
+    /**
+     * Size of the internal buffer when decoding.
+     */
+    private static final int BUFFER_SIZE = 512;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constructors:
+
+    /**
+     * Prevent construction.
+     */
+    private GZIPCodec() {
+        // Empty
+    }
+
+    /**
+     * Returns the name of the codec.
+     *
+     * @return string name of the codec (i.e., "gzip")
+     */
+    public static String getID() {
+        return "gzip";
+    }
+
+    /**
+     * Compress/encode the data provided using the GZIP format.
+     *
+     * @param data data to compress
+     * @return compressed data
+     * @throws IOException on compression failure
+     */
+    public static byte[] encode(final byte[] data) throws IOException {
+        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+        GZIPOutputStream gzOut = null;
+        try {
+            gzOut = new GZIPOutputStream(byteOut);
+            gzOut.write(data);
+            gzOut.close();
+            byteOut.close();
+            return byteOut.toByteArray();
+        } finally {
+            gzOut.close();
+            byteOut.close();
+        }
+    }
+
+    /**
+     * Uncompress/decode the data provided using the GZIP format.
+     *
+     * @param data data to uncompress
+     * @return uncompressed data
+     * @throws IOException on decompression failure
+     */
+    public static byte[] decode(final byte[] compressed) throws IOException {
+        ByteArrayInputStream byteIn = new ByteArrayInputStream(compressed);
+        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+        GZIPInputStream gzIn = null;
+        try {
+            gzIn = new GZIPInputStream(byteIn);
+            int read;
+            byte[] buffer = new byte[BUFFER_SIZE];
+            do {
+                read = gzIn.read(buffer);
+                if (read > 0) {
+                    byteOut.write(buffer, 0, read);
+                }
+            } while (read >= 0);
+            return byteOut.toByteArray();
+        } finally {
+            gzIn.close();
+            byteOut.close();
+        }
+    }
+
+}
diff --git a/src/com/kenai/jbosh/HTTPExchange.java b/src/com/kenai/jbosh/HTTPExchange.java
new file mode 100644
index 0000000..c77caf0
--- /dev/null
+++ b/src/com/kenai/jbosh/HTTPExchange.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A request and response pair representing a single exchange with a remote
+ * content manager.  This is primarily a container class intended to maintain
+ * the relationship between the request and response but allows the response
+ * to be added after the fact.
+ */
+final class HTTPExchange {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG =
+            Logger.getLogger(HTTPExchange.class.getName());
+    
+    /**
+     * Request body.
+     */
+    private final AbstractBody request;
+
+    /**
+     * Lock instance used to protect and provide conditions.
+     */
+    private final Lock lock = new ReentrantLock();
+
+    /**
+     * Condition used to signal when the response has been set.
+     */
+    private final Condition ready = lock.newCondition();
+
+    /**
+     * HTTPResponse instance.
+     */
+    private HTTPResponse response;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constructor:
+
+    /**
+     * Create a new request/response pair object.
+     *
+     * @param req request message body
+     */
+    HTTPExchange(final AbstractBody req) {
+        if (req == null) {
+            throw(new IllegalArgumentException("Request body cannot be null"));
+        }
+        request = req;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Package-private methods:
+
+    /**
+     * Get the original request message.
+     *
+     * @return request message body.
+     */
+    AbstractBody getRequest() {
+        return request;
+    }
+
+    /**
+     * Set the HTTPResponse instance.
+     *
+     * @return HTTPResponse instance associated with the request.
+     */
+    void setHTTPResponse(HTTPResponse resp) {
+        lock.lock();
+        try {
+            if (response != null) {
+                throw(new IllegalStateException(
+                        "HTTPResponse was already set"));
+            }
+            response = resp;
+            ready.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Get the HTTPResponse instance.
+     *
+     * @return HTTPResponse instance associated with the request.
+     */
+    HTTPResponse getHTTPResponse() {
+        lock.lock();
+        try {
+            while (response == null) {
+                try {
+                    ready.await();
+                } catch (InterruptedException intx) {
+                    LOG.log(Level.FINEST, "Interrupted", intx);
+                }
+            }
+            return response;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+}
diff --git a/src/com/kenai/jbosh/HTTPResponse.java b/src/com/kenai/jbosh/HTTPResponse.java
new file mode 100644
index 0000000..f1f301c
--- /dev/null
+++ b/src/com/kenai/jbosh/HTTPResponse.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * This class represents a complete HTTP response to a request made via
+ * a {@code HTTPSender} send request.  Instances of this interface are
+ * intended to represent a deferred, future response, not necessarily a
+ * response which is immediately available.
+ */
+interface HTTPResponse {
+
+    /**
+     * Close out any resources still held by the original request.  The
+     * conversation may need to be aborted if the session it was a part of
+     * gets abruptly terminated.
+     */
+    void abort();
+
+    /**
+     * Get the HTTP status code of the response (e.g., 200, 404, etc.).  If
+     * the response has not yet been received from the remote server, this
+     * method should block until the response has arrived.
+     *
+     * @return HTTP status code
+     * @throws InterruptedException if interrupted while awaiting response
+     */
+    int getHTTPStatus() throws InterruptedException, BOSHException;
+
+    /**
+     * Get the HTTP response message body.  If the response has not yet been
+     * received from the remote server, this method should block until the
+     * response has arrived.
+     *
+     * @return response message body
+     * @throws InterruptedException if interrupted while awaiting response
+     */
+    AbstractBody getBody() throws InterruptedException, BOSHException;
+    
+}
diff --git a/src/com/kenai/jbosh/HTTPSender.java b/src/com/kenai/jbosh/HTTPSender.java
new file mode 100644
index 0000000..486d274
--- /dev/null
+++ b/src/com/kenai/jbosh/HTTPSender.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+/**
+ * Interface used to represent code which can send a BOSH XML body over
+ * HTTP to a connection manager.
+ */
+interface HTTPSender {
+
+    /**
+     * Initialize the HTTP sender instance for use with the session provided.
+     * This method will be called once before use of the service instance.
+     *
+     * @param sessionCfg session configuration
+     */
+    void init(BOSHClientConfig sessionCfg);
+
+    /**
+     * Dispose of all resources used to provide the required services.  This
+     * method will be called once when the service instance is no longer
+     * required.
+     */
+    void destroy();
+
+    /**
+     * Create a {@code Callable} instance which can be used to send the
+     * request specified to the connection manager.  This method should
+     * return immediately, prior to doing any real work.  The invocation
+     * of the returned {@code Callable} should send the request (if it has
+     * not already been sent by the time of the call), block while waiting
+     * for the response, and then return the response body.
+     *
+     * @param params CM session creation resopnse params
+     * @param body request body to send
+     * @return callable used to access the response
+     */
+    HTTPResponse send(CMSessionParams params, AbstractBody body);
+    
+}
diff --git a/src/com/kenai/jbosh/QName.java b/src/com/kenai/jbosh/QName.java
new file mode 100644
index 0000000..d395a06
--- /dev/null
+++ b/src/com/kenai/jbosh/QName.java
@@ -0,0 +1,269 @@
+/*
+ * The Apache Software License, Version 1.1
+ *
+ *
+ * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by the
+ *        Apache Software Foundation (http://www.apache.org/)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Axis" and "Apache Software Foundation" must
+ *    not be used to endorse or promote products derived from this
+ *    software without prior written permission. For written
+ *    permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ *    nor may "Apache" appear in their name, without prior written
+ *    permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+package com.kenai.jbosh;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
+/**
+ * <code>QName</code> class represents the value of a qualified name
+ * as specified in <a href="http://www.w3.org/TR/xmlschema-2/#QName">XML
+ * Schema Part2: Datatypes specification</a>.
+ * <p>
+ * The value of a QName contains a <b>namespaceURI</b>, a <b>localPart</b> and a <b>prefix</b>.
+ * The localPart provides the local part of the qualified name. The
+ * namespaceURI is a URI reference identifying the namespace.
+ *
+ * @version 1.1
+ */
+public class QName implements Serializable {
+
+    /** comment/shared empty string */
+    private static final String emptyString = "".intern();
+
+    /** Field namespaceURI */
+    private String namespaceURI;
+
+    /** Field localPart */
+    private String localPart;
+
+    /** Field prefix */
+    private String prefix;
+
+    /**
+     * Constructor for the QName.
+     *
+     * @param localPart Local part of the QName
+     */
+    public QName(String localPart) {
+        this(emptyString, localPart, emptyString);
+    }
+
+    /**
+     * Constructor for the QName.
+     *
+     * @param namespaceURI Namespace URI for the QName
+     * @param localPart Local part of the QName.
+     */
+    public QName(String namespaceURI, String localPart) {
+        this(namespaceURI, localPart, emptyString);
+    }
+
+    /**
+     * Constructor for the QName.
+     *
+     * @param namespaceURI Namespace URI for the QName
+     * @param localPart Local part of the QName.
+     * @param prefix Prefix of the QName.
+     */
+    public QName(String namespaceURI, String localPart, String prefix) {
+        this.namespaceURI = (namespaceURI == null)
+                ? emptyString
+                : namespaceURI.intern();
+        if (localPart == null) {
+            throw new IllegalArgumentException("invalid QName local part");
+        } else {
+            this.localPart = localPart.intern();
+        }
+
+        if (prefix == null) {
+            throw new IllegalArgumentException("invalid QName prefix");
+        } else {
+            this.prefix = prefix.intern();
+        }
+    }
+
+    /**
+     * Gets the Namespace URI for this QName
+     *
+     * @return Namespace URI
+     */
+    public String getNamespaceURI() {
+        return namespaceURI;
+    }
+
+    /**
+     * Gets the Local part for this QName
+     *
+     * @return Local part
+     */
+    public String getLocalPart() {
+        return localPart;
+    }
+
+    /**
+     * Gets the Prefix for this QName
+     *
+     * @return Prefix
+     */
+    public String getPrefix() {
+        return prefix;
+    }
+
+    /**
+     * Returns a string representation of this QName
+     *
+     * @return  a string representation of the QName
+     */
+    public String toString() {
+
+        return ((namespaceURI == emptyString)
+                ? localPart
+                : '{' + namespaceURI + '}' + localPart);
+    }
+
+    /**
+     * Tests this QName for equality with another object.
+     * <p>
+     * If the given object is not a QName or is null then this method
+     * returns <tt>false</tt>.
+     * <p>
+     * For two QNames to be considered equal requires that both
+     * localPart and namespaceURI must be equal. This method uses
+     * <code>String.equals</code> to check equality of localPart
+     * and namespaceURI. Any class that extends QName is required
+     * to satisfy this equality contract.
+     * <p>
+     * This method satisfies the general contract of the <code>Object.equals</code> method.
+     *
+     * @param obj the reference object with which to compare
+     *
+     * @return <code>true</code> if the given object is identical to this
+     *      QName: <code>false</code> otherwise.
+     */
+    public final boolean equals(Object obj) {
+
+        if (obj == this) {
+            return true;
+        }
+
+        if (!(obj instanceof QName)) {
+            return false;
+        }
+
+        if ((namespaceURI == ((QName) obj).namespaceURI)
+                && (localPart == ((QName) obj).localPart)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns a QName holding the value of the specified String.
+     * <p>
+     * The string must be in the form returned by the QName.toString()
+     * method, i.e. "{namespaceURI}localPart", with the "{namespaceURI}"
+     * part being optional.
+     * <p>
+     * This method doesn't do a full validation of the resulting QName.
+     * In particular, it doesn't check that the resulting namespace URI
+     * is a legal URI (per RFC 2396 and RFC 2732), nor that the resulting
+     * local part is a legal NCName per the XML Namespaces specification.
+     *
+     * @param s the string to be parsed
+     * @throws java.lang.IllegalArgumentException If the specified String cannot be parsed as a QName
+     * @return QName corresponding to the given String
+     */
+    public static QName valueOf(String s) {
+
+        if ((s == null) || s.equals("")) {
+            throw new IllegalArgumentException("invalid QName literal");
+        }
+
+        if (s.charAt(0) == '{') {
+            int i = s.indexOf('}');
+
+            if (i == -1) {
+                throw new IllegalArgumentException("invalid QName literal");
+            }
+
+            if (i == s.length() - 1) {
+                throw new IllegalArgumentException("invalid QName literal");
+            } else {
+                return new QName(s.substring(1, i), s.substring(i + 1));
+            }
+        } else {
+            return new QName(s);
+        }
+    }
+
+    /**
+     * Returns a hash code value for this QName object. The hash code
+     * is based on both the localPart and namespaceURI parts of the
+     * QName. This method satisfies the  general contract of the
+     * <code>Object.hashCode</code> method.
+     *
+     * @return a hash code value for this Qname object
+     */
+    public final int hashCode() {
+        return namespaceURI.hashCode() ^ localPart.hashCode();
+    }
+
+    /**
+     * Ensure that deserialization properly interns the results.
+     * @param in the ObjectInputStream to be read
+     */
+    private void readObject(ObjectInputStream in) throws
+            IOException, ClassNotFoundException {
+        in.defaultReadObject();
+
+        namespaceURI = namespaceURI.intern();
+        localPart = localPart.intern();
+        prefix = prefix.intern();
+    }
+}
+
diff --git a/src/com/kenai/jbosh/RequestIDSequence.java b/src/com/kenai/jbosh/RequestIDSequence.java
new file mode 100644
index 0000000..14b1475
--- /dev/null
+++ b/src/com/kenai/jbosh/RequestIDSequence.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.security.SecureRandom;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Request ID sequence generator.  This generator generates a random first
+ * RID and then manages the sequence from there on out.
+ */
+final class RequestIDSequence {
+
+    /**
+     * Maximum number of bits available for representing request IDs, according
+     * to the XEP-0124 spec.s
+     */
+    private static final int MAX_BITS = 53;
+
+    /**
+     * Bits devoted to incremented values.
+     */
+    private static final int INCREMENT_BITS = 32;
+
+    /**
+     * Minimum number of times the initial RID can be incremented before
+     * exceeding the maximum.
+     */
+    private static final long MIN_INCREMENTS = 1L << INCREMENT_BITS;
+
+    /**
+     * Max initial value. 
+     */
+    private static final long MAX_INITIAL = (1L << MAX_BITS) - MIN_INCREMENTS;
+
+    /**
+     * Max bits mask.
+     */
+    private static final long MASK = ~(Long.MAX_VALUE << MAX_BITS);
+
+    /**
+     * Random number generator.
+     */
+    private static final SecureRandom RAND = new SecureRandom();
+
+    /**
+     * Internal lock.
+     */
+    private static final Lock LOCK = new ReentrantLock();
+
+    /**
+     * The last reqest ID used, or &lt;= 0 if a new request ID needs to be
+     * generated.
+     */
+    private AtomicLong nextRequestID = new AtomicLong();
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constructors:
+
+    /**
+     * Prevent direct construction.
+     */
+    RequestIDSequence() {
+        nextRequestID = new AtomicLong(generateInitialValue());
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Public methods:
+
+    /**
+     * Calculates the next request ID value to use.  This number must be
+     * initialized such that it is unlikely to ever exceed 2 ^ 53, according
+     * to XEP-0124.
+     *
+     * @return next request ID value
+     */
+    public long getNextRID() {
+        return nextRequestID.getAndIncrement();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Private methods:
+
+    /**
+     * Generates an initial RID value by generating numbers until a number is
+     * found which is smaller than the maximum allowed value and greater
+     * than zero.
+     *
+     * @return random initial value
+     */
+    private long generateInitialValue() {
+        long result;
+        LOCK.lock();
+        try {
+            do {
+                result = RAND.nextLong() & MASK;
+            } while (result > MAX_INITIAL);
+        } finally {
+            LOCK.unlock();
+        }
+        return result;
+    }
+
+}
diff --git a/src/com/kenai/jbosh/ServiceLib.java b/src/com/kenai/jbosh/ServiceLib.java
new file mode 100644
index 0000000..07d0556
--- /dev/null
+++ b/src/com/kenai/jbosh/ServiceLib.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Utility library for use in loading services using the Jar Service
+ * Provider Interface (Jar SPI).  This can be replaced once the minimum
+ * java rev moves beyond Java 5.
+ */
+final class ServiceLib {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG =
+            Logger.getLogger(ServiceLib.class.getName());
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Package-private methods:
+
+    /**
+     * Prevent construction.
+     */
+    private ServiceLib() {
+        // Empty
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Package-private methods:
+
+    /**
+     * Probe for and select an implementation of the specified service
+     * type by using the a modified Jar SPI mechanism.  Modified in that
+     * the system properties will be checked to see if there is a value
+     * set for the naem of the class to be loaded.  If so, that value is
+     * treated as the class name of the first implementation class to be
+     * attempted to be loaded.  This provides a (unsupported) mechanism
+     * to insert other implementations.  Note that the supported mechanism
+     * is by properly ordering the classpath.
+     *
+     * @return service instance
+     * @throws IllegalStateException is no service implementations could be
+     *  instantiated
+     */
+    static <T> T loadService(Class<T> ofType) {
+        List<String> implClasses = loadServicesImplementations(ofType);
+        for (String implClass : implClasses) {
+            T result = attemptLoad(ofType, implClass);
+            if (result != null) {
+                if (LOG.isLoggable(Level.FINEST)) {
+                    LOG.finest("Selected " + ofType.getSimpleName()
+                            + " implementation: "
+                            + result.getClass().getName());
+                }
+                return result;
+            }
+        }
+        throw(new IllegalStateException(
+                "Could not load " + ofType.getName() + " implementation"));
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Private methods:
+
+    /**
+     * Generates a list of implementation class names by using
+     * the Jar SPI technique.  The order in which the class names occur
+     * in the service manifest is significant.
+     *
+     * @return list of all declared implementation class names
+     */
+    private static List<String> loadServicesImplementations(
+            final Class ofClass) {
+        List<String> result = new ArrayList<String>();
+
+        // Allow a sysprop to specify the first candidate
+        String override = System.getProperty(ofClass.getName());
+        if (override != null) {
+            result.add(override);
+        }
+
+        ClassLoader loader = ServiceLib.class.getClassLoader();
+        URL url = loader.getResource("META-INF/services/" + ofClass.getName());
+        InputStream inStream = null;
+        InputStreamReader reader = null;
+        BufferedReader bReader = null;
+        try {
+            inStream = url.openStream();
+            reader = new InputStreamReader(inStream);
+            bReader = new BufferedReader(reader);
+            String line;
+            while ((line = bReader.readLine()) != null) {
+                if (!line.matches("\\s*(#.*)?")) {
+                    // not a comment or blank line
+                    result.add(line.trim());
+                }
+            }
+        } catch (IOException iox) {
+            LOG.log(Level.WARNING,
+                    "Could not load services descriptor: " + url.toString(),
+                    iox);
+        } finally {
+            finalClose(bReader);
+            finalClose(reader);
+            finalClose(inStream);
+        }
+        return result;
+    }
+
+    /**
+     * Attempts to load the specified implementation class.
+     * Attempts will fail if - for example - the implementation depends
+     * on a class not found on the classpath.
+     *
+     * @param className implementation class to attempt to load
+     * @return service instance, or {@code null} if the instance could not be
+     *  loaded
+     */
+    private static <T> T attemptLoad(
+            final Class<T> ofClass,
+            final String className) {
+        if (LOG.isLoggable(Level.FINEST)) {
+            LOG.finest("Attempting service load: " + className);
+        }
+        Level level;
+        Exception thrown;
+        try {
+            Class clazz = Class.forName(className);
+            if (!ofClass.isAssignableFrom(clazz)) {
+                if (LOG.isLoggable(Level.WARNING)) {
+                    LOG.warning(clazz.getName() + " is not assignable to "
+                            + ofClass.getName());
+                }
+                return null;
+            }
+            return ofClass.cast(clazz.newInstance());
+        } catch (ClassNotFoundException ex) {
+            level = Level.FINEST;
+            thrown = ex;
+        } catch (InstantiationException ex) {
+            level = Level.WARNING;
+            thrown = ex;
+        } catch (IllegalAccessException ex) {
+            level = Level.WARNING;
+            thrown = ex;
+        }
+        LOG.log(level,
+                "Could not load " + ofClass.getSimpleName()
+                + " instance: " + className,
+                thrown);
+        return null;
+    }
+
+    /**
+     * Check and close a closeable object, trapping and ignoring any
+     * exception that might result.
+     *
+     * @param closeMe the thing to close
+     */
+    private static void finalClose(final Closeable closeMe) {
+        if (closeMe != null) {
+            try {
+                closeMe.close();
+            } catch (IOException iox) {
+                LOG.log(Level.FINEST, "Could not close: " + closeMe, iox);
+            }
+        }
+    }
+
+}
diff --git a/src/com/kenai/jbosh/StaticBody.java b/src/com/kenai/jbosh/StaticBody.java
new file mode 100644
index 0000000..fe225fb
--- /dev/null
+++ b/src/com/kenai/jbosh/StaticBody.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Implementation of the {@code AbstractBody} class which allows for the
+ * definition of messages from pre-existing message content.  Instances of
+ * this class are based on the underlying data and therefore cannot be
+ * modified.  In order to obtain the wrapper element namespace and
+ * attribute information, the body content is partially parsed.
+ * <p/>
+ * This class does only minimal syntactic and semantic checking with respect
+ * to what the generated XML will look like.  It is up to the developer to
+ * protect against the definition of malformed XML messages when building
+ * instances of this class.
+ * <p/>
+ * Instances of this class are immutable and thread-safe.
+ */
+final class StaticBody extends AbstractBody {
+
+    /**
+     * Selected parser to be used to process raw XML messages.
+     */
+    private static final BodyParser PARSER =
+            new BodyParserXmlPull();
+
+    /**
+     * Size of the internal buffer when copying from a stream.
+     */
+    private static final int BUFFER_SIZE = 1024;
+
+    /**
+     * Map of all attributes to their values.
+     */
+    private final Map<BodyQName, String> attrs;
+
+    /**
+     * This body message in raw XML form.
+     */
+    private final String raw;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constructors:
+
+    /**
+     * Prevent direct construction.
+     */
+    private StaticBody(
+            final Map<BodyQName, String> attrMap,
+            final String rawXML) {
+        attrs = attrMap;
+        raw = rawXML;
+    }
+
+    /**
+     * Creates an instance which is initialized by reading a body
+     * message from the provided stream.
+     *
+     * @param inStream stream to read message XML from
+     * @return body instance
+     * @throws BOSHException on parse error
+     */
+    public static StaticBody fromStream(
+            final InputStream inStream)
+            throws BOSHException {
+        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+        try {
+            byte[] buffer = new byte[BUFFER_SIZE];
+            int read;
+            do {
+                read = inStream.read(buffer);
+                if (read > 0) {
+                    byteOut.write(buffer, 0, read);
+                }
+            } while (read >= 0);
+        } catch (IOException iox) {
+            throw(new BOSHException(
+                    "Could not read body data", iox));
+        }
+        return fromString(byteOut.toString());
+    }
+
+    /**
+     * Creates an instance which is initialized by reading a body
+     * message from the provided raw XML string.
+     *
+     * @param rawXML raw message XML in string form
+     * @return body instance
+     * @throws BOSHException on parse error
+     */
+    public static StaticBody fromString(
+            final String rawXML)
+            throws BOSHException {
+        BodyParserResults results = PARSER.parse(rawXML);
+        return new StaticBody(results.getAttributes(), rawXML);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Map<BodyQName, String> getAttributes() {
+        return Collections.unmodifiableMap(attrs);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toXML() {
+        return raw;
+    }
+
+}
diff --git a/src/com/kenai/jbosh/TerminalBindingCondition.java b/src/com/kenai/jbosh/TerminalBindingCondition.java
new file mode 100644
index 0000000..0aecfd8
--- /dev/null
+++ b/src/com/kenai/jbosh/TerminalBindingCondition.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Terminal binding conditions and their associated messages.
+ */
+final class TerminalBindingCondition {
+
+    /**
+     * Map of condition names to condition instances.
+     */
+    private static final Map<String, TerminalBindingCondition>
+            COND_TO_INSTANCE = new HashMap<String, TerminalBindingCondition>();
+
+    /**
+     * Map of HTTP response codes to condition instances.
+     */
+    private static final Map<Integer, TerminalBindingCondition>
+            CODE_TO_INSTANCE = new HashMap<Integer, TerminalBindingCondition>();
+
+    static final TerminalBindingCondition BAD_REQUEST =
+            createWithCode("bad-request", "The format of an HTTP header or "
+            + "binding element received from the client is unacceptable "
+            + "(e.g., syntax error).", Integer.valueOf(400));
+
+    static final TerminalBindingCondition HOST_GONE =
+            create("host-gone", "The target domain specified in the 'to' "
+            + "attribute or the target host or port specified in the 'route' "
+            + "attribute is no longer serviced by the connection manager.");
+
+    static final TerminalBindingCondition HOST_UNKNOWN =
+            create("host-unknown", "The target domain specified in the 'to' "
+            + "attribute or the target host or port specified in the 'route' "
+            + "attribute is unknown to the connection manager.");
+
+    static final TerminalBindingCondition IMPROPER_ADDRESSING =
+            create("improper-addressing", "The initialization element lacks a "
+            + "'to' or 'route' attribute (or the attribute has no value) but "
+            + "the connection manager requires one.");
+
+    static final TerminalBindingCondition INTERNAL_SERVER_ERROR =
+            create("internal-server-error", "The connection manager has "
+            + "experienced an internal error that prevents it from servicing "
+            + "the request.");
+
+    static final TerminalBindingCondition ITEM_NOT_FOUND =
+            createWithCode("item-not-found", "(1) 'sid' is not valid, (2) "
+            + "'stream' is not valid, (3) 'rid' is larger than the upper limit "
+            + "of the expected window, (4) connection manager is unable to "
+            + "resend response, (5) 'key' sequence is invalid.",
+            Integer.valueOf(404));
+
+    static final TerminalBindingCondition OTHER_REQUEST =
+            create("other-request", "Another request being processed at the "
+            + "same time as this request caused the session to terminate.");
+
+    static final TerminalBindingCondition POLICY_VIOLATION =
+            createWithCode("policy-violation", "The client has broken the "
+            + "session rules (polling too frequently, requesting too "
+            + "frequently, sending too many simultaneous requests).",
+            Integer.valueOf(403));
+
+    static final TerminalBindingCondition REMOTE_CONNECTION_FAILED =
+            create("remote-connection-failed", "The connection manager was "
+            + "unable to connect to, or unable to connect securely to, or has "
+            + "lost its connection to, the server.");
+
+    static final TerminalBindingCondition REMOTE_STREAM_ERROR =
+            create("remote-stream-error", "Encapsulated transport protocol "
+            + "error.");
+
+    static final TerminalBindingCondition SEE_OTHER_URI =
+            create("see-other-uri", "The connection manager does not operate "
+            + "at this URI (e.g., the connection manager accepts only SSL or "
+            + "TLS connections at some https: URI rather than the http: URI "
+            + "requested by the client).");
+
+    static final TerminalBindingCondition SYSTEM_SHUTDOWN =
+            create("system-shutdown", "The connection manager is being shut "
+            + "down. All active HTTP sessions are being terminated. No new "
+            + "sessions can be created.");
+
+    static final TerminalBindingCondition UNDEFINED_CONDITION =
+            create("undefined-condition", "Unknown or undefined error "
+            + "condition.");
+
+    /**
+     * Condition name.
+     */
+    private final String cond;
+
+    /**
+     * Descriptive message.
+     */
+    private final String msg;
+
+    /**
+     * Private constructor to pre
+     */
+    private TerminalBindingCondition(
+            final String condition,
+            final String message) {
+        cond = condition;
+        msg = message;
+    }
+    
+    /**
+     * Helper method to call the helper method to add entries.
+     */
+    private static TerminalBindingCondition create(
+            final String condition,
+            final String message) {
+        return createWithCode(condition, message, null);
+    }
+    
+    /**
+     * Helper method to add entries.
+     */
+    private static TerminalBindingCondition createWithCode(
+            final String condition,
+            final String message,
+            final Integer code) {
+        if (condition == null) {
+            throw(new IllegalArgumentException(
+                    "condition may not be null"));
+        }
+        if (message == null) {
+            throw(new IllegalArgumentException(
+                    "message may not be null"));
+        }
+        if (COND_TO_INSTANCE.get(condition) != null) {
+            throw(new IllegalStateException(
+                    "Multiple definitions of condition: " + condition));
+        }
+        TerminalBindingCondition result =
+                new TerminalBindingCondition(condition, message);
+        COND_TO_INSTANCE.put(condition, result);
+        if (code != null) {
+            if (CODE_TO_INSTANCE.get(code) != null) {
+                throw(new IllegalStateException(
+                        "Multiple definitions of code: " + code));
+            }
+            CODE_TO_INSTANCE.put(code, result);
+        }
+        return result;
+    }
+
+    /**
+     * Lookup the terminal binding condition instance with the condition
+     * name specified.
+     *
+     * @param condStr condition name
+     * @return terminal binding condition instance, or {@code null} if no
+     *  instance is known by the name specified
+     */
+    static TerminalBindingCondition forString(final String condStr) {
+        return COND_TO_INSTANCE.get(condStr);
+    }
+
+    /**
+     * Lookup the terminal binding condition instance associated with the
+     * HTTP response code specified.
+     *
+     * @param httpRespCode HTTP response code
+     * @return terminal binding condition instance, or {@code null} if no
+     *  instance is known by the response code specified
+     */
+    static TerminalBindingCondition forHTTPResponseCode(final int httpRespCode) {
+        return CODE_TO_INSTANCE.get(Integer.valueOf(httpRespCode));
+    }
+
+    /**
+     * Get the name of the condition.
+     *
+     * @return condition name
+     */
+    String getCondition() {
+        return cond;
+    }
+
+    /**
+     * Get the human readable error message associated with this condition.
+     *
+     * @return error message
+     */
+    String getMessage() {
+        return msg;
+    }
+
+}
diff --git a/src/com/kenai/jbosh/ZLIBCodec.java b/src/com/kenai/jbosh/ZLIBCodec.java
new file mode 100644
index 0000000..20844ad
--- /dev/null
+++ b/src/com/kenai/jbosh/ZLIBCodec.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2009 Mike Cumings
+ *
+ * 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 com.kenai.jbosh;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.InflaterInputStream;
+
+/**
+ * Codec methods for compressing and uncompressing using ZLIB.
+ */
+final class ZLIBCodec {
+
+    /**
+     * Size of the internal buffer when decoding.
+     */
+    private static final int BUFFER_SIZE = 512;
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Constructors:
+
+    /**
+     * Prevent construction.
+     */
+    private ZLIBCodec() {
+        // Empty
+    }
+
+    /**
+     * Returns the name of the codec.
+     *
+     * @return string name of the codec (i.e., "deflate")
+     */
+    public static String getID() {
+        return "deflate";
+    }
+
+    /**
+     * Compress/encode the data provided using the ZLIB format.
+     *
+     * @param data data to compress
+     * @return compressed data
+     * @throws IOException on compression failure
+     */
+    public static byte[] encode(final byte[] data) throws IOException {
+        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+        DeflaterOutputStream deflateOut = null;
+        try {
+            deflateOut = new DeflaterOutputStream(byteOut);
+            deflateOut.write(data);
+            deflateOut.close();
+            byteOut.close();
+            return byteOut.toByteArray();
+        } finally {
+            deflateOut.close();
+            byteOut.close();
+        }
+    }
+
+    /**
+     * Uncompress/decode the data provided using the ZLIB format.
+     *
+     * @param data data to uncompress
+     * @return uncompressed data
+     * @throws IOException on decompression failure
+     */
+    public static byte[] decode(final byte[] compressed) throws IOException {
+        ByteArrayInputStream byteIn = new ByteArrayInputStream(compressed);
+        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+        InflaterInputStream inflaterIn = null;
+        try {
+            inflaterIn = new InflaterInputStream(byteIn);
+            int read;
+            byte[] buffer = new byte[BUFFER_SIZE];
+            do {
+                read = inflaterIn.read(buffer);
+                if (read > 0) {
+                    byteOut.write(buffer, 0, read);
+                }
+            } while (read >= 0);
+            return byteOut.toByteArray();
+        } finally {
+            inflaterIn.close();
+            byteOut.close();
+        }
+    }
+
+}
diff --git a/src/com/kenai/jbosh/package.html b/src/com/kenai/jbosh/package.html
new file mode 100644
index 0000000..77a1924
--- /dev/null
+++ b/src/com/kenai/jbosh/package.html
@@ -0,0 +1,8 @@
+<html>
+    <body>
+        Core classes of the JBOSH API.
+        <p/>
+        Users of the client portion of the API should start by reading
+        up on the <code>BOSHClient</code> documentation.
+    </body>
+</html>
\ No newline at end of file
diff --git a/src/com/novell/sasl/client/DigestChallenge.java b/src/com/novell/sasl/client/DigestChallenge.java
new file mode 100644
index 0000000..90e6247
--- /dev/null
+++ b/src/com/novell/sasl/client/DigestChallenge.java
@@ -0,0 +1,393 @@
+/* **************************************************************************
+ * $OpenLDAP: /com/novell/sasl/client/DigestChallenge.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $
+ *
+ * Copyright (C) 2003 Novell, Inc. All Rights Reserved.
+ *
+ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
+ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
+ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
+ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
+ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
+ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
+ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
+ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
+ ******************************************************************************/
+package com.novell.sasl.client;
+
+import java.util.*;
+import org.apache.harmony.javax.security.sasl.*;
+
+/**
+ * Implements the DigestChallenge class which will be used by the
+ * DigestMD5SaslClient class
+ */
+class DigestChallenge extends Object
+{
+    public static final int QOP_AUTH           =    0x01;
+    public static final int QOP_AUTH_INT       =    0x02;
+    public static final int QOP_AUTH_CONF       =    0x04;
+    public static final int QOP_UNRECOGNIZED   =    0x08;
+
+    private static final int CIPHER_3DES          = 0x01;
+    private static final int CIPHER_DES           = 0x02;
+    private static final int CIPHER_RC4_40        = 0x04;
+    private static final int CIPHER_RC4           = 0x08;
+    private static final int CIPHER_RC4_56        = 0x10;
+    private static final int CIPHER_UNRECOGNIZED  = 0x20;
+    private static final int CIPHER_RECOGNIZED_MASK =
+     CIPHER_3DES | CIPHER_DES | CIPHER_RC4_40 | CIPHER_RC4 | CIPHER_RC4_56;
+
+    private ArrayList m_realms;
+    private String    m_nonce;
+    private int       m_qop;
+    private boolean   m_staleFlag;
+    private int       m_maxBuf;
+    private String    m_characterSet;
+    private String    m_algorithm;
+    private int       m_cipherOptions;
+
+    DigestChallenge(
+        byte[] challenge)
+            throws SaslException
+    {
+        m_realms = new ArrayList(5);
+        m_nonce = null;
+        m_qop = 0;
+        m_staleFlag = false;
+        m_maxBuf = -1;
+        m_characterSet = null;
+        m_algorithm = null;
+        m_cipherOptions = 0;
+
+        DirectiveList dirList = new DirectiveList(challenge);
+        try
+        {
+            dirList.parseDirectives();
+            checkSemantics(dirList);
+        }
+        catch (SaslException e)
+        {
+        }
+    }
+
+    /**
+     * Checks the semantics of the directives in the directive list as parsed
+     * from the digest challenge byte array.
+     *
+     * @param dirList  the list of directives parsed from the digest challenge
+     *
+     * @exception SaslException   If a semantic error occurs
+     */
+    void checkSemantics(
+        DirectiveList dirList) throws SaslException
+    {
+    Iterator        directives = dirList.getIterator();
+    ParsedDirective directive;
+    String          name;
+
+    while (directives.hasNext())
+    {
+        directive = (ParsedDirective)directives.next();
+        name = directive.getName();
+        if (name.equals("realm"))
+            handleRealm(directive);
+        else if (name.equals("nonce"))
+            handleNonce(directive);
+        else if (name.equals("qop"))
+            handleQop(directive);
+        else if (name.equals("maxbuf"))
+            handleMaxbuf(directive);
+        else if (name.equals("charset"))
+            handleCharset(directive);
+        else if (name.equals("algorithm"))
+            handleAlgorithm(directive);
+        else if (name.equals("cipher"))
+            handleCipher(directive);
+        else if (name.equals("stale"))
+            handleStale(directive);
+    }
+
+    /* post semantic check */
+    if (-1 == m_maxBuf)
+        m_maxBuf = 65536;
+
+    if (m_qop == 0)
+        m_qop = QOP_AUTH;
+    else if ( (m_qop & QOP_AUTH) != QOP_AUTH )
+        throw new SaslException("Only qop-auth is supported by client");
+    else if ( ((m_qop & QOP_AUTH_CONF) == QOP_AUTH_CONF) &&
+              (0 == (m_cipherOptions & CIPHER_RECOGNIZED_MASK)) )
+        throw new SaslException("Invalid cipher options");
+    else if (null == m_nonce)
+        throw new SaslException("Missing nonce directive");
+    else if (m_staleFlag)
+        throw new SaslException("Unexpected stale flag");
+    else if ( null == m_algorithm )
+        throw new SaslException("Missing algorithm directive");
+    }
+
+    /**
+     * This function implements the semenatics of the nonce directive.
+     *
+     * @param      pd   ParsedDirective
+     *
+     * @exception  SaslException   If an error occurs due to too many nonce
+     *                             values
+     */
+    void handleNonce(
+        ParsedDirective  pd) throws SaslException
+    {
+        if (null != m_nonce)
+            throw new SaslException("Too many nonce values.");
+
+        m_nonce = pd.getValue();
+    }
+
+    /**
+     * This function implements the semenatics of the realm directive.
+     *
+     * @param      pd   ParsedDirective
+     */
+    void handleRealm(
+        ParsedDirective  pd)
+    {
+        m_realms.add(pd.getValue());
+    }
+
+    /**
+     * This function implements the semenatics of the qop (quality of protection)
+     * directive. The value of the qop directive is as defined below:
+     *      qop-options =     "qop" "=" <"> qop-list <">
+     *      qop-list    =     1#qop-value
+     *      qop-value    =     "auth" | "auth-int"  | "auth-conf" | token
+     *
+     * @param      pd   ParsedDirective
+     *
+     * @exception  SaslException   If an error occurs due to too many qop
+     *                             directives
+     */
+    void handleQop(
+        ParsedDirective  pd) throws SaslException
+    {
+        String       token;
+        TokenParser  parser;
+
+        if (m_qop != 0)
+            throw new SaslException("Too many qop directives.");
+
+        parser = new TokenParser(pd.getValue());
+        for (token = parser.parseToken();
+             token != null;
+             token = parser.parseToken())
+        {
+            if (token.equals("auth"))
+                  m_qop |= QOP_AUTH;
+              else if (token.equals("auth-int"))
+                  m_qop |= QOP_AUTH_INT;
+            else if (token.equals("auth-conf"))
+                m_qop |= QOP_AUTH_CONF;
+            else
+                m_qop |= QOP_UNRECOGNIZED;
+        }
+    }
+
+    /**
+     * This function implements the semenatics of the Maxbuf directive.
+     * the value is defined as: 1*DIGIT
+     *
+     * @param      pd   ParsedDirective
+     *
+     * @exception  SaslException If an error occur    
+     */
+    void handleMaxbuf(
+        ParsedDirective  pd) throws SaslException
+    {
+        if (-1 != m_maxBuf) /*it's initialized to -1 */
+            throw new SaslException("Too many maxBuf directives.");
+
+        m_maxBuf = Integer.parseInt(pd.getValue());
+
+        if (0 == m_maxBuf)
+            throw new SaslException("Max buf value must be greater than zero.");
+    }
+
+    /**
+     * This function implements the semenatics of the charset directive.
+     * the value is defined as: 1*DIGIT
+     *
+     * @param      pd   ParsedDirective
+     *
+     * @exception  SaslException If an error occurs dur to too many charset
+     *                           directives or Invalid character encoding
+     *                           directive
+     */
+    void handleCharset(
+        ParsedDirective  pd) throws SaslException
+    {
+        if (null != m_characterSet)
+            throw new SaslException("Too many charset directives.");
+
+        m_characterSet = pd.getValue();
+
+        if (!m_characterSet.equals("utf-8"))
+            throw new SaslException("Invalid character encoding directive");
+    }
+
+    /**
+     * This function implements the semenatics of the charset directive.
+     * the value is defined as: 1*DIGIT
+     *
+     * @param      pd   ParsedDirective
+     *
+     * @exception  SaslException If an error occurs due to too many algorith
+     *                           directive or Invalid algorithm directive
+     *                           value
+     */
+    void handleAlgorithm(
+        ParsedDirective  pd) throws SaslException
+    {
+        if (null != m_algorithm)
+            throw new SaslException("Too many algorithm directives.");
+
+          m_algorithm = pd.getValue();
+
+        if (!"md5-sess".equals(m_algorithm))
+            throw new SaslException("Invalid algorithm directive value: " +
+                                    m_algorithm);
+    }
+
+    /**
+     * This function implements the semenatics of the cipher-opts directive
+     * directive. The value of the qop directive is as defined below:
+     *      qop-options =     "qop" "=" <"> qop-list <">
+     *      qop-list    =     1#qop-value
+     *      qop-value    =     "auth" | "auth-int"  | "auth-conf" | token
+     *
+     * @param      pd   ParsedDirective
+     *
+     * @exception  SaslException If an error occurs due to Too many cipher
+     *                           directives 
+     */
+    void handleCipher(
+        ParsedDirective  pd) throws SaslException
+    {
+        String  token;
+        TokenParser parser;
+
+        if (0 != m_cipherOptions)
+            throw new SaslException("Too many cipher directives.");
+
+        parser = new TokenParser(pd.getValue());
+        token = parser.parseToken();
+        for (token = parser.parseToken();
+             token != null;
+             token = parser.parseToken())
+        {
+              if ("3des".equals(token))
+                  m_cipherOptions |= CIPHER_3DES;
+              else if ("des".equals(token))
+                  m_cipherOptions |= CIPHER_DES;
+            else if ("rc4-40".equals(token))
+                m_cipherOptions |= CIPHER_RC4_40;
+            else if ("rc4".equals(token))
+                m_cipherOptions |= CIPHER_RC4;
+            else if ("rc4-56".equals(token))
+                m_cipherOptions |= CIPHER_RC4_56;
+            else
+                m_cipherOptions |= CIPHER_UNRECOGNIZED;
+        }
+
+        if (m_cipherOptions == 0)
+            m_cipherOptions = CIPHER_UNRECOGNIZED;
+    }
+
+    /**
+     * This function implements the semenatics of the stale directive.
+     *
+     * @param      pd   ParsedDirective
+     *
+     * @exception  SaslException If an error occurs due to Too many stale
+     *                           directives or Invalid stale directive value
+     */
+    void handleStale(
+        ParsedDirective  pd) throws SaslException
+    {
+        if (false != m_staleFlag)
+            throw new SaslException("Too many stale directives.");
+
+        if ("true".equals(pd.getValue()))
+            m_staleFlag = true;
+        else
+            throw new SaslException("Invalid stale directive value: " +
+                                    pd.getValue());
+    }
+
+    /**
+     * Return the list of the All the Realms
+     *
+     * @return  List of all the realms 
+     */
+    public ArrayList getRealms()
+    {
+        return m_realms;
+    }
+
+    /**
+     * @return Returns the Nonce
+     */
+    public String getNonce()
+    {
+        return m_nonce;
+    }
+
+    /**
+     * Return the quality-of-protection
+     * 
+     * @return The quality-of-protection
+     */
+    public int getQop()
+    {
+        return m_qop;
+    }
+
+    /**
+     * @return The state of the Staleflag
+     */
+    public boolean getStaleFlag()
+    {
+        return m_staleFlag;
+    }
+
+    /**
+     * @return The Maximum Buffer value
+     */
+    public int getMaxBuf()
+    {
+        return m_maxBuf;
+    }
+
+    /**
+     * @return character set values as string
+     */
+    public String getCharacterSet()
+    {
+        return m_characterSet;
+    }
+
+    /**
+     * @return The String value of the algorithm
+     */
+    public String getAlgorithm()
+    {
+        return m_algorithm;
+    }
+
+    /**
+     * @return The cipher options
+     */
+    public int getCipherOptions()
+    {
+        return m_cipherOptions;
+    }
+}
+
diff --git a/src/com/novell/sasl/client/DigestMD5SaslClient.java b/src/com/novell/sasl/client/DigestMD5SaslClient.java
new file mode 100644
index 0000000..141c96b
--- /dev/null
+++ b/src/com/novell/sasl/client/DigestMD5SaslClient.java
@@ -0,0 +1,820 @@
+/* **************************************************************************
+ * $OpenLDAP: /com/novell/sasl/client/DigestMD5SaslClient.java,v 1.4 2005/01/17 15:00:54 sunilk Exp $
+ *
+ * Copyright (C) 2003 Novell, Inc. All Rights Reserved.
+ *
+ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
+ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
+ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
+ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
+ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
+ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
+ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
+ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
+ ******************************************************************************/
+package com.novell.sasl.client;
+
+import org.apache.harmony.javax.security.sasl.*;
+import org.apache.harmony.javax.security.auth.callback.*;
+import java.security.SecureRandom;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.io.UnsupportedEncodingException;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Implements the Client portion of DigestMD5 Sasl mechanism.
+ */
+public class DigestMD5SaslClient implements SaslClient
+{
+    private String           m_authorizationId = "";
+    private String           m_protocol = "";
+    private String           m_serverName = "";
+    private Map              m_props;
+    private CallbackHandler  m_cbh;
+    private int              m_state;
+    private String           m_qopValue = "";
+    private char[]              m_HA1 = null;
+    private String           m_digestURI;
+    private DigestChallenge  m_dc;
+    private String           m_clientNonce = "";
+    private String           m_realm = "";
+    private String           m_name = "";
+
+    private static final int   STATE_INITIAL = 0;
+    private static final int   STATE_DIGEST_RESPONSE_SENT = 1;
+    private static final int   STATE_VALID_SERVER_RESPONSE = 2;
+    private static final int   STATE_INVALID_SERVER_RESPONSE = 3;
+    private static final int   STATE_DISPOSED = 4;
+
+    private static final int   NONCE_BYTE_COUNT = 32;
+    private static final int   NONCE_HEX_COUNT = 2*NONCE_BYTE_COUNT;
+
+    private static final String DIGEST_METHOD = "AUTHENTICATE";
+
+    /**
+     * Creates an DigestMD5SaslClient object using the parameters supplied.
+     * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are
+     * contained in props
+     *
+     * @param authorizationId  The possibly null protocol-dependent
+     *                     identification to be used for authorization. If
+     *                     null or empty, the server derives an authorization
+     *                     ID from the client's authentication credentials.
+     *                     When the SASL authentication completes
+     *                     successfully, the specified entity is granted
+     *                     access.
+     *
+     * @param protocol     The non-null string name of the protocol for which
+     *                     the authentication is being performed (e.g. "ldap")
+     *
+     * @param serverName   The non-null fully qualified host name of the server
+     *                     to authenticate to
+     *
+     * @param props        The possibly null set of properties used to select
+     *                     the SASL mechanism and to configure the
+     *                     authentication exchange of the selected mechanism.
+     *                     See the Sasl class for a list of standard properties.
+     *                     Other, possibly mechanism-specific, properties can
+     *                     be included. Properties not relevant to the selected
+     *                     mechanism are ignored.
+     *
+     * @param cbh          The possibly null callback handler to used by the
+     *                     SASL mechanisms to get further information from the
+     *                     application/library to complete the authentication.
+     *                     For example, a SASL mechanism might require the
+     *                     authentication ID, password and realm from the
+     *                     caller. The authentication ID is requested by using
+     *                     a NameCallback. The password is requested by using
+     *                     a PasswordCallback. The realm is requested by using
+     *                     a RealmChoiceCallback if there is a list of realms
+     *                     to choose from, and by using a RealmCallback if the
+     *                     realm must be entered.
+     *
+     * @return            A possibly null SaslClient created using the
+     *                     parameters supplied. If null, this factory cannot
+     *                     produce a SaslClient using the parameters supplied.
+     *
+     * @exception SaslException  If a SaslClient instance cannot be created
+     *                     because of an error
+     */
+    public static SaslClient getClient(
+        String          authorizationId,
+        String          protocol,
+        String          serverName,
+        Map             props,
+        CallbackHandler cbh)
+    {
+        String desiredQOP = (String)props.get(Sasl.QOP);
+        String desiredStrength = (String)props.get(Sasl.STRENGTH);
+        String serverAuth = (String)props.get(Sasl.SERVER_AUTH);
+
+        //only support qop equal to auth
+        if ((desiredQOP != null) && !"auth".equals(desiredQOP))
+            return null;
+
+        //doesn't support server authentication
+        if ((serverAuth != null) && !"false".equals(serverAuth))
+            return null;
+
+        //need a callback handler to get the password
+        if (cbh == null)
+            return null;
+
+        return new DigestMD5SaslClient(authorizationId, protocol,
+                                       serverName, props, cbh);
+    }
+
+    /**
+     * Creates an DigestMD5SaslClient object using the parameters supplied.
+     * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are
+     * contained in props
+     *
+     * @param authorizationId  The possibly null protocol-dependent
+     *                     identification to be used for authorization. If
+     *                     null or empty, the server derives an authorization
+     *                     ID from the client's authentication credentials.
+     *                     When the SASL authentication completes
+     *                     successfully, the specified entity is granted
+     *                     access.
+     *
+     * @param protocol     The non-null string name of the protocol for which
+     *                     the authentication is being performed (e.g. "ldap")
+     *
+     * @param serverName   The non-null fully qualified host name of the server
+     *                     to authenticate to
+     *
+     * @param props        The possibly null set of properties used to select
+     *                     the SASL mechanism and to configure the
+     *                     authentication exchange of the selected mechanism.
+     *                     See the Sasl class for a list of standard properties.
+     *                     Other, possibly mechanism-specific, properties can
+     *                     be included. Properties not relevant to the selected
+     *                     mechanism are ignored.
+     *
+     * @param cbh          The possibly null callback handler to used by the
+     *                     SASL mechanisms to get further information from the
+     *                     application/library to complete the authentication.
+     *                     For example, a SASL mechanism might require the
+     *                     authentication ID, password and realm from the
+     *                     caller. The authentication ID is requested by using
+     *                     a NameCallback. The password is requested by using
+     *                     a PasswordCallback. The realm is requested by using
+     *                     a RealmChoiceCallback if there is a list of realms
+     *                     to choose from, and by using a RealmCallback if the
+     *                     realm must be entered.
+     *
+     */
+    private  DigestMD5SaslClient(
+        String          authorizationId,
+        String          protocol,
+        String          serverName,
+        Map             props,
+        CallbackHandler cbh)
+    {
+        m_authorizationId = authorizationId;
+        m_protocol = protocol;
+        m_serverName = serverName;
+        m_props = props;
+        m_cbh = cbh;
+
+        m_state = STATE_INITIAL;
+    }
+
+    /**
+     * Determines if this mechanism has an optional initial response. If true,
+     * caller should call evaluateChallenge() with an empty array to get the
+     * initial response.
+     *
+     * @return  true if this mechanism has an initial response
+     */
+    public boolean hasInitialResponse()
+    {
+        return false;
+    }
+
+    /**
+     * Determines if the authentication exchange has completed. This method
+     * may be called at any time, but typically, it will not be called until
+     * the caller has received indication from the server (in a protocol-
+     * specific manner) that the exchange has completed.
+     *
+     * @return  true if the authentication exchange has completed;
+     *           false otherwise.
+     */
+    public boolean isComplete()
+    {
+        if ((m_state == STATE_VALID_SERVER_RESPONSE) ||
+            (m_state == STATE_INVALID_SERVER_RESPONSE) ||
+            (m_state == STATE_DISPOSED))
+            return true;
+        else
+            return false;
+    }
+
+    /**
+     * Unwraps a byte array received from the server. This method can be called
+     * only after the authentication exchange has completed (i.e., when
+     * isComplete() returns true) and only if the authentication exchange has
+     * negotiated integrity and/or privacy as the quality of protection;
+     * otherwise, an IllegalStateException is thrown.
+     *
+     * incoming is the contents of the SASL buffer as defined in RFC 2222
+     * without the leading four octet field that represents the length.
+     * offset and len specify the portion of incoming to use.
+     *
+     * @param incoming   A non-null byte array containing the encoded bytes
+     *                   from the server
+     * @param offset     The starting position at incoming of the bytes to use
+     *
+     * @param len        The number of bytes from incoming to use
+     *
+     * @return           A non-null byte array containing the decoded bytes
+     *
+     */
+    public byte[] unwrap(
+        byte[] incoming,
+        int    offset,
+        int    len)
+            throws SaslException
+    {
+        throw new IllegalStateException(
+         "unwrap: QOP has neither integrity nor privacy>");
+    }
+
+    /**
+     * Wraps a byte array to be sent to the server. This method can be called
+     * only after the authentication exchange has completed (i.e., when
+     * isComplete() returns true) and only if the authentication exchange has
+     * negotiated integrity and/or privacy as the quality of protection;
+     * otherwise, an IllegalStateException is thrown.
+     *
+     * The result of this method will make up the contents of the SASL buffer as
+     * defined in RFC 2222 without the leading four octet field that represents
+     * the length. offset and len specify the portion of outgoing to use.
+     *
+     * @param outgoing   A non-null byte array containing the bytes to encode
+     * @param offset     The starting position at outgoing of the bytes to use
+     * @param len        The number of bytes from outgoing to use
+     *
+     * @return A non-null byte array containing the encoded bytes
+     *
+     * @exception SaslException  if incoming cannot be successfully unwrapped.
+     *
+     * @exception IllegalStateException   if the authentication exchange has
+     *                   not completed, or if the negotiated quality of
+     *                   protection has neither integrity nor privacy.
+     */
+    public byte[] wrap(
+        byte[]  outgoing,
+        int     offset,
+        int     len)
+            throws SaslException
+    {
+        throw new IllegalStateException(
+         "wrap: QOP has neither integrity nor privacy>");
+    }
+
+    /**
+     * Retrieves the negotiated property. This method can be called only after
+     * the authentication exchange has completed (i.e., when isComplete()
+     * returns true); otherwise, an IllegalStateException is thrown.
+     *
+     * @param propName   The non-null property name
+     *
+     * @return  The value of the negotiated property. If null, the property was
+     *          not negotiated or is not applicable to this mechanism.
+     *
+     * @exception IllegalStateException   if this authentication exchange has
+     *                                    not completed
+     */
+    public Object getNegotiatedProperty(
+        String propName)
+    {
+        if (m_state != STATE_VALID_SERVER_RESPONSE)
+            throw new IllegalStateException(
+             "getNegotiatedProperty: authentication exchange not complete.");
+
+        if (Sasl.QOP.equals(propName))
+            return "auth";
+        else
+            return null;
+    }
+
+    /**
+     * Disposes of any system resources or security-sensitive information the
+     * SaslClient might be using. Invoking this method invalidates the
+     * SaslClient instance. This method is idempotent.
+     *
+     * @exception SaslException  if a problem was encountered while disposing
+     *                           of the resources
+     */
+    public void dispose()
+            throws SaslException
+    {
+        if (m_state != STATE_DISPOSED)
+        {
+            m_state = STATE_DISPOSED;
+        }
+    }
+
+    /**
+     * Evaluates the challenge data and generates a response. If a challenge
+     * is received from the server during the authentication process, this
+     * method is called to prepare an appropriate next response to submit to
+     * the server.
+     *
+     * @param challenge  The non-null challenge sent from the server. The
+     *                   challenge array may have zero length.
+     *
+     * @return    The possibly null reponse to send to the server. It is null
+     *            if the challenge accompanied a "SUCCESS" status and the
+     *            challenge only contains data for the client to update its
+     *            state and no response needs to be sent to the server.
+     *            The response is a zero-length byte array if the client is to
+     *            send a response with no data.
+     *
+     * @exception SaslException   If an error occurred while processing the
+     *                            challenge or generating a response.
+     */
+    public byte[] evaluateChallenge(
+        byte[] challenge)
+            throws SaslException
+    {
+        byte[] response = null;
+
+        //printState();
+        switch (m_state)
+        {
+        case STATE_INITIAL:
+            if (challenge.length == 0)
+                throw new SaslException("response = byte[0]");
+            else
+                try
+                {
+                    response = createDigestResponse(challenge).
+                                                           getBytes("UTF-8");
+                    m_state = STATE_DIGEST_RESPONSE_SENT;
+                }
+                catch (java.io.UnsupportedEncodingException e)
+                {
+                    throw new SaslException(
+                     "UTF-8 encoding not suppported by platform", e);
+                }
+            break;
+        case STATE_DIGEST_RESPONSE_SENT:
+            if (checkServerResponseAuth(challenge))
+                m_state = STATE_VALID_SERVER_RESPONSE;
+            else
+            {
+                m_state = STATE_INVALID_SERVER_RESPONSE;
+                throw new SaslException("Could not validate response-auth " +
+                                        "value from server");
+            }
+            break;
+        case STATE_VALID_SERVER_RESPONSE:
+        case STATE_INVALID_SERVER_RESPONSE:
+            throw new SaslException("Authentication sequence is complete");
+        case STATE_DISPOSED:
+            throw new SaslException("Client has been disposed");
+        default:
+            throw new SaslException("Unknown client state.");
+        }
+
+        return response;
+    }
+
+    /**
+     * This function takes a 16 byte binary md5-hash value and creates a 32
+     * character (plus    a terminating null character) hex-digit 
+     * representation of binary data.
+     *
+     * @param hash  16 byte binary md5-hash value in bytes
+     * 
+     * @return   32 character (plus    a terminating null character) hex-digit
+     *           representation of binary data.
+     */
+    char[] convertToHex(
+        byte[] hash)
+    {
+        int          i;
+        byte         j;
+        byte         fifteen = 15;
+        char[]      hex = new char[32];
+
+        for (i = 0; i < 16; i++)
+        {
+            //convert value of top 4 bits to hex char
+            hex[i*2] = getHexChar((byte)((hash[i] & 0xf0) >> 4));
+            //convert value of bottom 4 bits to hex char
+            hex[(i*2)+1] = getHexChar((byte)(hash[i] & 0x0f));
+        }
+
+        return hex;
+    }
+
+    /**
+     * Calculates the HA1 portion of the response
+     *
+     * @param  algorithm   Algorith to use.
+     * @param  userName    User being authenticated
+     * @param  realm       realm information
+     * @param  password    password of teh user
+     * @param  nonce       nonce value
+     * @param  clientNonce Clients Nonce value
+     *
+     * @return  HA1 portion of the response in a character array
+     *
+     * @exception SaslException  If an error occurs
+     */
+    char[] DigestCalcHA1(
+        String   algorithm,
+        String   userName,
+        String   realm,
+        String   password,
+        String   nonce,
+        String   clientNonce) throws SaslException
+    {
+        byte[]        hash;
+
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+
+            md.update(userName.getBytes("UTF-8"));
+            md.update(":".getBytes("UTF-8"));
+            md.update(realm.getBytes("UTF-8"));
+            md.update(":".getBytes("UTF-8"));
+            md.update(password.getBytes("UTF-8"));
+            hash = md.digest();
+
+            if ("md5-sess".equals(algorithm))
+            {
+                md.update(hash);
+                md.update(":".getBytes("UTF-8"));
+                md.update(nonce.getBytes("UTF-8"));
+                md.update(":".getBytes("UTF-8"));
+                md.update(clientNonce.getBytes("UTF-8"));
+                hash = md.digest();
+            }
+        }
+        catch(NoSuchAlgorithmException e)
+        {
+            throw new SaslException("No provider found for MD5 hash", e);
+        }
+        catch(UnsupportedEncodingException e)
+        {
+            throw new SaslException(
+             "UTF-8 encoding not supported by platform.", e);
+        }
+
+        return convertToHex(hash);
+    }
+
+
+    /**
+     * This function calculates the response-value of the response directive of
+     * the digest-response as documented in RFC 2831
+     *
+     * @param  HA1           H(A1)
+     * @param  serverNonce   nonce from server
+     * @param  nonceCount    8 hex digits
+     * @param  clientNonce   client nonce 
+     * @param  qop           qop-value: "", "auth", "auth-int"
+     * @param  method        method from the request
+     * @param  digestUri     requested URL
+     * @param  clientResponseFlag request-digest or response-digest
+     *
+     * @return Response-value of the response directive of the digest-response
+     *
+     * @exception SaslException  If an error occurs
+     */
+    char[] DigestCalcResponse(
+        char[]      HA1,            /* H(A1) */
+        String      serverNonce,    /* nonce from server */
+        String      nonceCount,     /* 8 hex digits */
+        String      clientNonce,    /* client nonce */
+        String      qop,            /* qop-value: "", "auth", "auth-int" */
+        String      method,         /* method from the request */
+        String      digestUri,      /* requested URL */
+        boolean     clientResponseFlag) /* request-digest or response-digest */
+            throws SaslException
+    {
+        byte[]             HA2;
+        byte[]             respHash;
+        char[]             HA2Hex;
+
+        // calculate H(A2)
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            if (clientResponseFlag)
+                  md.update(method.getBytes("UTF-8"));
+            md.update(":".getBytes("UTF-8"));
+            md.update(digestUri.getBytes("UTF-8"));
+            if ("auth-int".equals(qop))
+            {
+                md.update(":".getBytes("UTF-8"));
+                md.update("00000000000000000000000000000000".getBytes("UTF-8"));
+            }
+            HA2 = md.digest();
+            HA2Hex = convertToHex(HA2);
+
+            // calculate response
+            md.update(new String(HA1).getBytes("UTF-8"));
+            md.update(":".getBytes("UTF-8"));
+            md.update(serverNonce.getBytes("UTF-8"));
+            md.update(":".getBytes("UTF-8"));
+            if (qop.length() > 0)
+            {
+                md.update(nonceCount.getBytes("UTF-8"));
+                md.update(":".getBytes("UTF-8"));
+                md.update(clientNonce.getBytes("UTF-8"));
+                md.update(":".getBytes("UTF-8"));
+                md.update(qop.getBytes("UTF-8"));
+                md.update(":".getBytes("UTF-8"));
+            }
+            md.update(new String(HA2Hex).getBytes("UTF-8"));
+            respHash = md.digest();
+        }
+        catch(NoSuchAlgorithmException e)
+        {
+            throw new SaslException("No provider found for MD5 hash", e);
+        }
+        catch(UnsupportedEncodingException e)
+        {
+            throw new SaslException(
+             "UTF-8 encoding not supported by platform.", e);
+        }
+
+        return convertToHex(respHash);
+    }
+
+
+    /**
+     * Creates the intial response to be sent to the server.
+     *
+     * @param challenge  Challenge in bytes recived form the Server
+     *
+     * @return Initial response to be sent to the server
+     */
+    private String createDigestResponse(
+        byte[] challenge)
+            throws SaslException
+    {
+        char[]            response;
+        StringBuffer    digestResponse = new StringBuffer(512);
+        int             realmSize;
+
+        m_dc = new DigestChallenge(challenge);
+
+        m_digestURI = m_protocol + "/" + m_serverName;
+
+        if ((m_dc.getQop() & DigestChallenge.QOP_AUTH)
+            == DigestChallenge.QOP_AUTH )
+            m_qopValue = "auth";
+        else
+            throw new SaslException("Client only supports qop of 'auth'");
+
+        //get call back information
+        Callback[] callbacks = new Callback[3];
+        ArrayList realms = m_dc.getRealms();
+        realmSize = realms.size();
+        if (realmSize == 0)
+        {
+            callbacks[0] = new RealmCallback("Realm");
+        }
+        else if (realmSize == 1)
+        {
+            callbacks[0] = new RealmCallback("Realm", (String)realms.get(0));
+        }
+        else
+        {
+            callbacks[0] =
+             new RealmChoiceCallback(
+                         "Realm",
+                         (String[])realms.toArray(new String[realmSize]),
+                          0,      //the default choice index
+                          false); //no multiple selections
+        }
+
+        callbacks[1] = new PasswordCallback("Password", false); 
+        //false = no echo
+
+        if (m_authorizationId == null || m_authorizationId.length() == 0)
+            callbacks[2] = new NameCallback("Name");
+        else
+            callbacks[2] = new NameCallback("Name", m_authorizationId);
+
+        try
+        {
+            m_cbh.handle(callbacks);
+        }
+        catch(UnsupportedCallbackException e)
+        {
+            throw new SaslException("Handler does not support" +
+                                          " necessary callbacks",e);
+        }
+        catch(IOException e)
+        {
+            throw new SaslException("IO exception in CallbackHandler.", e);
+        }
+
+        if (realmSize > 1)
+        {
+            int[] selections =
+             ((RealmChoiceCallback)callbacks[0]).getSelectedIndexes();
+
+            if (selections.length > 0)
+                m_realm =
+                ((RealmChoiceCallback)callbacks[0]).getChoices()[selections[0]];
+            else
+                m_realm = ((RealmChoiceCallback)callbacks[0]).getChoices()[0];
+        }
+        else
+            m_realm = ((RealmCallback)callbacks[0]).getText();
+
+        m_clientNonce = getClientNonce();
+
+        m_name = ((NameCallback)callbacks[2]).getName();
+        if (m_name == null)
+            m_name = ((NameCallback)callbacks[2]).getDefaultName();
+        if (m_name == null)
+            throw new SaslException("No user name was specified.");
+
+        m_HA1 = DigestCalcHA1(
+                      m_dc.getAlgorithm(),
+                      m_name,
+                      m_realm,
+                      new String(((PasswordCallback)callbacks[1]).getPassword()),
+                      m_dc.getNonce(),
+                      m_clientNonce);
+
+        response = DigestCalcResponse(m_HA1,
+                                      m_dc.getNonce(),
+                                      "00000001",
+                                      m_clientNonce,
+                                      m_qopValue,
+                                      "AUTHENTICATE",
+                                      m_digestURI,
+                                      true);
+
+        digestResponse.append("username=\"");
+        digestResponse.append(m_authorizationId);
+        if (0 != m_realm.length())
+        {
+            digestResponse.append("\",realm=\"");
+            digestResponse.append(m_realm);
+        }
+        digestResponse.append("\",cnonce=\"");
+        digestResponse.append(m_clientNonce);
+        digestResponse.append("\",nc=");
+        digestResponse.append("00000001"); //nounce count
+        digestResponse.append(",qop=");
+        digestResponse.append(m_qopValue);
+        digestResponse.append(",digest-uri=\"");
+	digestResponse.append(m_digestURI);
+        digestResponse.append("\",response=");
+        digestResponse.append(response);
+        digestResponse.append(",charset=utf-8,nonce=\"");
+        digestResponse.append(m_dc.getNonce());
+        digestResponse.append("\"");
+
+        return digestResponse.toString();
+     }
+     
+     
+    /**
+     * This function validates the server response. This step performs a 
+     * modicum of mutual authentication by verifying that the server knows
+     * the user's password
+     *
+     * @param  serverResponse  Response recived form Server
+     *
+     * @return  true if the mutual authentication succeeds;
+     *          else return false
+     *
+     * @exception SaslException  If an error occurs
+     */
+    boolean checkServerResponseAuth(
+            byte[]  serverResponse) throws SaslException
+    {
+        char[]           response;
+        ResponseAuth  responseAuth = null;
+        String        responseStr;
+
+        responseAuth = new ResponseAuth(serverResponse);
+
+        response = DigestCalcResponse(m_HA1,
+                                  m_dc.getNonce(),
+                                  "00000001",
+                                  m_clientNonce,
+                                  m_qopValue,
+                                  DIGEST_METHOD,
+                                  m_digestURI,
+                                  false);
+
+        responseStr = new String(response);
+
+        return responseStr.equals(responseAuth.getResponseValue());
+    }
+
+
+    /**
+     * This function returns hex character representing the value of the input
+     * 
+     * @param value Input value in byte
+     *
+     * @return Hex value of the Input byte value
+     */
+    private static char getHexChar(
+        byte    value)
+    {
+        switch (value)
+        {
+        case 0:
+            return '0';
+        case 1:
+            return '1';
+        case 2:
+            return '2';
+        case 3:
+            return '3';
+        case 4:
+            return '4';
+        case 5:
+            return '5';
+        case 6:
+            return '6';
+        case 7:
+            return '7';
+        case 8:
+            return '8';
+        case 9:
+            return '9';
+        case 10:
+            return 'a';
+        case 11:
+            return 'b';
+        case 12:
+            return 'c';
+        case 13:
+            return 'd';
+        case 14:
+            return 'e';
+        case 15:
+            return 'f';
+        default:
+            return 'Z';
+        }
+    }
+
+    /**
+     * Calculates the Nonce value of the Client
+     * 
+     * @return   Nonce value of the client
+     *
+     * @exception   SaslException If an error Occurs
+     */
+    String getClientNonce() throws SaslException
+    {
+        byte[]          nonceBytes = new byte[NONCE_BYTE_COUNT];
+        SecureRandom    prng;
+        byte            nonceByte;
+        char[]          hexNonce = new char[NONCE_HEX_COUNT];
+
+        try
+        {
+            prng = SecureRandom.getInstance("SHA1PRNG");
+            prng.nextBytes(nonceBytes);
+            for(int i=0; i<NONCE_BYTE_COUNT; i++)
+            {
+                //low nibble
+                hexNonce[i*2] = getHexChar((byte)(nonceBytes[i] & 0x0f));
+                //high nibble
+                hexNonce[(i*2)+1] = getHexChar((byte)((nonceBytes[i] & 0xf0)
+                                                                      >> 4));
+            }
+            return new String(hexNonce);
+        }
+        catch(NoSuchAlgorithmException e)
+        {
+            throw new SaslException("No random number generator available", e);
+        }
+    }
+
+    /**
+     * Returns the IANA-registered mechanism name of this SASL client.
+     *  (e.g. "CRAM-MD5", "GSSAPI")
+     *
+     * @return  "DIGEST-MD5"the IANA-registered mechanism name of this SASL
+     *          client.
+     */
+    public String getMechanismName()
+    {
+        return "DIGEST-MD5";
+    }
+
+} //end class DigestMD5SaslClient
+
diff --git a/src/com/novell/sasl/client/DirectiveList.java b/src/com/novell/sasl/client/DirectiveList.java
new file mode 100644
index 0000000..fc26a6b
--- /dev/null
+++ b/src/com/novell/sasl/client/DirectiveList.java
@@ -0,0 +1,363 @@
+/* **************************************************************************
+ * $OpenLDAP: /com/novell/sasl/client/DirectiveList.java,v 1.4 2005/01/17 15:00:54 sunilk Exp $
+ *
+ * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
+ *
+ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
+ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
+ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
+ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
+ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
+ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
+ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
+ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
+ ******************************************************************************/
+package com.novell.sasl.client;
+
+import java.util.*;
+import org.apache.harmony.javax.security.sasl.*;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Implements the DirectiveList class whihc will be used by the 
+ * DigestMD5SaslClient class
+ */
+class DirectiveList extends Object
+{
+    private static final int STATE_LOOKING_FOR_FIRST_DIRECTIVE  = 1;
+    private static final int STATE_LOOKING_FOR_DIRECTIVE        = 2;
+    private static final int STATE_SCANNING_NAME                = 3;
+    private static final int STATE_LOOKING_FOR_EQUALS            = 4;
+    private static final int STATE_LOOKING_FOR_VALUE            = 5;
+    private static final int STATE_LOOKING_FOR_COMMA            = 6;
+    private static final int STATE_SCANNING_QUOTED_STRING_VALUE    = 7;
+    private static final int STATE_SCANNING_TOKEN_VALUE            = 8;
+    private static final int STATE_NO_UTF8_SUPPORT              = 9;
+
+    private int        m_curPos;
+    private int        m_errorPos;
+    private String     m_directives;
+    private int        m_state;
+    private ArrayList  m_directiveList;
+    private String     m_curName;
+    private int        m_scanStart;
+
+    /**
+     *  Constructs a new DirectiveList.
+     */
+     DirectiveList(
+        byte[] directives)
+    {
+        m_curPos = 0;
+        m_state = STATE_LOOKING_FOR_FIRST_DIRECTIVE;
+        m_directiveList = new ArrayList(10);
+        m_scanStart = 0;
+        m_errorPos = -1;
+        try
+        {
+            m_directives = new String(directives, "UTF-8");
+        }
+        catch(UnsupportedEncodingException e)
+        {
+            m_state = STATE_NO_UTF8_SUPPORT;
+        }
+    }
+
+    /**
+     * This function takes a US-ASCII character string containing a list of comma
+     * separated directives, and parses the string into the individual directives
+     * and their values. A directive consists of a token specifying the directive
+     * name followed by an equal sign (=) and the directive value. The value is
+     * either a token or a quoted string
+     *
+     * @exception SaslException  If an error Occurs
+     */
+    void parseDirectives() throws SaslException
+    {
+        char        prevChar;
+        char        currChar;
+        int            rc = 0;
+        boolean        haveQuotedPair = false;
+        String      currentName = "<no name>";
+
+        if (m_state == STATE_NO_UTF8_SUPPORT)
+            throw new SaslException("No UTF-8 support on platform");
+
+        prevChar = 0;
+
+        while (m_curPos < m_directives.length())
+        {
+            currChar = m_directives.charAt(m_curPos);
+            switch (m_state)
+            {
+            case STATE_LOOKING_FOR_FIRST_DIRECTIVE:
+            case STATE_LOOKING_FOR_DIRECTIVE:
+                if (isWhiteSpace(currChar))
+                {
+                    break;
+                }
+                else if (isValidTokenChar(currChar))
+                {
+                    m_scanStart = m_curPos;
+                    m_state = STATE_SCANNING_NAME;
+                }
+                else
+                {
+                     m_errorPos = m_curPos;
+                    throw new SaslException("Parse error: Invalid name character");
+                }
+                break;
+
+            case STATE_SCANNING_NAME:
+                if (isValidTokenChar(currChar))
+                {
+                    break;
+                }
+                else if (isWhiteSpace(currChar))
+                {
+                    currentName = m_directives.substring(m_scanStart, m_curPos);
+                    m_state = STATE_LOOKING_FOR_EQUALS;
+                }
+                else if ('=' == currChar)
+                {
+                    currentName = m_directives.substring(m_scanStart, m_curPos);
+                    m_state = STATE_LOOKING_FOR_VALUE;
+                }
+                else
+                {
+                     m_errorPos = m_curPos;
+                    throw new SaslException("Parse error: Invalid name character");
+                }
+                break;
+
+            case STATE_LOOKING_FOR_EQUALS:
+                if (isWhiteSpace(currChar))
+                {
+                    break;
+                }
+                else if ('=' == currChar)
+                {
+                    m_state = STATE_LOOKING_FOR_VALUE;
+                }
+                else
+                {
+                    m_errorPos = m_curPos;
+                    throw new SaslException("Parse error: Expected equals sign '='.");
+                }
+                break;
+
+            case STATE_LOOKING_FOR_VALUE:
+                if (isWhiteSpace(currChar))
+                {
+                    break;
+                }
+                else if ('"' == currChar)
+                {
+                    m_scanStart = m_curPos+1; /* don't include the quote */
+                    m_state = STATE_SCANNING_QUOTED_STRING_VALUE;
+                }
+                else if (isValidTokenChar(currChar))
+                {
+                    m_scanStart = m_curPos;
+                    m_state = STATE_SCANNING_TOKEN_VALUE;
+                }
+                else
+                {
+                    m_errorPos = m_curPos;
+                    throw new SaslException("Parse error: Unexpected character");
+                }
+                break;
+
+            case STATE_SCANNING_TOKEN_VALUE:
+                if (isValidTokenChar(currChar))
+                {
+                    break;
+                }
+                else if (isWhiteSpace(currChar))
+                {
+                    addDirective(currentName, false);
+                    m_state = STATE_LOOKING_FOR_COMMA;
+                }
+                else if (',' == currChar)
+                {
+                    addDirective(currentName, false);
+                    m_state = STATE_LOOKING_FOR_DIRECTIVE;
+                }
+                else
+                {
+                     m_errorPos = m_curPos;
+                    throw new SaslException("Parse error: Invalid value character");
+                }
+                break;
+
+            case STATE_SCANNING_QUOTED_STRING_VALUE:
+                if ('\\' == currChar)
+                    haveQuotedPair = true;
+                if ( ('"' == currChar) &&
+                     ('\\' != prevChar) )
+                {
+                    addDirective(currentName, haveQuotedPair);
+                    haveQuotedPair = false;
+                    m_state = STATE_LOOKING_FOR_COMMA;
+                }
+                break;
+
+            case STATE_LOOKING_FOR_COMMA:
+                if (isWhiteSpace(currChar))
+                    break;
+                else if (currChar == ',')
+                    m_state = STATE_LOOKING_FOR_DIRECTIVE;
+                else
+                {
+                    m_errorPos = m_curPos;
+                    throw new SaslException("Parse error: Expected a comma.");
+                }
+                break;
+            }
+            if (0 != rc)
+                break;
+            prevChar = currChar;
+            m_curPos++;
+        } /* end while loop */
+
+
+        if (rc == 0)
+        {
+            /* check the ending state */
+            switch (m_state)
+            {
+            case STATE_SCANNING_TOKEN_VALUE:
+                addDirective(currentName, false);
+                break;
+
+            case STATE_LOOKING_FOR_FIRST_DIRECTIVE:
+            case STATE_LOOKING_FOR_COMMA:
+                break;
+
+            case STATE_LOOKING_FOR_DIRECTIVE:
+                    throw new SaslException("Parse error: Trailing comma.");
+
+            case STATE_SCANNING_NAME:
+            case STATE_LOOKING_FOR_EQUALS:
+            case STATE_LOOKING_FOR_VALUE:
+                    throw new SaslException("Parse error: Missing value.");
+
+            case STATE_SCANNING_QUOTED_STRING_VALUE:
+                    throw new SaslException("Parse error: Missing closing quote.");
+            }
+        }
+
+    }
+
+    /**
+     * This function returns TRUE if the character is a valid token character.
+     *
+     *     token          = 1*<any CHAR except CTLs or separators>
+     *
+     *      separators     = "(" | ")" | "<" | ">" | "@"
+     *                     | "," | ";" | ":" | "\" | <">
+     *                     | "/" | "[" | "]" | "?" | "="
+     *                     | "{" | "}" | SP | HT
+     *
+     *      CTL            = <any US-ASCII control character
+     *                       (octets 0 - 31) and DEL (127)>
+     *
+     *      CHAR           = <any US-ASCII character (octets 0 - 127)>
+     *
+     * @param c  character to be tested
+     *
+     * @return Returns TRUE if the character is a valid token character.
+     */
+    boolean isValidTokenChar(
+        char c)
+    {
+        if ( ( (c >= '\u0000') && (c <='\u0020') ) ||
+             ( (c >= '\u003a') && (c <= '\u0040') ) ||
+             ( (c >= '\u005b') && (c <= '\u005d') ) ||
+             ('\u002c' == c) ||
+             ('\u0025' == c) ||
+             ('\u0028' == c) ||
+             ('\u0029' == c) ||
+             ('\u007b' == c) ||
+             ('\u007d' == c) ||
+             ('\u007f' == c) )
+            return false;
+
+        return true;
+    }
+
+    /**
+     * This function returns TRUE if the character is linear white space (LWS).
+     *         LWS = [CRLF] 1*( SP | HT )
+     * @param c  Input charcter to be tested
+     *
+     * @return Returns TRUE if the character is linear white space (LWS)
+     */
+    boolean isWhiteSpace(
+        char c)
+    {
+        if ( ('\t' == c) ||  // HORIZONTAL TABULATION.
+             ('\n' == c) ||  // LINE FEED.
+             ('\r' == c) ||  // CARRIAGE RETURN.
+             ('\u0020' == c) )
+            return true;
+
+        return false;
+    }
+
+    /**
+     * This function creates a directive record and adds it to the list, the
+     * value will be added later after it is parsed.
+     *
+     * @param name  Name
+     * @param haveQuotedPair true if quoted pair is there else false
+     */
+    void addDirective(
+        String    name,
+        boolean   haveQuotedPair)
+    {
+        String value;
+        int    inputIndex;
+        int    valueIndex;
+        char   valueChar;
+        int    type;
+
+        if (!haveQuotedPair)
+        {
+            value = m_directives.substring(m_scanStart, m_curPos);
+        }
+        else
+        { //copy one character at a time skipping backslash excapes.
+            StringBuffer valueBuf = new StringBuffer(m_curPos - m_scanStart);
+            valueIndex = 0;
+            inputIndex = m_scanStart;
+            while (inputIndex < m_curPos)
+            {
+                if ('\\' == (valueChar = m_directives.charAt(inputIndex)))
+                    inputIndex++;
+                valueBuf.setCharAt(valueIndex, m_directives.charAt(inputIndex));
+                valueIndex++;
+                inputIndex++;
+            }
+            value = new String(valueBuf);
+        }
+
+        if (m_state == STATE_SCANNING_QUOTED_STRING_VALUE)
+            type = ParsedDirective.QUOTED_STRING_VALUE;
+        else
+            type = ParsedDirective.TOKEN_VALUE;
+        m_directiveList.add(new ParsedDirective(name, value, type));
+    }
+
+
+    /**
+     * Returns the List iterator.
+     *
+     * @return     Returns the Iterator Object for the List.
+     */
+    Iterator getIterator()
+    {
+        return m_directiveList.iterator();
+    }
+}
+
diff --git a/src/com/novell/sasl/client/ParsedDirective.java b/src/com/novell/sasl/client/ParsedDirective.java
new file mode 100644
index 0000000..17bf70e
--- /dev/null
+++ b/src/com/novell/sasl/client/ParsedDirective.java
@@ -0,0 +1,56 @@
+/* **************************************************************************
+ * $OpenLDAP: /com/novell/sasl/client/ParsedDirective.java,v 1.1 2003/08/21 10:06:26 kkanil Exp $
+ *
+ * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
+ *
+ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
+ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
+ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
+ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
+ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
+ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
+ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
+ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
+ ******************************************************************************/
+package com.novell.sasl.client;
+
+/**
+ * Implements the ParsedDirective class which will be used in the
+ * DigestMD5SaslClient mechanism.
+ */
+class ParsedDirective
+{
+    public static final int  QUOTED_STRING_VALUE = 1;
+    public static final int  TOKEN_VALUE         = 2;
+
+    private int     m_valueType;
+    private String  m_name;
+    private String  m_value;
+
+    ParsedDirective(
+        String  name,
+        String  value,
+        int     type)
+    {
+        m_name = name;
+        m_value = value;
+        m_valueType = type;
+    }
+
+    String getValue()
+    {
+        return m_value;
+    }
+
+    String getName()
+    {
+        return m_name;
+    }
+
+    int getValueType()
+    {
+        return m_valueType;
+    }
+
+}
+
diff --git a/src/com/novell/sasl/client/ResponseAuth.java b/src/com/novell/sasl/client/ResponseAuth.java
new file mode 100644
index 0000000..0aef955
--- /dev/null
+++ b/src/com/novell/sasl/client/ResponseAuth.java
@@ -0,0 +1,83 @@
+/* **************************************************************************
+ * $OpenLDAP: /com/novell/sasl/client/ResponseAuth.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $
+ *
+ * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
+ *
+ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
+ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
+ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
+ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
+ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
+ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
+ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
+ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
+ ******************************************************************************/
+package com.novell.sasl.client;
+
+import java.util.*;
+import org.apache.harmony.javax.security.sasl.*;
+
+/**
+ * Implements the ResponseAuth class used by the DigestMD5SaslClient mechanism
+ */
+class ResponseAuth
+{
+
+    private String m_responseValue;
+
+    ResponseAuth(
+        byte[] responseAuth)
+            throws SaslException
+    {
+        m_responseValue = null;
+
+        DirectiveList dirList = new DirectiveList(responseAuth);
+        try
+        {
+            dirList.parseDirectives();
+            checkSemantics(dirList);
+        }
+        catch (SaslException e)
+        {
+        }
+    }
+
+    /**
+     * Checks the semantics of the directives in the directive list as parsed
+     * from the digest challenge byte array.
+     *
+     * @param dirList  the list of directives parsed from the digest challenge
+     *
+     * @exception SaslException   If a semantic error occurs
+     */
+    void checkSemantics(
+        DirectiveList dirList) throws SaslException
+    {
+        Iterator        directives = dirList.getIterator();
+        ParsedDirective directive;
+        String          name;
+
+        while (directives.hasNext())
+        {
+            directive = (ParsedDirective)directives.next();
+            name = directive.getName();
+            if (name.equals("rspauth"))
+                m_responseValue = directive.getValue();
+        }
+
+        /* post semantic check */
+        if (m_responseValue == null)
+            throw new SaslException("Missing response-auth directive.");
+    }
+
+    /**
+     * returns the ResponseValue
+     *
+     * @return the ResponseValue as a String.
+     */
+    public String getResponseValue()
+    {
+        return m_responseValue;
+    }
+}
+
diff --git a/src/com/novell/sasl/client/TokenParser.java b/src/com/novell/sasl/client/TokenParser.java
new file mode 100644
index 0000000..3d3491d
--- /dev/null
+++ b/src/com/novell/sasl/client/TokenParser.java
@@ -0,0 +1,208 @@
+/* **************************************************************************
+ * $OpenLDAP: /com/novell/sasl/client/TokenParser.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $
+ *
+ * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
+ *
+ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
+ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
+ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
+ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
+ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
+ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
+ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
+ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
+ ******************************************************************************/
+package com.novell.sasl.client;
+
+import org.apache.harmony.javax.security.sasl.*;
+/**
+ * The TokenParser class will parse individual tokens from a list of tokens that
+ * are a directive value for a DigestMD5 authentication.The tokens are separated
+ * commas.
+ */
+class TokenParser extends Object
+{
+    private static final int STATE_LOOKING_FOR_FIRST_TOKEN = 1;
+    private static final int STATE_LOOKING_FOR_TOKEN       = 2;
+    private static final int STATE_SCANNING_TOKEN          = 3;
+    private static final int STATE_LOOKING_FOR_COMMA       = 4;
+    private static final int STATE_PARSING_ERROR           = 5;
+    private static final int STATE_DONE                    = 6;
+
+    private int        m_curPos;
+    private int     m_scanStart;
+    private int     m_state;
+    private String  m_tokens;
+
+
+    TokenParser(
+        String tokens)
+    {
+        m_tokens = tokens;
+        m_curPos = 0;
+        m_scanStart = 0;
+        m_state =  STATE_LOOKING_FOR_FIRST_TOKEN;
+    }
+
+    /**
+     * This function parses the next token from the tokens string and returns
+     * it as a string. If there are no more tokens a null reference is returned.
+     *
+     * @return  the parsed token or a null reference if there are no more
+     * tokens
+     *
+     * @exception  SASLException if an error occurs while parsing
+     */
+    String parseToken() throws SaslException
+    {
+        char    currChar;
+        String  token = null;
+
+
+        if (m_state == STATE_DONE)
+            return null;
+
+        while (m_curPos < m_tokens.length() && (token == null))
+        {
+            currChar = m_tokens.charAt(m_curPos);
+            switch (m_state)
+            {
+            case STATE_LOOKING_FOR_FIRST_TOKEN:
+            case STATE_LOOKING_FOR_TOKEN:
+                if (isWhiteSpace(currChar))
+                {
+                    break;
+                }
+                else if (isValidTokenChar(currChar))
+                {
+                    m_scanStart = m_curPos;
+                    m_state = STATE_SCANNING_TOKEN;
+                }
+                else
+                {
+                    m_state = STATE_PARSING_ERROR;
+                    throw new SaslException("Invalid token character at position " + m_curPos);
+                }
+                break;
+
+            case STATE_SCANNING_TOKEN:
+                if (isValidTokenChar(currChar))
+                {
+                    break;
+                }
+                else if (isWhiteSpace(currChar))
+                {
+                    token = m_tokens.substring(m_scanStart, m_curPos);
+                    m_state = STATE_LOOKING_FOR_COMMA;
+                }
+                else if (',' == currChar)
+                {
+                    token = m_tokens.substring(m_scanStart, m_curPos);
+                    m_state = STATE_LOOKING_FOR_TOKEN;
+                }
+                else
+                {
+                    m_state = STATE_PARSING_ERROR;
+                    throw new SaslException("Invalid token character at position " + m_curPos);
+                }
+                break;
+
+
+            case STATE_LOOKING_FOR_COMMA:
+                if (isWhiteSpace(currChar))
+                    break;
+                else if (currChar == ',')
+                    m_state = STATE_LOOKING_FOR_TOKEN;
+                else
+                {
+                    m_state = STATE_PARSING_ERROR;
+                    throw new SaslException("Expected a comma, found '" +
+                                            currChar + "' at postion " +
+                                            m_curPos);
+                }
+                break;
+            }
+            m_curPos++;
+        } /* end while loop */
+
+        if (token == null)
+        {    /* check the ending state */
+            switch (m_state)
+            {
+            case STATE_SCANNING_TOKEN:
+                token = m_tokens.substring(m_scanStart);
+                m_state = STATE_DONE;
+                break;
+
+            case STATE_LOOKING_FOR_FIRST_TOKEN:
+            case STATE_LOOKING_FOR_COMMA:
+                break;
+
+            case STATE_LOOKING_FOR_TOKEN:
+                throw new SaslException("Trialing comma");
+            }
+        }
+
+        return token;
+    }
+
+    /**
+     * This function returns TRUE if the character is a valid token character.
+     *
+     *     token          = 1*<any CHAR except CTLs or separators>
+     *
+     *      separators     = "(" | ")" | "<" | ">" | "@"
+     *                     | "," | ";" | ":" | "\" | <">
+     *                     | "/" | "[" | "]" | "?" | "="
+     *                     | "{" | "}" | SP | HT
+     *
+     *      CTL            = <any US-ASCII control character
+     *                       (octets 0 - 31) and DEL (127)>
+     *
+     *      CHAR           = <any US-ASCII character (octets 0 - 127)>
+     *
+     * @param c  character to be validated
+     *
+     * @return True if character is valid Token character else it returns 
+     * false
+     */
+    boolean isValidTokenChar(
+        char c)
+    {
+        if ( ( (c >= '\u0000') && (c <='\u0020') ) ||
+             ( (c >= '\u003a') && (c <= '\u0040') ) ||
+             ( (c >= '\u005b') && (c <= '\u005d') ) ||
+             ('\u002c' == c) ||
+             ('\u0025' == c) ||
+             ('\u0028' == c) ||
+             ('\u0029' == c) ||
+             ('\u007b' == c) ||
+             ('\u007d' == c) ||
+             ('\u007f' == c) )
+            return false;
+
+        return true;
+    }
+
+    /**
+     * This function returns TRUE if the character is linear white space (LWS).
+     *         LWS = [CRLF] 1*( SP | HT )
+     *
+     * @param c  character to be validated
+     *
+     * @return True if character is liner whitespace else it returns false
+     */
+    boolean isWhiteSpace(
+        char c)
+    {
+        if ( ('\t' == c) || // HORIZONTAL TABULATION.
+             ('\n' == c) || // LINE FEED.
+             ('\r' == c) || // CARRIAGE RETURN.
+             ('\u0020' == c) )
+            return true;
+
+        return false;
+    }
+
+}
+
diff --git a/src/de/measite/smack/AndroidDebugger.java b/src/de/measite/smack/AndroidDebugger.java
new file mode 100644
index 0000000..4dfc622
--- /dev/null
+++ b/src/de/measite/smack/AndroidDebugger.java
@@ -0,0 +1,185 @@
+package de.measite.smack;
+
+import org.jivesoftware.smack.debugger.SmackDebugger;
+import org.jivesoftware.smack.ConnectionListener;
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.util.*;
+
+import android.util.Log;
+
+import java.io.Reader;
+import java.io.Writer;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Very simple debugger that prints to the android log the sent and received stanzas. Use
+ * this debugger with caution since printing to the console is an expensive operation that may
+ * even block the thread since only one thread may print at a time.<p>
+ * <p/>
+ * It is possible to not only print the raw sent and received stanzas but also the interpreted
+ * packets by Smack. By default interpreted packets won't be printed. To enable this feature
+ * just change the <tt>printInterpreted</tt> static variable to <tt>true</tt>.
+ *
+ * @author Gaston Dombiak
+ */
+public class AndroidDebugger implements SmackDebugger {
+
+    public static boolean printInterpreted = false;
+    private SimpleDateFormat dateFormatter = new SimpleDateFormat("hh:mm:ss aaa");
+
+    private Connection connection = null;
+
+    private PacketListener listener = null;
+    private ConnectionListener connListener = null;
+
+    private Writer writer;
+    private Reader reader;
+    private ReaderListener readerListener;
+    private WriterListener writerListener;
+
+    public AndroidDebugger(Connection connection, Writer writer, Reader reader) {
+        this.connection = connection;
+        this.writer = writer;
+        this.reader = reader;
+        createDebug();
+    }
+
+    /**
+     * Creates the listeners that will print in the console when new activity is detected.
+     */
+    private void createDebug() {
+        // Create a special Reader that wraps the main Reader and logs data to the GUI.
+        ObservableReader debugReader = new ObservableReader(reader);
+        readerListener = new ReaderListener() {
+            public void read(String str) {
+            	Log.d("SMACK",
+                        dateFormatter.format(new Date()) + " RCV  (" + connection.hashCode() +
+                        "): " +
+                        str);
+            }
+        };
+        debugReader.addReaderListener(readerListener);
+
+        // Create a special Writer that wraps the main Writer and logs data to the GUI.
+        ObservableWriter debugWriter = new ObservableWriter(writer);
+        writerListener = new WriterListener() {
+            public void write(String str) {
+            	Log.d("SMACK",
+                        dateFormatter.format(new Date()) + " SENT (" + connection.hashCode() +
+                        "): " +
+                        str);
+            }
+        };
+        debugWriter.addWriterListener(writerListener);
+
+        // Assign the reader/writer objects to use the debug versions. The packet reader
+        // and writer will use the debug versions when they are created.
+        reader = debugReader;
+        writer = debugWriter;
+
+        // Create a thread that will listen for all incoming packets and write them to
+        // the GUI. This is what we call "interpreted" packet data, since it's the packet
+        // data as Smack sees it and not as it's coming in as raw XML.
+        listener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                if (printInterpreted) {
+                	Log.d("SMACK",
+                            dateFormatter.format(new Date()) + " RCV PKT (" +
+                            connection.hashCode() +
+                            "): " +
+                            packet.toXML());
+                }
+            }
+        };
+
+        connListener = new ConnectionListener() {
+            public void connectionClosed() {
+                Log.d("SMACK",
+                        dateFormatter.format(new Date()) + " Connection closed (" +
+                        connection.hashCode() +
+                        ")");
+            }
+
+            public void connectionClosedOnError(Exception e) {
+                Log.d("SMACK",
+                        dateFormatter.format(new Date()) +
+                        " Connection closed due to an exception (" +
+                        connection.hashCode() +
+                        ")");
+                e.printStackTrace();
+            }
+            public void reconnectionFailed(Exception e) {
+                Log.d("SMACK",
+                        dateFormatter.format(new Date()) +
+                        " Reconnection failed due to an exception (" +
+                        connection.hashCode() +
+                        ")");
+                e.printStackTrace();
+            }
+            public void reconnectionSuccessful() {
+                Log.d("SMACK",
+                        dateFormatter.format(new Date()) + " Connection reconnected (" +
+                        connection.hashCode() +
+                        ")");
+            }
+            public void reconnectingIn(int seconds) {
+                Log.d("SMACK",
+                        dateFormatter.format(new Date()) + " Connection (" +
+                        connection.hashCode() +
+                        ") will reconnect in " + seconds);
+            }
+        };
+    }
+
+    public Reader newConnectionReader(Reader newReader) {
+        ((ObservableReader)reader).removeReaderListener(readerListener);
+        ObservableReader debugReader = new ObservableReader(newReader);
+        debugReader.addReaderListener(readerListener);
+        reader = debugReader;
+        return reader;
+    }
+
+    public Writer newConnectionWriter(Writer newWriter) {
+        ((ObservableWriter)writer).removeWriterListener(writerListener);
+        ObservableWriter debugWriter = new ObservableWriter(newWriter);
+        debugWriter.addWriterListener(writerListener);
+        writer = debugWriter;
+        return writer;
+    }
+
+    public void userHasLogged(String user) {
+        boolean isAnonymous = "".equals(StringUtils.parseName(user));
+        String title =
+                "User logged (" + connection.hashCode() + "): "
+                + (isAnonymous ? "" : StringUtils.parseBareAddress(user))
+                + "@"
+                + connection.getServiceName()
+                + ":"
+                + connection.getPort();
+        title += "/" + StringUtils.parseResource(user);
+        Log.d("SMACK", title);
+        // Add the connection listener to the connection so that the debugger can be notified
+        // whenever the connection is closed.
+        connection.addConnectionListener(connListener);
+    }
+
+    public Reader getReader() {
+        return reader;
+    }
+
+    public Writer getWriter() {
+        return writer;
+    }
+
+    public PacketListener getReaderListener() {
+        return listener;
+    }
+
+    public PacketListener getWriterListener() {
+        return null;
+    }
+}
+
diff --git a/src/de/measite/smack/Sasl.java b/src/de/measite/smack/Sasl.java
new file mode 100644
index 0000000..a59135d
--- /dev/null
+++ b/src/de/measite/smack/Sasl.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2009 Rene Treffer
+ *
+ * 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 de.measite.smack;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
+import org.apache.harmony.javax.security.sasl.SaslClient;
+import org.apache.harmony.javax.security.sasl.SaslException;
+import org.apache.harmony.javax.security.sasl.SaslServer;
+import org.apache.harmony.javax.security.sasl.SaslServerFactory;
+
+public class Sasl {
+
+    // SaslClientFactory service name
+    private static final String CLIENTFACTORYSRV = "SaslClientFactory"; //$NON-NLS-1$
+
+    // SaslServerFactory service name
+    private static final String SERVERFACTORYSRV = "SaslServerFactory"; //$NON-NLS-1$
+
+    public static final String POLICY_NOPLAINTEXT = "javax.security.sasl.policy.noplaintext"; //$NON-NLS-1$
+
+    public static final String POLICY_NOACTIVE = "javax.security.sasl.policy.noactive"; //$NON-NLS-1$
+
+    public static final String POLICY_NODICTIONARY = "javax.security.sasl.policy.nodictionary"; //$NON-NLS-1$
+
+    public static final String POLICY_NOANONYMOUS = "javax.security.sasl.policy.noanonymous"; //$NON-NLS-1$
+
+    public static final String POLICY_FORWARD_SECRECY = "javax.security.sasl.policy.forward"; //$NON-NLS-1$
+
+    public static final String POLICY_PASS_CREDENTIALS = "javax.security.sasl.policy.credentials"; //$NON-NLS-1$
+
+    public static final String MAX_BUFFER = "javax.security.sasl.maxbuffer"; //$NON-NLS-1$
+
+    public static final String RAW_SEND_SIZE = "javax.security.sasl.rawsendsize"; //$NON-NLS-1$
+
+    public static final String REUSE = "javax.security.sasl.reuse"; //$NON-NLS-1$
+
+    public static final String QOP = "javax.security.sasl.qop"; //$NON-NLS-1$
+
+    public static final String STRENGTH = "javax.security.sasl.strength"; //$NON-NLS-1$
+
+    public static final String SERVER_AUTH = "javax.security.sasl.server.authentication"; //$NON-NLS-1$
+
+    public static Enumeration<SaslClientFactory> getSaslClientFactories() {
+        Hashtable<SaslClientFactory,Object> factories = new Hashtable<SaslClientFactory,Object>();
+        factories.put(new SaslClientFactory(), new Object());
+        return factories.keys();
+    }
+
+    public static Enumeration<SaslServerFactory> getSaslServerFactories() {
+        return org.apache.harmony.javax.security.sasl.Sasl.getSaslServerFactories();
+    }
+
+    public static SaslServer createSaslServer(String mechanism, String protocol,
+            String serverName, Map<String, ?> prop, CallbackHandler cbh) throws SaslException {
+        return org.apache.harmony.javax.security.sasl.Sasl.createSaslServer(mechanism, protocol, serverName, prop, cbh);
+    }
+
+    public static SaslClient createSaslClient(String[] mechanisms, String authanticationID,
+            String protocol, String serverName, Map<String, ?> prop, CallbackHandler cbh)
+            throws SaslException {
+        if (mechanisms == null) {
+            throw new NullPointerException("auth.33"); //$NON-NLS-1$
+        }
+        SaslClientFactory fact = getSaslClientFactories().nextElement();
+        String[] mech = fact.getMechanismNames(null);
+        boolean is = false;
+        if (mech != null) {
+            for (int j = 0; j < mech.length; j++) {
+                for (int n = 0; n < mechanisms.length; n++) {
+                    if (mech[j].equals(mechanisms[n])) {
+                        is = true;
+                        break;
+                    }
+                }
+            }
+        }
+        if (is) {
+            return fact.createSaslClient(
+                mechanisms,
+                authanticationID,
+                protocol,
+                serverName,
+                prop,
+                cbh
+            );
+        }
+        return null;
+    }
+
+}
diff --git a/src/de/measite/smack/SaslClientFactory.java b/src/de/measite/smack/SaslClientFactory.java
new file mode 100644
index 0000000..2fa1ebd
--- /dev/null
+++ b/src/de/measite/smack/SaslClientFactory.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2009 Rene Treffer
+ *
+ * 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 de.measite.smack;
+
+import java.util.Map;
+
+import com.novell.sasl.client.DigestMD5SaslClient;
+
+import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
+import org.apache.harmony.javax.security.sasl.SaslClient;
+import org.apache.harmony.javax.security.sasl.SaslException;
+import org.apache.qpid.management.common.sasl.PlainSaslClient;
+
+public class SaslClientFactory implements
+		org.apache.harmony.javax.security.sasl.SaslClientFactory {
+
+	@Override
+	public SaslClient createSaslClient(String[] mechanisms,
+			String authorizationId, String protocol, String serverName,
+			Map<String, ?> props, CallbackHandler cbh) throws SaslException {
+		for (String mech: mechanisms) {
+			if ("PLAIN".equals(mech)) {
+				return new PlainSaslClient(authorizationId, cbh);
+			} else
+			if ("DIGEST-MD5".equals(mech)) {
+				return DigestMD5SaslClient.getClient(
+					authorizationId,
+					protocol,
+					serverName,
+					props,
+					cbh
+				);
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public String[] getMechanismNames(Map<String, ?> props) {
+		return new String[]{
+			"PLAIN",
+			"DIGEST-MD5"
+		};
+	}
+
+}
diff --git a/src/org/apache/harmony/javax/security/auth/AuthPermission.java b/src/org/apache/harmony/javax/security/auth/AuthPermission.java
new file mode 100644
index 0000000..bb12554
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/AuthPermission.java
@@ -0,0 +1,97 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth;
+
+import java.security.BasicPermission;
+
+
+
+/**
+ * Governs the use of methods in this package and also its subpackages. A
+ * <i>target name</i> of the permission specifies which methods are allowed
+ * without specifying the concrete action lists. Possible target names and
+ * associated authentication permissions are:
+ *
+ * <pre>
+ *    doAs                      invoke Subject.doAs methods.
+ *    doAsPrivileged            invoke the Subject.doAsPrivileged methods.
+ *    getSubject                invoke Subject.getSubject().
+ *    getSubjectFromDomainCombiner    invoke SubjectDomainCombiner.getSubject().
+ *    setReadOnly               invoke Subject.setReadonly().
+ *    modifyPrincipals          modify the set of principals
+ *                              associated with a Subject.
+ *    modifyPublicCredentials   modify the set of public credentials
+ *                              associated with a Subject.
+ *    modifyPrivateCredentials  modify the set of private credentials
+ *                              associated with a Subject.
+ *    refreshCredential         invoke the refresh method on a credential of a
+ *                              refreshable credential class.
+ *    destroyCredential         invoke the destroy method on a credential of a
+ *                              destroyable credential class.
+ *    createLoginContext.<i>name</i>   instantiate a LoginContext with the
+ *                              specified name. The wildcard name ('*')
+ *                              allows to a LoginContext of any name.
+ *    getLoginConfiguration     invoke the getConfiguration method of
+ *                              javax.security.auth.login.Configuration.
+ *    refreshLoginConfiguration Invoke the refresh method of
+ *                              javax.security.auth.login.Configuration.
+ * </pre>
+ */
+public final class AuthPermission extends BasicPermission {
+
+    private static final long serialVersionUID = 5806031445061587174L;
+
+    private static final String CREATE_LOGIN_CONTEXT = "createLoginContext"; //$NON-NLS-1$
+
+    private static final String CREATE_LOGIN_CONTEXT_ANY = "createLoginContext.*"; //$NON-NLS-1$
+
+    // inits permission name.
+    private static String init(String name) {
+
+        if (name == null) {
+            throw new NullPointerException("auth.13"); //$NON-NLS-1$
+        }
+
+        if (CREATE_LOGIN_CONTEXT.equals(name)) {
+            return CREATE_LOGIN_CONTEXT_ANY;
+        }
+        return name;
+    }
+
+    /**
+     * Creates an authentication permission with the specified target name.
+     *
+     * @param name
+     *            the target name of this authentication permission.
+     */
+    public AuthPermission(String name) {
+        super(init(name));
+    }
+
+    /**
+     * Creates an authentication permission with the specified target name.
+     *
+     * @param name
+     *            the target name of this authentication permission.
+     * @param actions
+     *            this parameter is ignored and should be {@code null}.
+     */
+    public AuthPermission(String name, String actions) {
+        super(init(name), actions);
+    }
+}
\ No newline at end of file
diff --git a/src/org/apache/harmony/javax/security/auth/DestroyFailedException.java b/src/org/apache/harmony/javax/security/auth/DestroyFailedException.java
new file mode 100644
index 0000000..7c7ea79
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/DestroyFailedException.java
@@ -0,0 +1,44 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth;
+
+/**
+ * Signals that the {@link Destroyable#destroy()} method failed.
+ */
+public class DestroyFailedException extends Exception {
+
+    private static final long serialVersionUID = -7790152857282749162L;
+
+    /**
+     * Creates an exception of type {@code DestroyFailedException}.
+     */
+    public DestroyFailedException() {
+        super();
+    }
+
+    /**
+     * Creates an exception of type {@code DestroyFailedException}.
+     *
+     * @param message
+     *            A detail message that describes the reason for this exception.
+     */
+    public DestroyFailedException(String message) {
+        super(message);
+    }
+
+}
diff --git a/src/org/apache/harmony/javax/security/auth/Destroyable.java b/src/org/apache/harmony/javax/security/auth/Destroyable.java
new file mode 100644
index 0000000..12a107b
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/Destroyable.java
@@ -0,0 +1,43 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth;
+
+/**
+ * Allows for special treatment of sensitive information, when it comes to
+ * destroying or clearing of the data.
+ */
+public interface Destroyable {
+
+    /**
+     * Erases the sensitive information. Once an object is destroyed any calls
+     * to its methods will throw an {@code IllegalStateException}. If it does
+     * not succeed a DestroyFailedException is thrown.
+     *
+     * @throws DestroyFailedException
+     *             if the information cannot be erased.
+     */
+    void destroy() throws DestroyFailedException;
+
+    /**
+     * Returns {@code true} once an object has been safely destroyed.
+     *
+     * @return whether the object has been safely destroyed.
+     */
+    boolean isDestroyed();
+
+}
diff --git a/src/org/apache/harmony/javax/security/auth/PrivateCredentialPermission.java b/src/org/apache/harmony/javax/security/auth/PrivateCredentialPermission.java
new file mode 100644
index 0000000..d62bb24
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/PrivateCredentialPermission.java
@@ -0,0 +1,395 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Principal;
+import java.util.Set;
+
+
+
+/**
+ * Protects private credential objects belonging to a {@code Subject}. It has
+ * only one action which is "read". The target name of this permission has a
+ * special syntax:
+ *
+ * <pre>
+ * targetName = CredentialClass {PrincipalClass &quot;PrincipalName&quot;}*
+ * </pre>
+ *
+ * First it states a credential class and is followed then by a list of one or
+ * more principals identifying the subject.
+ * <p>
+ * The principals on their part are specified as the name of the {@code
+ * Principal} class followed by the principal name in quotes. For example, the
+ * following file may define permission to read the private credentials of a
+ * principal named "Bob": "com.sun.PrivateCredential com.sun.Principal \"Bob\""
+ * <p>
+ * The syntax also allows the use of the wildcard "*" in place of {@code
+ * CredentialClass} or {@code PrincipalClass} and/or {@code PrincipalName}.
+ *
+ * @see Principal
+ */
+public final class PrivateCredentialPermission extends Permission {
+
+    private static final long serialVersionUID = 5284372143517237068L;
+
+    // allowed action
+    private static final String READ = "read"; //$NON-NLS-1$
+
+    private String credentialClass;
+
+    // current offset        
+    private transient int offset;
+
+    // owners set
+    private transient CredOwner[] set;
+    
+    /**
+     * Creates a new permission for private credentials specified by the target
+     * name {@code name} and an {@code action}. The action is always
+     * {@code "read"}.
+     *
+     * @param name
+     *            the target name of the permission.
+     * @param action
+     *            the action {@code "read"}.
+     */
+    public PrivateCredentialPermission(String name, String action) {
+        super(name);
+        if (READ.equalsIgnoreCase(action)) {
+            initTargetName(name);
+        } else {
+            throw new IllegalArgumentException("auth.11"); //$NON-NLS-1$
+        }
+    }
+
+    /**
+     * Creates a {@code PrivateCredentialPermission} from the {@code Credential}
+     * class and set of principals.
+     * 
+     * @param credentialClass
+     *            the credential class name.
+     * @param principals
+     *            the set of principals.
+     */
+    PrivateCredentialPermission(String credentialClass, Set<Principal> principals) {
+        super(credentialClass);
+        this.credentialClass = credentialClass;
+
+        set = new CredOwner[principals.size()];
+        for (Principal p : principals) {
+            CredOwner element = new CredOwner(p.getClass().getName(), p.getName());
+            // check for duplicate elements
+            boolean found = false;
+            for (int ii = 0; ii < offset; ii++) {
+                if (set[ii].equals(element)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                set[offset++] = element;
+            }
+        }
+    }
+
+    /**
+     * Initialize a PrivateCredentialPermission object and checks that a target
+     * name has a correct format: CredentialClass 1*(PrincipalClass
+     * "PrincipalName")
+     */
+    private void initTargetName(String name) {
+
+        if (name == null) {
+            throw new NullPointerException("auth.0E"); //$NON-NLS-1$
+        }
+
+        // check empty string
+        name = name.trim();
+        if (name.length() == 0) {
+            throw new IllegalArgumentException("auth.0F"); //$NON-NLS-1$
+        }
+
+        // get CredentialClass
+        int beg = name.indexOf(' ');
+        if (beg == -1) {
+            throw new IllegalArgumentException("auth.10"); //$NON-NLS-1$
+        }
+        credentialClass = name.substring(0, beg);
+
+        // get a number of pairs: PrincipalClass "PrincipalName"
+        beg++;
+        int count = 0;
+        int nameLength = name.length();
+        for (int i, j = 0; beg < nameLength; beg = j + 2, count++) {
+            i = name.indexOf(' ', beg);
+            j = name.indexOf('"', i + 2);
+
+            if (i == -1 || j == -1 || name.charAt(i + 1) != '"') {
+                throw new IllegalArgumentException("auth.10"); //$NON-NLS-1$
+            }
+        }
+
+        // name MUST have one pair at least
+        if (count < 1) {
+            throw new IllegalArgumentException("auth.10"); //$NON-NLS-1$
+        }
+
+        beg = name.indexOf(' ');
+        beg++;
+
+        // populate principal set with instances of CredOwner class
+        String principalClass;
+        String principalName;
+
+        set = new CredOwner[count];
+        for (int index = 0, i, j; index < count; beg = j + 2, index++) {
+            i = name.indexOf(' ', beg);
+            j = name.indexOf('"', i + 2);
+
+            principalClass = name.substring(beg, i);
+            principalName = name.substring(i + 2, j);
+
+            CredOwner element = new CredOwner(principalClass, principalName);
+            // check for duplicate elements
+            boolean found = false;
+            for (int ii = 0; ii < offset; ii++) {
+                if (set[ii].equals(element)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                set[offset++] = element;
+            }
+        }
+    }
+
+    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+        ois.defaultReadObject();
+        initTargetName(getName());
+    }
+
+    /**
+     * Returns the principal's classes and names associated with this {@code
+     * PrivateCredentialPermission} as a two dimensional array. The first
+     * dimension of the array corresponds to the number of principals. The
+     * second dimension defines either the name of the {@code PrincipalClass}
+     * [x][0] or the value of {@code PrincipalName} [x][1].
+     * <p>
+     * This corresponds to the the target name's syntax:
+     *
+     * <pre>
+     * targetName = CredentialClass {PrincipalClass &quot;PrincipalName&quot;}*
+     * </pre>
+     *
+     * @return the principal classes and names associated with this {@code
+     *         PrivateCredentialPermission}.
+     */
+    public String[][] getPrincipals() {
+
+        String[][] s = new String[offset][2];
+
+        for (int i = 0; i < s.length; i++) {
+            s[i][0] = set[i].principalClass;
+            s[i][1] = set[i].principalName;
+        }
+        return s;
+    }
+
+    @Override
+    public String getActions() {
+        return READ;
+    }
+
+    /**
+     * Returns the class name of the credential associated with this permission.
+     *
+     * @return the class name of the credential associated with this permission.
+     */
+    public String getCredentialClass() {
+        return credentialClass;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 0;
+        for (int i = 0; i < offset; i++) {
+            hash = hash + set[i].hashCode();
+        }
+        return getCredentialClass().hashCode() + hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj == null || this.getClass() != obj.getClass()) {
+            return false;
+        }
+
+        PrivateCredentialPermission that = (PrivateCredentialPermission) obj;
+
+        return credentialClass.equals(that.credentialClass) && (offset == that.offset)
+                && sameMembers(set, that.set, offset);
+    }
+
+    @Override
+    public boolean implies(Permission permission) {
+
+        if (permission == null || this.getClass() != permission.getClass()) {
+            return false;
+        }
+
+        PrivateCredentialPermission that = (PrivateCredentialPermission) permission;
+
+        if (!("*".equals(credentialClass) || credentialClass //$NON-NLS-1$
+                .equals(that.getCredentialClass()))) {
+            return false;
+        }
+
+        if (that.offset == 0) {
+            return true;
+        }
+
+        CredOwner[] thisCo = set;
+        CredOwner[] thatCo = that.set;
+        int thisPrincipalsSize = offset;
+        int thatPrincipalsSize = that.offset;
+        for (int i = 0, j; i < thisPrincipalsSize; i++) {
+            for (j = 0; j < thatPrincipalsSize; j++) {
+                if (thisCo[i].implies(thatCo[j])) {
+                    break;
+                }
+            }
+            if (j == thatCo.length) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public PermissionCollection newPermissionCollection() {
+        return null;
+    }
+
+    /**
+     * Returns true if the two arrays have the same length, and every member of
+     * one array is contained in another array
+     */
+    private boolean sameMembers(Object[] ar1, Object[] ar2, int length) {
+        if (ar1 == null && ar2 == null) {
+            return true;
+        }
+        if (ar1 == null || ar2 == null) {
+            return false;
+        }
+        boolean found;
+        for (int i = 0; i < length; i++) {
+            found = false;
+            for (int j = 0; j < length; j++) {
+                if (ar1[i].equals(ar2[j])) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static final class CredOwner implements Serializable {
+
+        private static final long serialVersionUID = -5607449830436408266L;
+
+        String principalClass;
+
+        String principalName;
+
+        // whether class name contains wildcards
+        private transient boolean isClassWildcard;
+
+        // whether pname contains wildcards
+        private transient boolean isPNameWildcard;
+
+        // Creates a new CredOwner with the specified Principal Class and Principal Name 
+        CredOwner(String principalClass, String principalName) {
+            super();
+            if ("*".equals(principalClass)) { //$NON-NLS-1$
+                isClassWildcard = true;
+            }
+
+            if ("*".equals(principalName)) { //$NON-NLS-1$
+                isPNameWildcard = true;
+            }
+
+            if (isClassWildcard && !isPNameWildcard) {
+                throw new IllegalArgumentException("auth.12"); //$NON-NLS-1$
+            }
+
+            this.principalClass = principalClass;
+            this.principalName = principalName;
+        }
+
+        // Checks if this CredOwner implies the specified Object. 
+        boolean implies(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+
+            CredOwner co = (CredOwner) obj;
+
+            if (isClassWildcard || principalClass.equals(co.principalClass)) {
+                if (isPNameWildcard || principalName.equals(co.principalName)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        // Checks two CredOwner objects for equality. 
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (obj instanceof CredOwner) {
+                CredOwner that = (CredOwner) obj;
+                return principalClass.equals(that.principalClass)
+                    && principalName.equals(that.principalName);
+            }
+            return false;
+        }
+
+        // Returns the hash code value for this object.
+        @Override
+        public int hashCode() {
+            return principalClass.hashCode() + principalName.hashCode();
+        }
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/RefreshFailedException.java b/src/org/apache/harmony/javax/security/auth/RefreshFailedException.java
new file mode 100644
index 0000000..71bcc6b
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/RefreshFailedException.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth;
+
+public class RefreshFailedException extends Exception {
+
+    private static final long serialVersionUID = 5058444488565265840L;
+
+    public RefreshFailedException() {
+        super();
+    }
+
+    public RefreshFailedException(String message) {
+        super(message);
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/Refreshable.java b/src/org/apache/harmony/javax/security/auth/Refreshable.java
new file mode 100644
index 0000000..90b00cb
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/Refreshable.java
@@ -0,0 +1,26 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth;
+
+public interface Refreshable {
+
+    void refresh() throws RefreshFailedException;
+
+    boolean isCurrent();
+
+}
diff --git a/src/org/apache/harmony/javax/security/auth/Subject.java b/src/org/apache/harmony/javax/security/auth/Subject.java
new file mode 100644
index 0000000..142686e
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/Subject.java
@@ -0,0 +1,782 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.DomainCombiner;
+import java.security.Permission;
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.ProtectionDomain;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Set;
+
+
+
+/**
+ * The central class of the {@code javax.security.auth} package representing an
+ * authenticated user or entity (both referred to as "subject"). IT defines also
+ * the static methods that allow code to be run, and do modifications according
+ * to the subject's permissions.
+ * <p>
+ * A subject has the following features:
+ * <ul>
+ * <li>A set of {@code Principal} objects specifying the identities bound to a
+ * {@code Subject} that distinguish it.</li>
+ * <li>Credentials (public and private) such as certificates, keys, or
+ * authentication proofs such as tickets</li>
+ * </ul>
+ */
+public final class Subject implements Serializable {
+
+    private static final long serialVersionUID = -8308522755600156056L;
+    
+    private static final AuthPermission _AS = new AuthPermission("doAs"); //$NON-NLS-1$
+
+    private static final AuthPermission _AS_PRIVILEGED = new AuthPermission(
+            "doAsPrivileged"); //$NON-NLS-1$
+
+    private static final AuthPermission _SUBJECT = new AuthPermission(
+            "getSubject"); //$NON-NLS-1$
+
+    private static final AuthPermission _PRINCIPALS = new AuthPermission(
+            "modifyPrincipals"); //$NON-NLS-1$
+
+    private static final AuthPermission _PRIVATE_CREDENTIALS = new AuthPermission(
+            "modifyPrivateCredentials"); //$NON-NLS-1$
+
+    private static final AuthPermission _PUBLIC_CREDENTIALS = new AuthPermission(
+            "modifyPublicCredentials"); //$NON-NLS-1$
+
+    private static final AuthPermission _READ_ONLY = new AuthPermission(
+            "setReadOnly"); //$NON-NLS-1$
+
+    private final Set<Principal> principals;
+
+    private boolean readOnly;
+    
+    // set of private credentials
+    private transient SecureSet<Object> privateCredentials;
+
+    // set of public credentials
+    private transient SecureSet<Object> publicCredentials;
+    
+    /**
+     * The default constructor initializing the sets of public and private
+     * credentials and principals with the empty set.
+     */
+    public Subject() {
+        super();
+        principals = new SecureSet<Principal>(_PRINCIPALS);
+        publicCredentials = new SecureSet<Object>(_PUBLIC_CREDENTIALS);
+        privateCredentials = new SecureSet<Object>(_PRIVATE_CREDENTIALS);
+
+        readOnly = false;
+    }
+
+    /**
+     * The constructor for the subject, setting its public and private
+     * credentials and principals according to the arguments.
+     *
+     * @param readOnly
+     *            {@code true} if this {@code Subject} is read-only, thus
+     *            preventing any modifications to be done.
+     * @param subjPrincipals
+     *            the set of Principals that are attributed to this {@code
+     *            Subject}.
+     * @param pubCredentials
+     *            the set of public credentials that distinguish this {@code
+     *            Subject}.
+     * @param privCredentials
+     *            the set of private credentials that distinguish this {@code
+     *            Subject}.
+     */
+    public Subject(boolean readOnly, Set<? extends Principal> subjPrincipals,
+            Set<?> pubCredentials, Set<?> privCredentials) {
+
+        if (subjPrincipals == null || pubCredentials == null || privCredentials == null) {
+            throw new NullPointerException();
+        }
+
+        principals = new SecureSet<Principal>(_PRINCIPALS, subjPrincipals);
+        publicCredentials = new SecureSet<Object>(_PUBLIC_CREDENTIALS, pubCredentials);
+        privateCredentials = new SecureSet<Object>(_PRIVATE_CREDENTIALS, privCredentials);
+
+        this.readOnly = readOnly;
+    }
+
+    /**
+     * Runs the code defined by {@code action} using the permissions granted to
+     * the {@code Subject} itself and to the code as well.
+     *
+     * @param subject
+     *            the distinguished {@code Subject}.
+     * @param action
+     *            the code to be run.
+     * @return the {@code Object} returned when running the {@code action}.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object doAs(Subject subject, PrivilegedAction action) {
+
+        checkPermission(_AS);
+
+        return doAs_PrivilegedAction(subject, action, AccessController.getContext());
+    }
+
+    /**
+     * Run the code defined by {@code action} using the permissions granted to
+     * the {@code Subject} and to the code itself, additionally providing a more
+     * specific context.
+     *
+     * @param subject
+     *            the distinguished {@code Subject}.
+     * @param action
+     *            the code to be run.
+     * @param context
+     *            the specific context in which the {@code action} is invoked.
+     *            if {@code null} a new {@link AccessControlContext} is
+     *            instantiated.
+     * @return the {@code Object} returned when running the {@code action}.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object doAsPrivileged(Subject subject, PrivilegedAction action,
+            AccessControlContext context) {
+
+        checkPermission(_AS_PRIVILEGED);
+
+        if (context == null) {
+            return doAs_PrivilegedAction(subject, action, new AccessControlContext(
+                    new ProtectionDomain[0]));
+        }
+        return doAs_PrivilegedAction(subject, action, context);
+    }
+
+    // instantiates a new context and passes it to AccessController
+    @SuppressWarnings("unchecked")
+    private static Object doAs_PrivilegedAction(Subject subject, PrivilegedAction action,
+            final AccessControlContext context) {
+
+        AccessControlContext newContext;
+
+        final SubjectDomainCombiner combiner;
+        if (subject == null) {
+            // performance optimization
+            // if subject is null there is nothing to combine
+            combiner = null;
+        } else {
+            combiner = new SubjectDomainCombiner(subject);
+        }
+
+        PrivilegedAction dccAction = new PrivilegedAction() {
+            public Object run() {
+
+                return new AccessControlContext(context, combiner);
+            }
+        };
+
+        newContext = (AccessControlContext) AccessController.doPrivileged(dccAction);
+
+        return AccessController.doPrivileged(action, newContext);
+    }
+
+    /**
+     * Runs the code defined by {@code action} using the permissions granted to
+     * the subject and to the code itself.
+     *
+     * @param subject
+     *            the distinguished {@code Subject}.
+     * @param action
+     *            the code to be run.
+     * @return the {@code Object} returned when running the {@code action}.
+     * @throws PrivilegedActionException
+     *             if running the {@code action} throws an exception.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object doAs(Subject subject, PrivilegedExceptionAction action)
+            throws PrivilegedActionException {
+
+        checkPermission(_AS);
+
+        return doAs_PrivilegedExceptionAction(subject, action, AccessController.getContext());
+    }
+
+    /**
+     * Runs the code defined by {@code action} using the permissions granted to
+     * the subject and to the code itself, additionally providing a more
+     * specific context.
+     *
+     * @param subject
+     *            the distinguished {@code Subject}.
+     * @param action
+     *            the code to be run.
+     * @param context
+     *            the specific context in which the {@code action} is invoked.
+     *            if {@code null} a new {@link AccessControlContext} is
+     *            instantiated.
+     * @return the {@code Object} returned when running the {@code action}.
+     * @throws PrivilegedActionException
+     *             if running the {@code action} throws an exception.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object doAsPrivileged(Subject subject,
+            PrivilegedExceptionAction action, AccessControlContext context)
+            throws PrivilegedActionException {
+
+        checkPermission(_AS_PRIVILEGED);
+
+        if (context == null) {
+            return doAs_PrivilegedExceptionAction(subject, action,
+                    new AccessControlContext(new ProtectionDomain[0]));
+        }
+        return doAs_PrivilegedExceptionAction(subject, action, context);
+    }
+
+    // instantiates a new context and passes it to AccessController
+    @SuppressWarnings("unchecked")
+    private static Object doAs_PrivilegedExceptionAction(Subject subject,
+            PrivilegedExceptionAction action, final AccessControlContext context)
+            throws PrivilegedActionException {
+
+        AccessControlContext newContext;
+
+        final SubjectDomainCombiner combiner;
+        if (subject == null) {
+            // performance optimization
+            // if subject is null there is nothing to combine
+            combiner = null;
+        } else {
+            combiner = new SubjectDomainCombiner(subject);
+        }
+
+        PrivilegedAction<AccessControlContext> dccAction = new PrivilegedAction<AccessControlContext>() {
+            public AccessControlContext run() {
+                return new AccessControlContext(context, combiner);
+            }
+        };
+
+        newContext = AccessController.doPrivileged(dccAction);
+
+        return AccessController.doPrivileged(action, newContext);
+    }
+
+    /**
+     * Checks two Subjects for equality. More specifically if the principals,
+     * public and private credentials are equal, equality for two {@code
+     * Subjects} is implied.
+     *
+     * @param obj
+     *            the {@code Object} checked for equality with this {@code
+     *            Subject}.
+     * @return {@code true} if the specified {@code Subject} is equal to this
+     *         one.
+     */
+    @Override
+    public boolean equals(Object obj) {
+
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj == null || this.getClass() != obj.getClass()) {
+            return false;
+        }
+
+        Subject that = (Subject) obj;
+
+        if (principals.equals(that.principals)
+                && publicCredentials.equals(that.publicCredentials)
+                && privateCredentials.equals(that.privateCredentials)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns this {@code Subject}'s {@link Principal}.
+     *
+     * @return this {@code Subject}'s {@link Principal}.
+     */
+    public Set<Principal> getPrincipals() {
+        return principals;
+    }
+
+
+    /**
+     * Returns this {@code Subject}'s {@link Principal} which is a subclass of
+     * the {@code Class} provided.
+     *
+     * @param c
+     *            the {@code Class} as a criteria which the {@code Principal}
+     *            returned must satisfy.
+     * @return this {@code Subject}'s {@link Principal}. Modifications to the
+     *         returned set of {@code Principal}s do not affect this {@code
+     *         Subject}'s set.
+     */
+    public <T extends Principal> Set<T> getPrincipals(Class<T> c) {
+        return ((SecureSet<Principal>) principals).get(c);
+    }
+
+    /**
+     * Returns the private credentials associated with this {@code Subject}.
+     *
+     * @return the private credentials associated with this {@code Subject}.
+     */
+    public Set<Object> getPrivateCredentials() {
+        return privateCredentials;
+    }
+
+    /**
+     * Returns this {@code Subject}'s private credentials which are a subclass
+     * of the {@code Class} provided.
+     *
+     * @param c
+     *            the {@code Class} as a criteria which the private credentials
+     *            returned must satisfy.
+     * @return this {@code Subject}'s private credentials. Modifications to the
+     *         returned set of credentials do not affect this {@code Subject}'s
+     *         credentials.
+     */
+    public <T> Set<T> getPrivateCredentials(Class<T> c) {
+        return privateCredentials.get(c);
+    }
+
+    /**
+     * Returns the public credentials associated with this {@code Subject}.
+     *
+     * @return the public credentials associated with this {@code Subject}.
+     */
+    public Set<Object> getPublicCredentials() {
+        return publicCredentials;
+    }
+
+
+    /**
+     * Returns this {@code Subject}'s public credentials which are a subclass of
+     * the {@code Class} provided.
+     *
+     * @param c
+     *            the {@code Class} as a criteria which the public credentials
+     *            returned must satisfy.
+     * @return this {@code Subject}'s public credentials. Modifications to the
+     *         returned set of credentials do not affect this {@code Subject}'s
+     *         credentials.
+     */
+    public <T> Set<T> getPublicCredentials(Class<T> c) {
+        return publicCredentials.get(c);
+    }
+
+    /**
+     * Returns a hash code of this {@code Subject}.
+     *
+     * @return a hash code of this {@code Subject}.
+     */
+    @Override
+    public int hashCode() {
+        return principals.hashCode() + privateCredentials.hashCode()
+                + publicCredentials.hashCode();
+    }
+
+    /**
+     * Prevents from modifications being done to the credentials and {@link
+     * Principal} sets. After setting it to read-only this {@code Subject} can
+     * not be made writable again. The destroy method on the credentials still
+     * works though.
+     */
+    public void setReadOnly() {
+        checkPermission(_READ_ONLY);
+
+        readOnly = true;
+    }
+
+    /**
+     * Returns whether this {@code Subject} is read-only or not.
+     *
+     * @return whether this {@code Subject} is read-only or not.
+     */
+    public boolean isReadOnly() {
+        return readOnly;
+    }
+
+    /**
+     * Returns a {@code String} representation of this {@code Subject}.
+     *
+     * @return a {@code String} representation of this {@code Subject}.
+     */
+    @Override
+    public String toString() {
+
+        StringBuilder buf = new StringBuilder("Subject:\n"); //$NON-NLS-1$
+
+        Iterator<?> it = principals.iterator();
+        while (it.hasNext()) {
+            buf.append("\tPrincipal: "); //$NON-NLS-1$
+            buf.append(it.next());
+            buf.append('\n');
+        }
+
+        it = publicCredentials.iterator();
+        while (it.hasNext()) {
+            buf.append("\tPublic Credential: "); //$NON-NLS-1$
+            buf.append(it.next());
+            buf.append('\n');
+        }
+
+        int offset = buf.length() - 1;
+        it = privateCredentials.iterator();
+        try {
+            while (it.hasNext()) {
+                buf.append("\tPrivate Credential: "); //$NON-NLS-1$
+                buf.append(it.next());
+                buf.append('\n');
+            }
+        } catch (SecurityException e) {
+            buf.delete(offset, buf.length());
+            buf.append("\tPrivate Credentials: no accessible information\n"); //$NON-NLS-1$
+        }
+        return buf.toString();
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException,
+            ClassNotFoundException {
+
+        in.defaultReadObject();
+
+        publicCredentials = new SecureSet<Object>(_PUBLIC_CREDENTIALS);
+        privateCredentials = new SecureSet<Object>(_PRIVATE_CREDENTIALS);
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.defaultWriteObject();
+    }
+
+    /**
+     * Returns the {@code Subject} that was last associated with the {@code
+     * context} provided as argument.
+     *
+     * @param context
+     *            the {@code context} that was associated with the
+     *            {@code Subject}.
+     * @return the {@code Subject} that was last associated with the {@code
+     *         context} provided as argument.
+     */
+    public static Subject getSubject(final AccessControlContext context) {
+        checkPermission(_SUBJECT);
+        if (context == null) {
+            throw new NullPointerException("auth.09"); //$NON-NLS-1$
+        }
+        PrivilegedAction<DomainCombiner> action = new PrivilegedAction<DomainCombiner>() {
+            public DomainCombiner run() {
+                return context.getDomainCombiner();
+            }
+        };
+        DomainCombiner combiner = AccessController.doPrivileged(action);
+
+        if ((combiner == null) || !(combiner instanceof SubjectDomainCombiner)) {
+            return null;
+        }
+        return ((SubjectDomainCombiner) combiner).getSubject();
+    }
+
+    // checks passed permission
+    private static void checkPermission(Permission p) {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(p);
+        }
+    }
+
+    // FIXME is used only in two places. remove?
+    private void checkState() {
+        if (readOnly) {
+            throw new IllegalStateException("auth.0A"); //$NON-NLS-1$
+        }
+    }
+
+    private final class SecureSet<SST> extends AbstractSet<SST> implements Serializable {
+
+        /**
+         * Compatibility issue: see comments for setType variable
+         */
+        private static final long serialVersionUID = 7911754171111800359L;
+
+        private LinkedList<SST> elements;
+
+        /*
+         * Is used to define a set type for serialization.
+         * 
+         * A type can be principal, priv. or pub. credential set. The spec.
+         * doesn't clearly says that priv. and pub. credential sets can be
+         * serialized and what classes they are. It is only possible to figure
+         * out from writeObject method comments that priv. credential set is
+         * serializable and it is an instance of SecureSet class. So pub.
+         * credential was implemented by analogy
+         * 
+         * Compatibility issue: the class follows its specified serial form.
+         * Also according to the serialization spec. adding new field is a
+         * compatible change. So is ok for principal set (because the default
+         * value for integer is zero). But priv. or pub. credential set it is
+         * not compatible because most probably other implementations resolve
+         * this issue in other way
+         */
+        private int setType;
+
+        // Defines principal set for serialization.
+        private static final int SET_Principal = 0;
+
+        // Defines private credential set for serialization.
+        private static final int SET_PrivCred = 1;
+
+        // Defines public credential set for serialization.
+        private static final int SET_PubCred = 2;
+
+        // permission required to modify set
+        private transient AuthPermission permission;
+
+        protected SecureSet(AuthPermission perm) {
+            permission = perm;
+            elements = new LinkedList<SST>();
+        }
+
+        // creates set from specified collection with specified permission
+        // all collection elements are verified before adding
+        protected SecureSet(AuthPermission perm, Collection<? extends SST> s) {
+            this(perm);
+
+            // Subject's constructor receives a Set, we can trusts if a set is from bootclasspath,
+            // and not to check whether it contains duplicates or not
+            boolean trust = s.getClass().getClassLoader() == null; 
+            
+            Iterator<? extends SST> it = s.iterator();
+            while (it.hasNext()) {
+                SST o = it.next();
+                verifyElement(o);
+                if (trust || !elements.contains(o)) {
+                    elements.add(o);
+                }
+            }
+        }
+
+        // verifies new set element
+        private void verifyElement(Object o) {
+
+            if (o == null) {
+                throw new NullPointerException();
+            }
+            if (permission == _PRINCIPALS && !(Principal.class.isAssignableFrom(o.getClass()))) {
+                throw new IllegalArgumentException("auth.0B"); //$NON-NLS-1$
+            }
+        }
+
+        /*
+         * verifies specified element, checks set state, and security permission
+         * to modify set before adding new element
+         */
+        @Override
+        public boolean add(SST o) {
+
+            verifyElement(o);
+
+            checkState();
+            checkPermission(permission);
+
+            if (!elements.contains(o)) {
+                elements.add(o);
+                return true;
+            }
+            return false;
+        }
+
+        // returns an instance of SecureIterator
+        @Override
+        public Iterator<SST> iterator() {
+
+            if (permission == _PRIVATE_CREDENTIALS) {
+                /*
+                 * private credential set requires iterator with additional
+                 * security check (PrivateCredentialPermission)
+                 */
+                return new SecureIterator(elements.iterator()) {
+                    /*
+                     * checks permission to access next private credential moves
+                     * to the next element even SecurityException was thrown
+                     */
+                    @Override
+                    public SST next() {
+                        SST obj = iterator.next();
+                        checkPermission(new PrivateCredentialPermission(obj
+                                .getClass().getName(), principals));
+                        return obj;
+                    }
+                };
+            }
+            return new SecureIterator(elements.iterator());
+        }
+
+        @Override
+        public boolean retainAll(Collection<?> c) {
+
+            if (c == null) {
+                throw new NullPointerException();
+            }
+            return super.retainAll(c);
+        }
+
+        @Override
+        public int size() {
+            return elements.size();
+        }
+
+        /**
+         * return set with elements that are instances or subclasses of the
+         * specified class
+         */
+        protected final <E> Set<E> get(final Class<E> c) {
+
+            if (c == null) {
+                throw new NullPointerException();
+            }
+
+            AbstractSet<E> s = new AbstractSet<E>() {
+                private LinkedList<E> elements = new LinkedList<E>();
+
+                @Override
+                public boolean add(E o) {
+
+                    if (!c.isAssignableFrom(o.getClass())) {
+                        throw new IllegalArgumentException(
+                                "auth.0C " + c.getName()); //$NON-NLS-1$
+                    }
+
+                    if (elements.contains(o)) {
+                        return false;
+                    }
+                    elements.add(o);
+                    return true;
+                }
+
+                @Override
+                public Iterator<E> iterator() {
+                    return elements.iterator();
+                }
+
+                @Override
+                public boolean retainAll(Collection<?> c) {
+
+                    if (c == null) {
+                        throw new NullPointerException();
+                    }
+                    return super.retainAll(c);
+                }
+
+                @Override
+                public int size() {
+                    return elements.size();
+                }
+            };
+
+            // FIXME must have permissions for requested priv. credentials
+            for (Iterator<SST> it = iterator(); it.hasNext();) {
+                SST o = it.next();
+                if (c.isAssignableFrom(o.getClass())) {
+                    s.add(c.cast(o));
+                }
+            }
+            return s;
+        }
+
+        private void readObject(ObjectInputStream in) throws IOException,
+                ClassNotFoundException {
+            in.defaultReadObject();
+
+            switch (setType) {
+            case SET_Principal:
+                permission = _PRINCIPALS;
+                break;
+            case SET_PrivCred:
+                permission = _PRIVATE_CREDENTIALS;
+                break;
+            case SET_PubCred:
+                permission = _PUBLIC_CREDENTIALS;
+                break;
+            default:
+                throw new IllegalArgumentException();
+            }
+
+            Iterator<SST> it = elements.iterator();
+            while (it.hasNext()) {
+                verifyElement(it.next());
+            }
+        }
+
+        private void writeObject(ObjectOutputStream out) throws IOException {
+
+            if (permission == _PRIVATE_CREDENTIALS) {
+                // does security check for each private credential
+                for (Iterator<SST> it = iterator(); it.hasNext();) {
+                    it.next();
+                }
+                setType = SET_PrivCred;
+            } else if (permission == _PRINCIPALS) {
+                setType = SET_Principal;
+            } else {
+                setType = SET_PubCred;
+            }
+
+            out.defaultWriteObject();
+        }
+
+        /**
+         * Represents iterator for subject's secure set
+         */
+        private class SecureIterator implements Iterator<SST> {
+            protected Iterator<SST> iterator;
+
+            protected SecureIterator(Iterator<SST> iterator) {
+                this.iterator = iterator;
+            }
+
+            public boolean hasNext() {
+                return iterator.hasNext();
+            }
+
+            public SST next() {
+                return iterator.next();
+            }
+
+            /**
+             * checks set state, and security permission to modify set before
+             * removing current element
+             */
+            public void remove() {
+                checkState();
+                checkPermission(permission);
+                iterator.remove();
+            }
+        }
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/SubjectDomainCombiner.java b/src/org/apache/harmony/javax/security/auth/SubjectDomainCombiner.java
new file mode 100644
index 0000000..edbb672
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/SubjectDomainCombiner.java
@@ -0,0 +1,123 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth;
+
+import java.security.DomainCombiner;
+import java.security.Principal;
+import java.security.ProtectionDomain;
+import java.util.Set;
+
+/**
+ * Merges permissions based on code source and code signers with permissions
+ * granted to the specified {@link Subject}.
+ */
+public class SubjectDomainCombiner implements DomainCombiner {
+
+    // subject to be associated
+    private Subject subject;
+
+    // permission required to get a subject object
+    private static final AuthPermission _GET = new AuthPermission(
+            "getSubjectFromDomainCombiner"); //$NON-NLS-1$
+
+    /**
+     * Creates a domain combiner for the entity provided in {@code subject}.
+     *
+     * @param subject
+     *            the entity to which this domain combiner is associated.
+     */
+    public SubjectDomainCombiner(Subject subject) {
+        super();
+        if (subject == null) {
+            throw new NullPointerException();
+        }
+        this.subject = subject;
+    }
+
+    /**
+     * Returns the entity to which this domain combiner is associated.
+     *
+     * @return the entity to which this domain combiner is associated.
+     */
+    public Subject getSubject() {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(_GET);
+        }
+
+        return subject;
+    }
+
+    /**
+     * Merges the {@code ProtectionDomain} with the {@code Principal}s
+     * associated with the subject of this {@code SubjectDomainCombiner}.
+     *
+     * @param currentDomains
+     *            the {@code ProtectionDomain}s associated with the context of
+     *            the current thread. The domains must be sorted according to
+     *            the execution order, the most recent residing at the
+     *            beginning.
+     * @param assignedDomains
+     *            the {@code ProtectionDomain}s from the parent thread based on
+     *            code source and signers.
+     * @return a single {@code ProtectionDomain} array computed from the two
+     *         provided arrays, or {@code null}.
+     * @see ProtectionDomain
+     */
+    public ProtectionDomain[] combine(ProtectionDomain[] currentDomains,
+            ProtectionDomain[] assignedDomains) {
+        // get array length for combining protection domains
+        int len = 0;
+        if (currentDomains != null) {
+            len += currentDomains.length;
+        }
+        if (assignedDomains != null) {
+            len += assignedDomains.length;
+        }
+        if (len == 0) {
+            return null;
+        }
+
+        ProtectionDomain[] pd = new ProtectionDomain[len];
+
+        // for each current domain substitute set of principal with subject's
+        int cur = 0;
+        if (currentDomains != null) {
+
+            Set<Principal> s = subject.getPrincipals();
+            Principal[] p = s.toArray(new Principal[s.size()]);
+
+            for (cur = 0; cur < currentDomains.length; cur++) {
+                if (currentDomains[cur] != null) {
+                    ProtectionDomain newPD;
+                    newPD = new ProtectionDomain(currentDomains[cur].getCodeSource(),
+                            currentDomains[cur].getPermissions(), currentDomains[cur]
+                                    .getClassLoader(), p);
+                    pd[cur] = newPD;
+                }
+            }
+        }
+
+        // copy assigned domains
+        if (assignedDomains != null) {
+            System.arraycopy(assignedDomains, 0, pd, cur, assignedDomains.length);
+        }
+
+        return pd;
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/callback/Callback.java b/src/org/apache/harmony/javax/security/auth/callback/Callback.java
new file mode 100644
index 0000000..8fd745c
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/callback/Callback.java
@@ -0,0 +1,25 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback;
+
+/**
+ * Defines an empty base interface for all {@code Callback}s used during
+ * authentication.
+ */
+public interface Callback {
+}
\ No newline at end of file
diff --git a/src/org/apache/harmony/javax/security/auth/callback/CallbackHandler.java b/src/org/apache/harmony/javax/security/auth/callback/CallbackHandler.java
new file mode 100644
index 0000000..d09fafa
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/callback/CallbackHandler.java
@@ -0,0 +1,54 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback;
+
+import java.io.IOException;
+
+/**
+ * Needs to be implemented by classes that want to handle authentication
+ * {@link Callback}s. A single method {@link #handle(Callback[])} must be
+ * provided that checks the type of the incoming {@code Callback}s and reacts
+ * accordingly. {@code CallbackHandler}s can be installed per application. It is
+ * also possible to configure a system-default {@code CallbackHandler} by
+ * setting the {@code auth.login.defaultCallbackHandler} property in the
+ * standard {@code security.properties} file.
+ */
+public interface CallbackHandler {
+
+    /**
+     * Handles the actual {@link Callback}. A {@code CallbackHandler} needs to
+     * implement this method. In the method, it is free to select which {@code
+     * Callback}s it actually wants to handle and in which way. For example, a
+     * console-based {@code CallbackHandler} might choose to sequentially ask
+     * the user for login and password, if it implements these {@code Callback}
+     * s, whereas a GUI-based one might open a single dialog window for both
+     * values. If a {@code CallbackHandler} is not able to handle a specific
+     * {@code Callback}, it needs to throw an
+     * {@link UnsupportedCallbackException}.
+     *
+     * @param callbacks
+     *            the array of {@code Callback}s that need handling
+     * @throws IOException
+     *             if an I/O related error occurs
+     * @throws UnsupportedCallbackException
+     *             if the {@code CallbackHandler} is not able to handle a
+     *             specific {@code Callback}
+     */
+    void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException;
+
+}
diff --git a/src/org/apache/harmony/javax/security/auth/callback/ChoiceCallback.java b/src/org/apache/harmony/javax/security/auth/callback/ChoiceCallback.java
new file mode 100644
index 0000000..1e53fb6
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/callback/ChoiceCallback.java
@@ -0,0 +1,109 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback;
+
+import java.io.Serializable;
+
+
+
+public class ChoiceCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = -3975664071579892167L;
+
+    private int defaultChoice;
+
+    private String prompt;
+
+    private boolean multipleSelectionsAllowed;
+
+    private String[] choices;
+
+    private int[] selections;
+
+    private void setChoices(String[] choices) {
+        if (choices == null || choices.length == 0) {
+            throw new IllegalArgumentException("auth.1C"); //$NON-NLS-1$
+        }
+        for (int i = 0; i < choices.length; i++) {
+            if (choices[i] == null || choices[i].length() == 0) {
+                throw new IllegalArgumentException("auth.1C"); //$NON-NLS-1$
+            }
+        }
+        //FIXME: System.arraycopy(choices, 0 , new String[choices.length], 0, choices.length);
+        this.choices = choices;
+
+    }
+
+    private void setPrompt(String prompt) {
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+    }
+
+    private void setDefaultChoice(int defaultChoice) {
+        if (0 > defaultChoice || defaultChoice >= choices.length) {
+            throw new IllegalArgumentException("auth.1D"); //$NON-NLS-1$
+        }
+        this.defaultChoice = defaultChoice;
+    }
+
+    public ChoiceCallback(String prompt, String[] choices, int defaultChoice,
+            boolean multipleSelectionsAllowed) {
+        super();
+        setPrompt(prompt);
+        setChoices(choices);
+        setDefaultChoice(defaultChoice);
+        this.multipleSelectionsAllowed = multipleSelectionsAllowed;
+    }
+
+    public boolean allowMultipleSelections() {
+        return multipleSelectionsAllowed;
+    }
+
+    public String[] getChoices() {
+        return choices;
+    }
+
+    public int getDefaultChoice() {
+        return defaultChoice;
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public int[] getSelectedIndexes() {
+        return selections;
+    }
+
+    public void setSelectedIndex(int selection) {
+        this.selections = new int[1];
+        this.selections[0] = selection;
+    }
+
+    public void setSelectedIndexes(int[] selections) {
+        if (!multipleSelectionsAllowed) {
+            throw new UnsupportedOperationException();
+        }
+        this.selections = selections;
+        //FIXME: 
+        // this.selections = new int[selections.length]
+        //System.arraycopy(selections, 0, this.selections, 0, this.selections.length);
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/callback/ConfirmationCallback.java b/src/org/apache/harmony/javax/security/auth/callback/ConfirmationCallback.java
new file mode 100644
index 0000000..a1893f3
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/callback/ConfirmationCallback.java
@@ -0,0 +1,234 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback;
+
+import java.io.Serializable;
+
+
+
+public class ConfirmationCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = -9095656433782481624L;
+
+    public static final int YES = 0; // default options
+
+    public static final int NO = 1;
+
+    public static final int CANCEL = 2;
+
+    public static final int OK = 3;
+
+    public static final int YES_NO_OPTION = 0; // options type
+
+    public static final int YES_NO_CANCEL_OPTION = 1;
+
+    public static final int OK_CANCEL_OPTION = 2;
+
+    public static final int UNSPECIFIED_OPTION = -1;
+
+    public static final int INFORMATION = 0; // messages type
+
+    public static final int WARNING = 1;
+
+    public static final int ERROR = 2;
+
+    private String prompt;
+
+    private int messageType;
+
+    private int optionType = UNSPECIFIED_OPTION;
+
+    private int defaultOption;
+
+    private String[] options;
+
+    private int selection;
+
+    public ConfirmationCallback(int messageType, int optionType, int defaultOption) {
+        super();
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$
+        }
+
+        switch (optionType) {
+            case YES_NO_OPTION:
+                if (defaultOption != YES && defaultOption != NO) {
+                    throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$
+                }
+                break;
+            case YES_NO_CANCEL_OPTION:
+                if (defaultOption != YES && defaultOption != NO && defaultOption != CANCEL) {
+                    throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$
+                }
+                break;
+            case OK_CANCEL_OPTION:
+                if (defaultOption != OK && defaultOption != CANCEL) {
+                    throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("auth.18"); //$NON-NLS-1$
+        }
+        this.messageType = messageType;
+        this.optionType = optionType;
+        this.defaultOption = defaultOption;
+    }
+
+    public ConfirmationCallback(int messageType, String[] options, int defaultOption) {
+        super();
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$
+        }
+
+        if (options == null || options.length == 0) {
+            throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$
+        }
+        for (int i = 0; i < options.length; i++) {
+            if (options[i] == null || options[i].length() == 0) {
+                throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$
+            }
+        }
+        if (0 > defaultOption || defaultOption >= options.length) {
+            throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$
+        }
+        // FIXME:System.arraycopy(options, 0 , new String[this.options.length],
+        // 0, this.options.length);
+        this.options = options;
+        this.defaultOption = defaultOption;
+        this.messageType = messageType;
+    }
+
+    public ConfirmationCallback(String prompt, int messageType, int optionType,
+            int defaultOption) {
+        super();
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$
+        }
+
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$
+        }
+
+        switch (optionType) {
+            case YES_NO_OPTION:
+                if (defaultOption != YES && defaultOption != NO) {
+                    throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$
+                }
+                break;
+            case YES_NO_CANCEL_OPTION:
+                if (defaultOption != YES && defaultOption != NO && defaultOption != CANCEL) {
+                    throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$
+                }
+                break;
+            case OK_CANCEL_OPTION:
+                if (defaultOption != OK && defaultOption != CANCEL) {
+                    throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("auth.18"); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+        this.messageType = messageType;
+        this.optionType = optionType;
+        this.defaultOption = defaultOption;
+    }
+
+    public ConfirmationCallback(String prompt, int messageType, String[] options,
+            int defaultOption) {
+        super();
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$
+        }
+
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$
+        }
+
+        if (options == null || options.length == 0) {
+            throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$
+        }
+        for (int i = 0; i < options.length; i++) {
+            if (options[i] == null || options[i].length() == 0) {
+                throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$
+            }
+        }
+        if (0 > defaultOption || defaultOption >= options.length) {
+            throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$
+        }
+        // FIXME:System.arraycopy(options, 0 , new String[this.options.length],
+        // 0, this.options.length);
+        this.options = options;
+        this.defaultOption = defaultOption;
+        this.messageType = messageType;
+        this.prompt = prompt;
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public int getMessageType() {
+        return messageType;
+    }
+
+    public int getDefaultOption() {
+        return defaultOption;
+    }
+
+    public String[] getOptions() {
+        return options;
+    }
+
+    public int getOptionType() {
+        return optionType;
+    }
+
+    public int getSelectedIndex() {
+        return selection;
+    }
+
+    public void setSelectedIndex(int selection) {
+        if (options != null) {
+            if (0 <= selection && selection <= options.length) {
+                this.selection = selection;
+            } else {
+                throw new ArrayIndexOutOfBoundsException("auth.1B"); //$NON-NLS-1$
+            }
+        } else {
+            switch (optionType) {
+                case YES_NO_OPTION:
+                    if (selection != YES && selection != NO) {
+                        throw new IllegalArgumentException("auth.19"); //$NON-NLS-1$
+                    }
+                    break;
+                case YES_NO_CANCEL_OPTION:
+                    if (selection != YES && selection != NO && selection != CANCEL) {
+                        throw new IllegalArgumentException("auth.19"); //$NON-NLS-1$
+                    }
+                    break;
+                case OK_CANCEL_OPTION:
+                    if (selection != OK && selection != CANCEL) {
+                        throw new IllegalArgumentException("auth.19"); //$NON-NLS-1$
+                    }
+                    break;
+            }
+            this.selection = selection;
+        }
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/callback/LanguageCallback.java b/src/org/apache/harmony/javax/security/auth/callback/LanguageCallback.java
new file mode 100644
index 0000000..729bb49
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/callback/LanguageCallback.java
@@ -0,0 +1,41 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback;
+
+import java.io.Serializable;
+import java.util.Locale;
+
+public class LanguageCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = 2019050433478903213L;
+
+    private Locale locale;
+
+    public LanguageCallback() {
+        super();
+    }
+
+    public Locale getLocale() {
+        return locale;
+    }
+
+    public void setLocale(Locale locale) {
+        this.locale = locale;
+    }
+
+}
diff --git a/src/org/apache/harmony/javax/security/auth/callback/NameCallback.java b/src/org/apache/harmony/javax/security/auth/callback/NameCallback.java
new file mode 100644
index 0000000..97264ef
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/callback/NameCallback.java
@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback;
+
+import java.io.Serializable;
+
+
+
+public class NameCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = 3770938795909392253L;
+
+    private String prompt;
+
+    private String defaultName;
+
+    private String inputName;
+
+    private void setPrompt(String prompt) {
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+    }
+
+    private void setDefaultName(String defaultName) {
+        if (defaultName == null || defaultName.length() == 0) {
+            throw new IllegalArgumentException("auth.1E"); //$NON-NLS-1$
+        }
+        this.defaultName = defaultName;
+    }
+
+    public NameCallback(String prompt) {
+        super();
+        setPrompt(prompt);
+    }
+
+    public NameCallback(String prompt, String defaultName) {
+        super();
+        setPrompt(prompt);
+        setDefaultName(defaultName);
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public String getDefaultName() {
+        return defaultName;
+    }
+
+    public void setName(String name) {
+        this.inputName = name;
+    }
+
+    public String getName() {
+        return inputName;
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/callback/PasswordCallback.java b/src/org/apache/harmony/javax/security/auth/callback/PasswordCallback.java
new file mode 100644
index 0000000..bd142fc
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/callback/PasswordCallback.java
@@ -0,0 +1,124 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+
+
+/**
+ * Is used in conjunction with a {@link CallbackHandler} to retrieve a password
+ * when needed.
+ */
+public class PasswordCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = 2267422647454909926L;
+
+    private String prompt;
+
+    boolean echoOn;
+
+    private char[] inputPassword;
+
+    private void setPrompt(String prompt) throws IllegalArgumentException {
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+    }
+
+    /**
+     * Creates a new {@code PasswordCallback} instance.
+     *
+     * @param prompt
+     *            the message that should be displayed to the user
+     * @param echoOn
+     *            determines whether the user input should be echoed
+     */
+    public PasswordCallback(String prompt, boolean echoOn) {
+        super();
+        setPrompt(prompt);
+        this.echoOn = echoOn;
+    }
+
+    /**
+     * Returns the prompt that was specified when creating this {@code
+     * PasswordCallback}
+     *
+     * @return the prompt
+     */
+    public String getPrompt() {
+        return prompt;
+    }
+
+    /**
+     * Queries whether this {@code PasswordCallback} expects user input to be
+     * echoed, which is specified during the creation of the object.
+     *
+     * @return {@code true} if (and only if) user input should be echoed
+     */
+    public boolean isEchoOn() {
+        return echoOn;
+    }
+
+    /**
+     * Sets the password. The {@link CallbackHandler} that performs the actual
+     * provisioning or input of the password needs to call this method to hand
+     * back the password to the security service that requested it.
+     *
+     * @param password
+     *            the password. A copy of this is stored, so subsequent changes
+     *            to the input array do not affect the {@code PasswordCallback}.
+     */
+    public void setPassword(char[] password) {
+        if (password == null) {
+            this.inputPassword = password;
+        } else {
+            inputPassword = new char[password.length];
+            System.arraycopy(password, 0, inputPassword, 0, inputPassword.length);
+        }
+    }
+
+    /**
+     * Returns the password. The security service that needs the password
+     * usually calls this method once the {@link CallbackHandler} has finished
+     * its work.
+     *
+     * @return the password. A copy of the internal password is created and
+     *         returned, so subsequent changes to the internal password do not
+     *         affect the result.
+     */
+    public char[] getPassword() {
+        if (inputPassword != null) {
+            char[] tmp = new char[inputPassword.length];
+            System.arraycopy(inputPassword, 0, tmp, 0, tmp.length);
+            return tmp;
+        }
+        return null;
+    }
+
+    /**
+     * Clears the password stored in this {@code PasswordCallback}.
+     */
+    public void clearPassword() {
+        if (inputPassword != null) {
+            Arrays.fill(inputPassword, '\u0000');
+        }
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/callback/TextInputCallback.java b/src/org/apache/harmony/javax/security/auth/callback/TextInputCallback.java
new file mode 100644
index 0000000..c7de222
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/callback/TextInputCallback.java
@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback;
+
+import java.io.Serializable;
+
+
+
+public class TextInputCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = -8064222478852811804L;
+
+    private String defaultText;
+
+    private String prompt;
+
+    private String inputText;
+
+    private void setPrompt(String prompt) {
+        if (prompt == null || prompt.length() == 0) {
+            throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$
+        }
+        this.prompt = prompt;
+    }
+
+    private void setDefaultText(String defaultText) {
+        if (defaultText == null || defaultText.length() == 0) {
+            throw new IllegalArgumentException("auth.15"); //$NON-NLS-1$
+        }
+        this.defaultText = defaultText;
+    }
+
+    public TextInputCallback(String prompt) {
+        super();
+        setPrompt(prompt);
+    }
+
+    public TextInputCallback(String prompt, String defaultText) {
+        super();
+        setPrompt(prompt);
+        setDefaultText(defaultText);
+    }
+
+    public String getDefaultText() {
+        return defaultText;
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    public String getText() {
+        return inputText;
+    }
+
+    public void setText(String text) {
+        this.inputText = text;
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/callback/TextOutputCallback.java b/src/org/apache/harmony/javax/security/auth/callback/TextOutputCallback.java
new file mode 100644
index 0000000..23a72fa
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/callback/TextOutputCallback.java
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback;
+
+import java.io.Serializable;
+
+
+
+public class TextOutputCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = 1689502495511663102L;
+
+    public static final int INFORMATION = 0;
+
+    public static final int WARNING = 1;
+
+    public static final int ERROR = 2;
+
+    private String message;
+
+    private int messageType;
+
+    public TextOutputCallback(int messageType, String message) {
+        if (messageType > ERROR || messageType < INFORMATION) {
+            throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$
+        }
+        if (message == null || message.length() == 0) {
+            throw new IllegalArgumentException("auth.1F"); //$NON-NLS-1$
+        }
+        this.messageType = messageType;
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public int getMessageType() {
+        return messageType;
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/callback/UnsupportedCallbackException.java b/src/org/apache/harmony/javax/security/auth/callback/UnsupportedCallbackException.java
new file mode 100644
index 0000000..19f6e40
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/callback/UnsupportedCallbackException.java
@@ -0,0 +1,64 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.callback;
+
+/**
+ * Thrown when a {@link CallbackHandler} does not support a particular {@link
+ * Callback}.
+ */
+public class UnsupportedCallbackException extends Exception {
+
+    private static final long serialVersionUID = -6873556327655666839L;
+
+    private Callback callback;
+
+    /**
+     * Creates a new exception instance and initializes it with just the
+     * unsupported {@code Callback}, but no error message.
+     *
+     * @param callback
+     *            the {@code Callback}
+     */
+    public UnsupportedCallbackException(Callback callback) {
+        super();
+        this.callback = callback;
+    }
+
+    /**
+     * Creates a new exception instance and initializes it with both the
+     * unsupported {@code Callback} and an error message.
+     *
+     * @param callback
+     *            the {@code Callback}
+     * @param message
+     *            the error message
+     */
+    public UnsupportedCallbackException(Callback callback, String message) {
+        super(message);
+        this.callback = callback;
+    }
+
+    /**
+     * Returns the unsupported {@code Callback} that triggered this exception.
+     *
+     * @return the {@code Callback}
+     */
+    public Callback getCallback() {
+        return callback;
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/login/AccountException.java b/src/org/apache/harmony/javax/security/auth/login/AccountException.java
new file mode 100644
index 0000000..c86e801
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/login/AccountException.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login;
+
+public class AccountException extends LoginException {
+
+    private static final long serialVersionUID = -2112878680072211787L;
+
+    public AccountException() {
+        super();
+    }
+
+    public AccountException(String message) {
+        super(message);
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/login/AccountExpiredException.java b/src/org/apache/harmony/javax/security/auth/login/AccountExpiredException.java
new file mode 100644
index 0000000..1e0ce4d
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/login/AccountExpiredException.java
@@ -0,0 +1,31 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login;
+
+public class AccountExpiredException extends AccountException {
+
+    private static final long serialVersionUID = -6064064890162661560L;
+
+    public AccountExpiredException() {
+        super();
+    }
+
+    public AccountExpiredException(String message) {
+        super(message);
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/login/AccountLockedException.java b/src/org/apache/harmony/javax/security/auth/login/AccountLockedException.java
new file mode 100644
index 0000000..47913a5
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/login/AccountLockedException.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login;
+
+public class AccountLockedException extends AccountException {
+
+    private static final long serialVersionUID = 8280345554014066334L;
+
+    public AccountLockedException() {
+        super();
+    }
+
+    public AccountLockedException(String message) {
+        super(message);
+    }
+
+}
diff --git a/src/org/apache/harmony/javax/security/auth/login/AccountNotFoundException.java b/src/org/apache/harmony/javax/security/auth/login/AccountNotFoundException.java
new file mode 100644
index 0000000..8ca9b96
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/login/AccountNotFoundException.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login;
+
+public class AccountNotFoundException extends AccountException {
+
+    private static final long serialVersionUID = 1498349563916294614L;
+
+    public AccountNotFoundException() {
+        super();
+    }
+
+    public AccountNotFoundException(String message) {
+        super(message);
+    }
+
+}
diff --git a/src/org/apache/harmony/javax/security/auth/login/AppConfigurationEntry.java b/src/org/apache/harmony/javax/security/auth/login/AppConfigurationEntry.java
new file mode 100644
index 0000000..2a735dc
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/login/AppConfigurationEntry.java
@@ -0,0 +1,95 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login;
+
+import java.util.Collections;
+import java.util.Map;
+
+
+
+public class AppConfigurationEntry {
+
+    // the login module options
+    private final Map<String, ?> options;
+
+    // the control flag
+    private final AppConfigurationEntry.LoginModuleControlFlag controlFlag;
+
+    // the login module name 
+    private final String loginModuleName;
+
+    public AppConfigurationEntry(String loginModuleName,
+            AppConfigurationEntry.LoginModuleControlFlag controlFlag, Map<String, ?> options) {
+
+        if (loginModuleName == null || loginModuleName.length() == 0) {
+            throw new IllegalArgumentException("auth.26"); //$NON-NLS-1$
+        }
+
+        if (controlFlag == null) {
+            throw new IllegalArgumentException("auth.27"); //$NON-NLS-1$
+        }
+
+        if (options == null) {
+            throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$
+        }
+
+        this.loginModuleName = loginModuleName;
+        this.controlFlag = controlFlag;
+        this.options = Collections.unmodifiableMap(options);
+    }
+
+    public String getLoginModuleName() {
+        return loginModuleName;
+    }
+
+    public LoginModuleControlFlag getControlFlag() {
+        return controlFlag;
+    }
+
+    public Map<java.lang.String, ?> getOptions() {
+        return options;
+    }
+
+    public static class LoginModuleControlFlag {
+
+        // the control flag
+        private final String flag;
+
+        public static final LoginModuleControlFlag REQUIRED = new LoginModuleControlFlag(
+                "LoginModuleControlFlag: required"); //$NON-NLS-1$
+
+        public static final LoginModuleControlFlag REQUISITE = new LoginModuleControlFlag(
+                "LoginModuleControlFlag: requisite"); //$NON-NLS-1$
+
+        public static final LoginModuleControlFlag OPTIONAL = new LoginModuleControlFlag(
+                "LoginModuleControlFlag: optional"); //$NON-NLS-1$
+
+        public static final LoginModuleControlFlag SUFFICIENT = new LoginModuleControlFlag(
+                "LoginModuleControlFlag: sufficient"); //$NON-NLS-1$
+
+        // Creates the LoginModuleControlFlag object with specified a flag
+        private LoginModuleControlFlag(String flag) {
+            this.flag = flag;
+        }
+
+        @Override
+        public String toString() {
+            return flag;
+        }
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/login/Configuration.java b/src/org/apache/harmony/javax/security/auth/login/Configuration.java
new file mode 100644
index 0000000..74c371f
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/login/Configuration.java
@@ -0,0 +1,102 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login;
+
+import java.security.AccessController;
+import org.apache.harmony.javax.security.auth.AuthPermission;
+
+public abstract class Configuration {
+
+    // the current configuration 
+    private static Configuration configuration;
+
+    // creates a AuthPermission object with a specify property
+    private static final AuthPermission GET_LOGIN_CONFIGURATION = new AuthPermission(
+            "getLoginConfiguration"); //$NON-NLS-1$
+
+    // creates a AuthPermission object with a specify property
+    private static final AuthPermission SET_LOGIN_CONFIGURATION = new AuthPermission(
+            "setLoginConfiguration"); //$NON-NLS-1$
+
+    // Key to security properties, defining default configuration provider.
+    private static final String LOGIN_CONFIGURATION_PROVIDER = "login.configuration.provider"; //$NON-NLS-1$
+
+    protected Configuration() {
+        super();
+    }
+
+    public static Configuration getConfiguration() {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(GET_LOGIN_CONFIGURATION);
+        }
+        return getAccessibleConfiguration();
+    }
+
+    /**
+     * Reads name of default configuration provider from security.properties,
+     * loads the class and instantiates the provider.<br> In case of any
+     * exception, wraps it with SecurityException and throws further.
+     */
+    private static final Configuration getDefaultProvider() {
+        return new Configuration() {
+			
+			@Override
+			public void refresh() {
+			}
+			
+			@Override
+			public AppConfigurationEntry[] getAppConfigurationEntry(
+					String applicationName) {
+				return new AppConfigurationEntry[0];
+			}
+		};
+    }
+
+    /**
+     * Shortcut accessor for friendly classes, to skip security checks.
+     * If active configuration was set to <code>null</code>, tries to load a default 
+     * provider, so this method never returns <code>null</code>. <br>
+     * This method is synchronized with setConfiguration()
+     */
+    static Configuration getAccessibleConfiguration() {
+        Configuration current = configuration;
+        if (current == null) {
+            synchronized (Configuration.class) {
+                if (configuration == null) {
+                    configuration = getDefaultProvider();
+                }
+                return configuration;
+            }
+        }
+        return current;
+    }
+
+    public static void setConfiguration(Configuration configuration) {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(SET_LOGIN_CONFIGURATION);
+        }
+        Configuration.configuration = configuration;
+    }
+
+    public abstract AppConfigurationEntry[] getAppConfigurationEntry(String applicationName);
+
+    public abstract void refresh();
+
+}
diff --git a/src/org/apache/harmony/javax/security/auth/login/CredentialException.java b/src/org/apache/harmony/javax/security/auth/login/CredentialException.java
new file mode 100644
index 0000000..e74e866
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/login/CredentialException.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login;
+
+public class CredentialException extends LoginException {
+
+    private static final long serialVersionUID = -4772893876810601859L;
+
+    public CredentialException() {
+        super();
+    }
+
+    public CredentialException(String message) {
+        super(message);
+    }
+
+}
diff --git a/src/org/apache/harmony/javax/security/auth/login/CredentialExpiredException.java b/src/org/apache/harmony/javax/security/auth/login/CredentialExpiredException.java
new file mode 100644
index 0000000..3ca3ad7
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/login/CredentialExpiredException.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login;
+
+public class CredentialExpiredException extends CredentialException {
+
+    private static final long serialVersionUID = -5344739593859737937L;
+
+    public CredentialExpiredException() {
+        super();
+    }
+
+    public CredentialExpiredException(String message) {
+        super(message);
+    }
+
+}
diff --git a/src/org/apache/harmony/javax/security/auth/login/CredentialNotFoundException.java b/src/org/apache/harmony/javax/security/auth/login/CredentialNotFoundException.java
new file mode 100644
index 0000000..ffd529f
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/login/CredentialNotFoundException.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login;
+
+public class CredentialNotFoundException extends CredentialException {
+
+    private static final long serialVersionUID = -7779934467214319475L;
+
+    public CredentialNotFoundException() {
+        super();
+    }
+
+    public CredentialNotFoundException(String message) {
+        super(message);
+    }
+
+}
diff --git a/src/org/apache/harmony/javax/security/auth/login/FailedLoginException.java b/src/org/apache/harmony/javax/security/auth/login/FailedLoginException.java
new file mode 100644
index 0000000..f689d99
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/login/FailedLoginException.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login;
+
+public class FailedLoginException extends LoginException {
+
+    private static final long serialVersionUID = 802556922354616286L;
+
+    public FailedLoginException() {
+        super();
+    }
+
+    public FailedLoginException(String message) {
+        super(message);
+    }
+
+}
\ No newline at end of file
diff --git a/src/org/apache/harmony/javax/security/auth/login/LoginContext.java b/src/org/apache/harmony/javax/security/auth/login/LoginContext.java
new file mode 100644
index 0000000..7d46278
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/login/LoginContext.java
@@ -0,0 +1,548 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login;
+
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.AccessControlContext;
+import java.security.PrivilegedExceptionAction;
+import java.security.PrivilegedActionException;
+
+import java.security.Security;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.harmony.javax.security.auth.Subject;
+import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
+import org.apache.harmony.javax.security.auth.callback.Callback;
+import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException;
+import org.apache.harmony.javax.security.auth.spi.LoginModule;
+import org.apache.harmony.javax.security.auth.AuthPermission;
+
+import org.apache.harmony.javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
+
+
+
+public class LoginContext {
+
+    private static final String DEFAULT_CALLBACK_HANDLER_PROPERTY = "auth.login.defaultCallbackHandler"; //$NON-NLS-1$
+
+    /*
+     * Integer constants which serve as a replacement for the corresponding
+     * LoginModuleControlFlag.* constants. These integers are used later as
+     * index in the arrays - see loginImpl() and logoutImpl() methods
+     */
+    private static final int OPTIONAL = 0;
+
+    private static final int REQUIRED = 1;
+
+    private static final int REQUISITE = 2;
+
+    private static final int SUFFICIENT = 3;
+
+    // Subject to be used for this LoginContext's operations
+    private Subject subject;
+
+    /*
+     * Shows whether the subject was specified by user (true) or was created by
+     * this LoginContext itself (false).
+     */
+    private boolean userProvidedSubject;
+
+    // Shows whether we use installed or user-provided Configuration
+    private boolean userProvidedConfig;
+
+    // An user's AccessControlContext, used when user specifies 
+    private AccessControlContext userContext;
+
+    /*
+     * Either a callback handler passed by the user or a wrapper for the user's
+     * specified handler - see init() below.
+     */
+    private CallbackHandler callbackHandler;
+
+    /*
+     * An array which keeps the instantiated and init()-ialized login modules
+     * and their states
+     */
+    private Module[] modules;
+
+    // Stores a shared state
+    private Map<String, ?> sharedState;
+
+    // A context class loader used to load [mainly] LoginModules
+    private ClassLoader contextClassLoader;
+
+    // Shows overall status - whether this LoginContext was successfully logged 
+    private boolean loggedIn;
+
+    public LoginContext(String name) throws LoginException {
+        super();
+        init(name, null, null, null);
+    }
+
+    public LoginContext(String name, CallbackHandler cbHandler) throws LoginException {
+        super();
+        if (cbHandler == null) {
+            throw new LoginException("auth.34"); //$NON-NLS-1$
+        }
+        init(name, null, cbHandler, null);
+    }
+
+    public LoginContext(String name, Subject subject) throws LoginException {
+        super();
+        if (subject == null) {
+            throw new LoginException("auth.03"); //$NON-NLS-1$
+        }
+        init(name, subject, null, null);
+    }
+
+    public LoginContext(String name, Subject subject, CallbackHandler cbHandler)
+            throws LoginException {
+        super();
+        if (subject == null) {
+            throw new LoginException("auth.03"); //$NON-NLS-1$
+        }
+        if (cbHandler == null) {
+            throw new LoginException("auth.34"); //$NON-NLS-1$
+        }
+        init(name, subject, cbHandler, null);
+    }
+
+    public LoginContext(String name, Subject subject, CallbackHandler cbHandler,
+            Configuration config) throws LoginException {
+        super();
+        init(name, subject, cbHandler, config);
+    }
+
+    // Does all the machinery needed for the initialization.
+    private void init(String name, Subject subject, final CallbackHandler cbHandler,
+            Configuration config) throws LoginException {
+        userProvidedSubject = (this.subject = subject) != null;
+
+        //
+        // Set config
+        //
+        if (name == null) {
+            throw new LoginException("auth.00"); //$NON-NLS-1$
+        }
+
+        if (config == null) {
+            config = Configuration.getAccessibleConfiguration();
+        } else {
+            userProvidedConfig = true;
+        }
+
+        SecurityManager sm = System.getSecurityManager();
+
+        if (sm != null && !userProvidedConfig) {
+            sm.checkPermission(new AuthPermission("createLoginContext." + name));//$NON-NLS-1$
+        }
+
+        AppConfigurationEntry[] entries = config.getAppConfigurationEntry(name);
+        if (entries == null) {
+            if (sm != null && !userProvidedConfig) {
+                sm.checkPermission(new AuthPermission("createLoginContext.other")); //$NON-NLS-1$
+            }
+            entries = config.getAppConfigurationEntry("other"); //$NON-NLS-1$
+            if (entries == null) {
+                throw new LoginException("auth.35 " + name); //$NON-NLS-1$
+            }
+        }
+
+        modules = new Module[entries.length];
+        for (int i = 0; i < modules.length; i++) {
+            modules[i] = new Module(entries[i]);
+        }
+        //
+        // Set CallbackHandler and this.contextClassLoader
+        //
+
+        /*
+         * as some of the operations to be executed (i.e. get*ClassLoader,
+         * getProperty, class loading) are security-checked, then combine all of
+         * them into a single doPrivileged() call.
+         */
+        try {
+            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
+                public Void run() throws Exception {
+                    // First, set the 'contextClassLoader'
+                    contextClassLoader = Thread.currentThread().getContextClassLoader();
+                    if (contextClassLoader == null) {
+                        contextClassLoader = ClassLoader.getSystemClassLoader();
+                    }
+                    // then, checks whether the cbHandler is set
+                    if (cbHandler == null) {
+                        // well, let's try to find it
+                        String klassName = Security
+                                .getProperty(DEFAULT_CALLBACK_HANDLER_PROPERTY);
+                        if (klassName == null || klassName.length() == 0) {
+                            return null;
+                        }
+                        Class<?> klass = Class.forName(klassName, true, contextClassLoader);
+                        callbackHandler = (CallbackHandler) klass.newInstance();
+                    } else {
+                        callbackHandler = cbHandler;
+                    }
+                    return null;
+                }
+            });
+        } catch (PrivilegedActionException ex) {
+            Throwable cause = ex.getCause();
+            throw (LoginException) new LoginException("auth.36").initCause(cause);//$NON-NLS-1$
+        }
+
+        if (userProvidedConfig) {
+            userContext = AccessController.getContext();
+        } else if (callbackHandler != null) {
+            userContext = AccessController.getContext();
+            callbackHandler = new ContextedCallbackHandler(callbackHandler);
+        }
+    }
+
+    public Subject getSubject() {
+        if (userProvidedSubject || loggedIn) {
+            return subject;
+        }
+        return null;
+    }
+
+    /**
+     * Warning: calling the method more than once may result in undefined
+     * behaviour if logout() method is not invoked before.
+     */
+    public void login() throws LoginException {
+        PrivilegedExceptionAction<Void> action = new PrivilegedExceptionAction<Void>() {
+            public Void run() throws LoginException {
+                loginImpl();
+                return null;
+            }
+        };
+        try {
+            if (userProvidedConfig) {
+                AccessController.doPrivileged(action, userContext);
+            } else {
+                AccessController.doPrivileged(action);
+            }
+        } catch (PrivilegedActionException ex) {
+            throw (LoginException) ex.getException();
+        }
+    }
+
+    /**
+     * The real implementation of login() method whose calls are wrapped into
+     * appropriate doPrivileged calls in login().
+     */
+    private void loginImpl() throws LoginException {
+        if (subject == null) {
+            subject = new Subject();
+        }
+
+        if (sharedState == null) {
+            sharedState = new HashMap<String, Object>();
+        }
+
+        // PHASE 1: Calling login()-s
+        Throwable firstProblem = null;
+
+        int[] logged = new int[4];
+        int[] total = new int[4];
+
+        for (Module module : modules) {
+            try {
+                // if a module fails during Class.forName(), then it breaks overall 
+                // attempt - see catch() below
+                module.create(subject, callbackHandler, sharedState);
+
+                if (module.module.login()) {
+                    ++total[module.getFlag()];
+                    ++logged[module.getFlag()];
+                    if (module.getFlag() == SUFFICIENT) {
+                        break;
+                    }
+                }
+            } catch (Throwable ex) {
+                if (firstProblem == null) {
+                    firstProblem = ex;
+                }
+                if (module.klass == null) {
+                    /*
+                     * an exception occurred during class lookup - overall
+                     * attempt must fail a little trick: increase the REQUIRED's
+                     * number - this will look like a failed REQUIRED module
+                     * later, so overall attempt will fail
+                     */
+                    ++total[REQUIRED];
+                    break;
+                }
+                ++total[module.getFlag()];
+                // something happened after the class was loaded
+                if (module.getFlag() == REQUISITE) {
+                    // ... and no need to walk down anymore
+                    break;
+                }
+            }
+        }
+        // end of PHASE1, 
+
+        // Let's decide whether we have either overall success or a total failure
+        boolean fail = true;
+
+        /*
+         * Note: 'failed[xxx]!=0' is not enough to check.
+         * 
+         * Use 'logged[xx] != total[xx]' instead. This is because some modules
+         * might not be counted as 'failed' if an exception occurred during
+         * preload()/Class.forName()-ing. But, such modules still get counted in
+         * the total[].
+         */
+
+        // if any REQ* module failed - then it's failure
+        if (logged[REQUIRED] != total[REQUIRED] || logged[REQUISITE] != total[REQUISITE]) {
+            // fail = true;
+        } else {
+            if (total[REQUIRED] == 0 && total[REQUISITE] == 0) {
+                // neither REQUIRED nor REQUISITE was configured.
+                // must have at least one SUFFICIENT or OPTIONAL
+                if (logged[OPTIONAL] != 0 || logged[SUFFICIENT] != 0) {
+                    fail = false;
+                }
+                //else { fail = true; }
+            } else {
+                fail = false;
+            }
+        }
+
+        int commited[] = new int[4];
+        // clear it
+        total[0] = total[1] = total[2] = total[3] = 0;
+        if (!fail) {
+            // PHASE 2: 
+
+            for (Module module : modules) {
+                if (module.klass != null) {
+                    ++total[module.getFlag()];
+                    try {
+                        module.module.commit();
+                        ++commited[module.getFlag()];
+                    } catch (Throwable ex) {
+                        if (firstProblem == null) {
+                            firstProblem = ex;
+                        }
+                    }
+                }
+            }
+        }
+
+        // need to decide once again
+        fail = true;
+        if (commited[REQUIRED] != total[REQUIRED] || commited[REQUISITE] != total[REQUISITE]) {
+            //fail = true;
+        } else {
+            if (total[REQUIRED] == 0 && total[REQUISITE] == 0) {
+                /*
+                 * neither REQUIRED nor REQUISITE was configured. must have at
+                 * least one SUFFICIENT or OPTIONAL
+                 */
+                if (commited[OPTIONAL] != 0 || commited[SUFFICIENT] != 0) {
+                    fail = false;
+                } else {
+                    //fail = true;
+                }
+            } else {
+                fail = false;
+            }
+        }
+
+        if (fail) {
+            // either login() or commit() failed. aborting...
+
+            for (Module module : modules) {
+                try {
+                    module.module.abort();
+                } catch ( /*LoginException*/Throwable ex) {
+                    if (firstProblem == null) {
+                        firstProblem = ex;
+                    }
+                }
+            }
+            if (firstProblem instanceof PrivilegedActionException
+                    && firstProblem.getCause() != null) {
+                firstProblem = firstProblem.getCause();
+            }
+            if (firstProblem instanceof LoginException) {
+                throw (LoginException) firstProblem;
+            }
+            throw (LoginException) new LoginException("auth.37").initCause(firstProblem); //$NON-NLS-1$
+        }
+        loggedIn = true;
+    }
+
+    public void logout() throws LoginException {
+        PrivilegedExceptionAction<Void> action = new PrivilegedExceptionAction<Void>() {
+            public Void run() throws LoginException {
+                logoutImpl();
+                return null;
+            }
+        };
+        try {
+            if (userProvidedConfig) {
+                AccessController.doPrivileged(action, userContext);
+            } else {
+                AccessController.doPrivileged(action);
+            }
+        } catch (PrivilegedActionException ex) {
+            throw (LoginException) ex.getException();
+        }
+    }
+
+    /**
+     * The real implementation of logout() method whose calls are wrapped into
+     * appropriate doPrivileged calls in logout().
+     */
+    private void logoutImpl() throws LoginException {
+        if (subject == null) {
+            throw new LoginException("auth.38"); //$NON-NLS-1$
+        }
+        loggedIn = false;
+        Throwable firstProblem = null;
+        int total = 0;
+        for (Module module : modules) {
+            try {
+                module.module.logout();
+                ++total;
+            } catch (Throwable ex) {
+                if (firstProblem == null) {
+                    firstProblem = ex;
+                }
+            }
+        }
+        if (firstProblem != null || total == 0) {
+            if (firstProblem instanceof PrivilegedActionException
+                    && firstProblem.getCause() != null) {
+                firstProblem = firstProblem.getCause();
+            }
+            if (firstProblem instanceof LoginException) {
+                throw (LoginException) firstProblem;
+            }
+            throw (LoginException) new LoginException("auth.37").initCause(firstProblem); //$NON-NLS-1$
+        }
+    }
+
+    /**
+     * <p>A class that servers as a wrapper for the CallbackHandler when we use
+     * installed Configuration, but not a passed one. See API docs on the
+     * LoginContext.</p>
+     * 
+     * <p>Simply invokes the given handler with the given AccessControlContext.</p>
+     */
+    private class ContextedCallbackHandler implements CallbackHandler {
+        private final CallbackHandler hiddenHandlerRef;
+
+        ContextedCallbackHandler(CallbackHandler handler) {
+            super();
+            this.hiddenHandlerRef = handler;
+        }
+
+        public void handle(final Callback[] callbacks) throws IOException,
+                UnsupportedCallbackException {
+            try {
+                AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
+                    public Void run() throws IOException, UnsupportedCallbackException {
+                        hiddenHandlerRef.handle(callbacks);
+                        return null;
+                    }
+                }, userContext);
+            } catch (PrivilegedActionException ex) {
+                if (ex.getCause() instanceof UnsupportedCallbackException) {
+                    throw (UnsupportedCallbackException) ex.getCause();
+                }
+                throw (IOException) ex.getCause();
+            }
+        }
+    }
+
+    /** 
+     * A private class that stores an instantiated LoginModule.
+     */
+    private final class Module {
+
+        // An initial info about the module to be used
+        AppConfigurationEntry entry;
+
+        // A mapping of LoginModuleControlFlag onto a simple int constant
+        int flag;
+
+        // The LoginModule itself 
+        LoginModule module;
+
+        // A class of the module
+        Class<?> klass;
+
+        Module(AppConfigurationEntry entry) {
+            this.entry = entry;
+            LoginModuleControlFlag flg = entry.getControlFlag();
+            if (flg == LoginModuleControlFlag.OPTIONAL) {
+                flag = OPTIONAL;
+            } else if (flg == LoginModuleControlFlag.REQUISITE) {
+                flag = REQUISITE;
+            } else if (flg == LoginModuleControlFlag.SUFFICIENT) {
+                flag = SUFFICIENT;
+            } else {
+                flag = REQUIRED;
+                //if(flg!=LoginModuleControlFlag.REQUIRED) throw new Error()
+            }
+        }
+
+        int getFlag() {
+            return flag;
+        }
+
+        /**
+         * Loads class of the LoginModule, instantiates it and then calls
+         * initialize().
+         */
+        void create(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState)
+                throws LoginException {
+            String klassName = entry.getLoginModuleName();
+            if (klass == null) {
+                try {
+                    klass = Class.forName(klassName, false, contextClassLoader);
+                } catch (ClassNotFoundException ex) {
+                    throw (LoginException) new LoginException(
+                            "auth.39 " + klassName).initCause(ex); //$NON-NLS-1$
+                }
+            }
+
+            if (module == null) {
+                try {
+                    module = (LoginModule) klass.newInstance();
+                } catch (IllegalAccessException ex) {
+                    throw (LoginException) new LoginException(
+                            "auth.3A " + klassName) //$NON-NLS-1$
+                            .initCause(ex);
+                } catch (InstantiationException ex) {
+                    throw (LoginException) new LoginException(
+                            "auth.3A" + klassName) //$NON-NLS-1$
+                            .initCause(ex);
+                }
+                module.initialize(subject, callbackHandler, sharedState, entry.getOptions());
+            }
+        }
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/auth/login/LoginException.java b/src/org/apache/harmony/javax/security/auth/login/LoginException.java
new file mode 100644
index 0000000..e9ea566
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/login/LoginException.java
@@ -0,0 +1,45 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.login;
+
+import java.security.GeneralSecurityException;
+
+/**
+ * Base class for exceptions that are thrown when a login error occurs.
+ */
+public class LoginException extends GeneralSecurityException {
+
+    private static final long serialVersionUID = -4679091624035232488L;
+
+    /**
+     * Creates a new exception instance and initializes it with default values.
+     */
+    public LoginException() {
+        super();
+    }
+
+    /**
+     * Creates a new exception instance and initializes it with a given message.
+     *
+     * @param message the error message
+     */
+    public LoginException(String message) {
+        super(message);
+    }
+
+}
diff --git a/src/org/apache/harmony/javax/security/auth/spi/LoginModule.java b/src/org/apache/harmony/javax/security/auth/spi/LoginModule.java
new file mode 100644
index 0000000..3ed9eb2
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/auth/spi/LoginModule.java
@@ -0,0 +1,38 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.auth.spi;
+
+import java.util.Map;
+
+import org.apache.harmony.javax.security.auth.Subject;
+import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
+import org.apache.harmony.javax.security.auth.login.LoginException;
+
+public interface LoginModule {
+
+    void initialize(Subject subject, CallbackHandler callbackHandler,
+            Map<String, ?> sharedState, Map<String, ?> options);
+
+    boolean login() throws LoginException;
+
+    boolean commit() throws LoginException;
+
+    boolean abort() throws LoginException;
+
+    boolean logout() throws LoginException;
+}
diff --git a/src/org/apache/harmony/javax/security/sasl/AuthenticationException.java b/src/org/apache/harmony/javax/security/sasl/AuthenticationException.java
new file mode 100644
index 0000000..38703ef
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/sasl/AuthenticationException.java
@@ -0,0 +1,35 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.sasl;
+
+public class AuthenticationException extends SaslException {
+
+    private static final long serialVersionUID = -3579708765071815007L;
+
+    public AuthenticationException() {
+        super();
+    }
+
+    public AuthenticationException(String detail) {
+        super(detail);
+    }
+
+    public AuthenticationException(String detail, Throwable ex) {
+        super(detail, ex);
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/sasl/AuthorizeCallback.java b/src/org/apache/harmony/javax/security/sasl/AuthorizeCallback.java
new file mode 100644
index 0000000..2ba90a2
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/sasl/AuthorizeCallback.java
@@ -0,0 +1,79 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.sasl;
+
+import java.io.Serializable;
+import org.apache.harmony.javax.security.auth.callback.Callback;
+
+public class AuthorizeCallback implements Callback, Serializable {
+
+    private static final long serialVersionUID = -2353344186490470805L;
+
+    /**
+     * Serialized field for storing authenticationID.
+     */
+    private final String authenticationID;
+
+    /**
+     * Serialized field for storing authorizationID.
+     */
+    private final String authorizationID;
+
+    /**
+     * Serialized field for storing authorizedID.
+     */
+    private String authorizedID;
+
+    /**
+     * Store authorized Serialized field.
+     */
+    private boolean authorized;
+
+    public AuthorizeCallback(String authnID, String authzID) {
+        super();
+        authenticationID = authnID;
+        authorizationID = authzID;
+        authorizedID = authzID;
+    }
+
+    public String getAuthenticationID() {
+        return authenticationID;
+    }
+
+    public String getAuthorizationID() {
+        return authorizationID;
+    }
+
+    public String getAuthorizedID() {
+        return (authorized ? authorizedID : null);
+    }
+
+    public boolean isAuthorized() {
+        return authorized;
+    }
+
+    public void setAuthorized(boolean ok) {
+        authorized = ok;
+    }
+
+    public void setAuthorizedID(String id) {
+        if (id != null) {
+            authorizedID = id;
+        }
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/sasl/RealmCallback.java b/src/org/apache/harmony/javax/security/sasl/RealmCallback.java
new file mode 100644
index 0000000..65b5d15
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/sasl/RealmCallback.java
@@ -0,0 +1,33 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.sasl;
+
+import org.apache.harmony.javax.security.auth.callback.TextInputCallback;
+
+public class RealmCallback extends TextInputCallback {
+
+    private static final long serialVersionUID = -4342673378785456908L;
+
+    public RealmCallback(String prompt) {
+        super(prompt);
+    }
+
+    public RealmCallback(String prompt, String defaultRealmInfo) {
+        super(prompt, defaultRealmInfo);
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/sasl/RealmChoiceCallback.java b/src/org/apache/harmony/javax/security/sasl/RealmChoiceCallback.java
new file mode 100644
index 0000000..079ea07
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/sasl/RealmChoiceCallback.java
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.sasl;
+
+import org.apache.harmony.javax.security.auth.callback.ChoiceCallback;
+
+public class RealmChoiceCallback extends ChoiceCallback {
+
+    private static final long serialVersionUID = -8588141348846281332L;
+
+    public RealmChoiceCallback(String prompt, String[] choices, int defaultChoice,
+            boolean multiple) {
+        super(prompt, choices, defaultChoice, multiple);
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/sasl/Sasl.java b/src/org/apache/harmony/javax/security/sasl/Sasl.java
new file mode 100644
index 0000000..4d827f8
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/sasl/Sasl.java
@@ -0,0 +1,204 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.sasl;
+
+import java.security.Provider;
+import java.security.Security;
+import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
+
+
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.HashSet;
+import java.util.Iterator;
+
+public class Sasl {
+    // SaslClientFactory service name
+    private static final String CLIENTFACTORYSRV = "SaslClientFactory"; //$NON-NLS-1$
+
+    // SaslServerFactory service name
+    private static final String SERVERFACTORYSRV = "SaslServerFactory"; //$NON-NLS-1$
+
+    public static final String POLICY_NOPLAINTEXT = "javax.security.sasl.policy.noplaintext"; //$NON-NLS-1$
+
+    public static final String POLICY_NOACTIVE = "javax.security.sasl.policy.noactive"; //$NON-NLS-1$
+
+    public static final String POLICY_NODICTIONARY = "javax.security.sasl.policy.nodictionary"; //$NON-NLS-1$
+
+    public static final String POLICY_NOANONYMOUS = "javax.security.sasl.policy.noanonymous"; //$NON-NLS-1$
+
+    public static final String POLICY_FORWARD_SECRECY = "javax.security.sasl.policy.forward"; //$NON-NLS-1$
+
+    public static final String POLICY_PASS_CREDENTIALS = "javax.security.sasl.policy.credentials"; //$NON-NLS-1$
+
+    public static final String MAX_BUFFER = "javax.security.sasl.maxbuffer"; //$NON-NLS-1$
+
+    public static final String RAW_SEND_SIZE = "javax.security.sasl.rawsendsize"; //$NON-NLS-1$
+
+    public static final String REUSE = "javax.security.sasl.reuse"; //$NON-NLS-1$
+
+    public static final String QOP = "javax.security.sasl.qop"; //$NON-NLS-1$
+
+    public static final String STRENGTH = "javax.security.sasl.strength"; //$NON-NLS-1$
+
+    public static final String SERVER_AUTH = "javax.security.sasl.server.authentication"; //$NON-NLS-1$
+
+    // Default public constructor is overridden
+    private Sasl() {
+        super();
+    }
+
+    // Forms new instance of factory
+    private static Object newInstance(String factoryName, Provider prv) throws SaslException {
+        String msg = "auth.31"; //$NON-NLS-1$
+        Object factory;
+        ClassLoader cl = prv.getClass().getClassLoader();
+        if (cl == null) {
+            cl = ClassLoader.getSystemClassLoader();
+        }
+        try {
+            factory = (Class.forName(factoryName, true, cl)).newInstance();
+            return factory;
+        } catch (IllegalAccessException e) {
+            throw new SaslException(msg + factoryName, e);
+        } catch (ClassNotFoundException e) {
+            throw new SaslException(msg + factoryName, e);
+        } catch (InstantiationException e) {
+            throw new SaslException(msg + factoryName, e);
+        }
+    }
+
+    /**
+     * This method forms the list of SaslClient/SaslServer factories which are
+     * implemented in used providers
+     */
+    private static Collection<?> findFactories(String service) {
+        HashSet<Object> fact = new HashSet<Object>();
+        Provider[] pp = Security.getProviders();
+        if ((pp == null) || (pp.length == 0)) {
+            return fact;
+        }
+        HashSet<String> props = new HashSet<String>();
+        for (int i = 0; i < pp.length; i++) {
+            String prName = pp[i].getName();
+            Enumeration<Object> keys = pp[i].keys();
+            while (keys.hasMoreElements()) {
+                String s = (String) keys.nextElement();
+                if (s.startsWith(service)) {
+                    String prop = pp[i].getProperty(s);
+                    try {
+                        if (props.add(prName.concat(prop))) {
+                            fact.add(newInstance(prop, pp[i]));
+                        }
+                    } catch (SaslException e) {
+                        // ignore this factory
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+        return fact;
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Enumeration<SaslClientFactory> getSaslClientFactories() {
+        Collection<SaslClientFactory> res = (Collection<SaslClientFactory>) findFactories(CLIENTFACTORYSRV);
+        return Collections.enumeration(res);
+
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Enumeration<SaslServerFactory> getSaslServerFactories() {
+        Collection<SaslServerFactory> res = (Collection<SaslServerFactory>) findFactories(SERVERFACTORYSRV);
+        return Collections.enumeration(res);
+    }
+
+    public static SaslServer createSaslServer(String mechanism, String protocol,
+            String serverName, Map<String, ?> prop, CallbackHandler cbh) throws SaslException {
+        if (mechanism == null) {
+            throw new NullPointerException("auth.32"); //$NON-NLS-1$
+        }
+        Collection<?> res = findFactories(SERVERFACTORYSRV);
+        if (res.isEmpty()) {
+            return null;
+        }
+
+        Iterator<?> iter = res.iterator();
+        while (iter.hasNext()) {
+            SaslServerFactory fact = (SaslServerFactory) iter.next();
+            String[] mech = fact.getMechanismNames(null);
+            boolean is = false;
+            if (mech != null) {
+                for (int j = 0; j < mech.length; j++) {
+                    if (mech[j].equals(mechanism)) {
+                        is = true;
+                        break;
+                    }
+                }
+            }
+            if (is) {
+                SaslServer saslS = fact.createSaslServer(mechanism, protocol, serverName, prop,
+                        cbh);
+                if (saslS != null) {
+                    return saslS;
+                }
+            }
+        }
+        return null;
+    }
+
+    public static SaslClient createSaslClient(String[] mechanisms, String authanticationID,
+            String protocol, String serverName, Map<String, ?> prop, CallbackHandler cbh)
+            throws SaslException {
+        if (mechanisms == null) {
+            throw new NullPointerException("auth.33"); //$NON-NLS-1$
+        }
+        Collection<?> res = findFactories(CLIENTFACTORYSRV);
+        if (res.isEmpty()) {
+            return null;
+        }
+
+        Iterator<?> iter = res.iterator();
+        while (iter.hasNext()) {
+            SaslClientFactory fact = (SaslClientFactory) iter.next();
+            String[] mech = fact.getMechanismNames(null);
+            boolean is = false;
+            if (mech != null) {
+                for (int j = 0; j < mech.length; j++) {
+                    for (int n = 0; n < mechanisms.length; n++) {
+                        if (mech[j].equals(mechanisms[n])) {
+                            is = true;
+                            break;
+                        }
+                    }
+                }
+            }
+            if (is) {
+                SaslClient saslC = fact.createSaslClient(mechanisms, authanticationID,
+                        protocol, serverName, prop, cbh);
+                if (saslC != null) {
+                    return saslC;
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/sasl/SaslClient.java b/src/org/apache/harmony/javax/security/sasl/SaslClient.java
new file mode 100644
index 0000000..e07ff53
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/sasl/SaslClient.java
@@ -0,0 +1,37 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.sasl;
+
+public interface SaslClient {
+
+    void dispose() throws SaslException;
+
+    byte[] evaluateChallenge(byte[] challenge) throws SaslException;
+
+    String getMechanismName();
+
+    Object getNegotiatedProperty(String propName);
+
+    boolean hasInitialResponse();
+
+    boolean isComplete();
+
+    byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException;
+
+    byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException;
+}
diff --git a/src/org/apache/harmony/javax/security/sasl/SaslClientFactory.java b/src/org/apache/harmony/javax/security/sasl/SaslClientFactory.java
new file mode 100644
index 0000000..e567ed3
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/sasl/SaslClientFactory.java
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.sasl;
+
+import java.util.Map;
+import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
+
+public interface SaslClientFactory {
+
+    SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol,
+            String serverName, Map<String, ?> props, CallbackHandler cbh) throws SaslException;
+
+    String[] getMechanismNames(Map<String, ?> props);
+
+}
diff --git a/src/org/apache/harmony/javax/security/sasl/SaslException.java b/src/org/apache/harmony/javax/security/sasl/SaslException.java
new file mode 100644
index 0000000..1ab7b12
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/sasl/SaslException.java
@@ -0,0 +1,69 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.sasl;
+
+import java.io.IOException;
+
+public class SaslException extends IOException {
+
+    private static final long serialVersionUID = 4579784287983423626L;
+
+    /**
+     * Serialized field for storing initial cause
+     */
+    private Throwable _exception;
+
+    public SaslException() {
+        super();
+    }
+
+    public SaslException(String detail) {
+        super(detail);
+    }
+
+    public SaslException(String detail, Throwable ex) {
+        super(detail);
+        if (ex != null) {
+            super.initCause(ex);
+            _exception = ex;
+        }
+    }
+
+    @Override
+    public Throwable getCause() {
+        return _exception;
+    }
+
+    @Override
+    public Throwable initCause(Throwable cause) {
+        super.initCause(cause);
+        _exception = cause;
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        if (_exception == null) {
+            return super.toString();
+        }
+        StringBuilder sb = new StringBuilder(super.toString());
+        sb.append(", caused by: "); //$NON-NLS-1$
+        sb.append(_exception.toString());
+        return sb.toString();
+    }
+}
diff --git a/src/org/apache/harmony/javax/security/sasl/SaslServer.java b/src/org/apache/harmony/javax/security/sasl/SaslServer.java
new file mode 100644
index 0000000..f057a4b
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/sasl/SaslServer.java
@@ -0,0 +1,37 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.sasl;
+
+public interface SaslServer {
+
+    void dispose() throws SaslException;
+
+    byte[] evaluateResponse(byte[] response) throws SaslException;
+
+    String getAuthorizationID();
+
+    String getMechanismName();
+
+    Object getNegotiatedProperty(String propName);
+
+    boolean isComplete();
+
+    byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException;
+
+    byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException;
+}
diff --git a/src/org/apache/harmony/javax/security/sasl/SaslServerFactory.java b/src/org/apache/harmony/javax/security/sasl/SaslServerFactory.java
new file mode 100644
index 0000000..d59530e
--- /dev/null
+++ b/src/org/apache/harmony/javax/security/sasl/SaslServerFactory.java
@@ -0,0 +1,30 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.apache.harmony.javax.security.sasl;
+
+import java.util.Map;
+import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
+
+public interface SaslServerFactory {
+
+    SaslServer createSaslServer(String mechanisms, String protocol, String serverName,
+            Map<String, ?> props, CallbackHandler cbh) throws SaslException;
+
+    String[] getMechanismNames(Map<String, ?> props);
+
+}
diff --git a/src/org/apache/qpid/management/common/sasl/CRAMMD5HashedSaslClientFactory.java b/src/org/apache/qpid/management/common/sasl/CRAMMD5HashedSaslClientFactory.java
new file mode 100644
index 0000000..5c33e40
--- /dev/null
+++ b/src/org/apache/qpid/management/common/sasl/CRAMMD5HashedSaslClientFactory.java
@@ -0,0 +1,59 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.management.common.sasl;
+
+import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
+import de.measite.smack.Sasl;
+import org.apache.harmony.javax.security.sasl.SaslClient;
+import org.apache.harmony.javax.security.sasl.SaslClientFactory;
+import org.apache.harmony.javax.security.sasl.SaslException;
+import java.util.Map;
+
+public class CRAMMD5HashedSaslClientFactory implements SaslClientFactory
+{
+    /** The name of this mechanism */
+    public static final String MECHANISM = "CRAM-MD5-HASHED";
+
+    public SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol,
+                                       String serverName, Map<String, ?> props, CallbackHandler cbh)
+    throws SaslException
+    {
+        for (int i = 0; i < mechanisms.length; i++)
+        {
+            if (mechanisms[i].equals(MECHANISM))
+            {
+                if (cbh == null)
+                {
+                    throw new SaslException("CallbackHandler must not be null");
+                }
+
+                String[] mechs = {"CRAM-MD5"};
+                return Sasl.createSaslClient(mechs, authorizationId, protocol, serverName, props, cbh);
+            }
+        }
+        return null;
+    }
+
+    public String[] getMechanismNames(Map props)
+    { 
+        return new String[]{MECHANISM};
+    }
+}
diff --git a/src/org/apache/qpid/management/common/sasl/ClientSaslFactory.java b/src/org/apache/qpid/management/common/sasl/ClientSaslFactory.java
new file mode 100644
index 0000000..19162d8
--- /dev/null
+++ b/src/org/apache/qpid/management/common/sasl/ClientSaslFactory.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.management.common.sasl;
+
+import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
+import org.apache.harmony.javax.security.sasl.SaslClient;
+import org.apache.harmony.javax.security.sasl.SaslClientFactory;
+import org.apache.harmony.javax.security.sasl.SaslException;
+import java.util.Map;
+
+public class ClientSaslFactory implements SaslClientFactory
+{
+    public SaslClient createSaslClient(String[] mechs, String authorizationId, String protocol,
+                                       String serverName, Map props, CallbackHandler cbh)
+    throws SaslException 
+    {
+        for (int i = 0; i < mechs.length; i++)
+        {
+            if (mechs[i].equals("PLAIN"))
+            {
+                return new PlainSaslClient(authorizationId, cbh);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Simple-minded implementation that ignores props
+     */
+    public String[] getMechanismNames(Map props)
+    {
+        return new String[]{"PLAIN"};
+    }
+
+}
diff --git a/src/org/apache/qpid/management/common/sasl/Constants.java b/src/org/apache/qpid/management/common/sasl/Constants.java
new file mode 100644
index 0000000..31010ba
--- /dev/null
+++ b/src/org/apache/qpid/management/common/sasl/Constants.java
@@ -0,0 +1,33 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.management.common.sasl;
+
+public class Constants
+{
+
+    public final static String MECH_CRAMMD5 = "CRAM-MD5";
+    public final static String MECH_PLAIN = "PLAIN";
+    public final static String SASL_CRAMMD5 = "SASL/CRAM-MD5";
+    public final static String SASL_PLAIN = "SASL/PLAIN";
+
+}
+
diff --git a/src/org/apache/qpid/management/common/sasl/JCAProvider.java b/src/org/apache/qpid/management/common/sasl/JCAProvider.java
new file mode 100644
index 0000000..5793dae
--- /dev/null
+++ b/src/org/apache/qpid/management/common/sasl/JCAProvider.java
@@ -0,0 +1,55 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.management.common.sasl;
+
+import org.apache.harmony.javax.security.sasl.SaslClientFactory;
+import java.security.Provider;
+import java.util.Map;
+
+public class JCAProvider extends Provider
+{
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Creates the security provider with a map from SASL mechanisms to implementing factories.
+     *
+     * @param providerMap The map from SASL mechanims to implementing factory classes.
+     */
+    public JCAProvider(Map<String, Class<? extends SaslClientFactory>> providerMap)
+    {
+        super("AMQSASLProvider", 1.0, "A JCA provider that registers all "
+              + "AMQ SASL providers that want to be registered");
+        register(providerMap);
+    }
+
+    /**
+     * Registers client factory classes for a map of mechanism names to client factory classes.
+     *
+     * @param providerMap The map from SASL mechanims to implementing factory classes.
+     */
+    private void register(Map<String, Class<? extends SaslClientFactory>> providerMap)
+    {
+        for (Map.Entry<String, Class<? extends SaslClientFactory>> me : providerMap.entrySet())
+        {
+            put("SaslClientFactory." + me.getKey(), me.getValue().getName());
+        }
+    }
+}
diff --git a/src/org/apache/qpid/management/common/sasl/PlainSaslClient.java b/src/org/apache/qpid/management/common/sasl/PlainSaslClient.java
new file mode 100644
index 0000000..99a1d43
--- /dev/null
+++ b/src/org/apache/qpid/management/common/sasl/PlainSaslClient.java
@@ -0,0 +1,210 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.management.common.sasl;
+
+import org.apache.harmony.javax.security.auth.callback.Callback;
+import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
+import org.apache.harmony.javax.security.auth.callback.NameCallback;
+import org.apache.harmony.javax.security.auth.callback.PasswordCallback;
+import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException;
+import de.measite.smack.Sasl;
+import org.apache.harmony.javax.security.sasl.SaslClient;
+import org.apache.harmony.javax.security.sasl.SaslException;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+public class PlainSaslClient implements SaslClient
+{
+
+    private boolean completed;
+    private CallbackHandler cbh;
+    private String authorizationID;
+    private String authenticationID;
+    private byte password[];
+    private static byte SEPARATOR = 0;
+    
+    public PlainSaslClient(String authorizationID, CallbackHandler cbh) throws SaslException
+    {
+        completed = false;
+        this.cbh = cbh;
+        Object[] userInfo = getUserInfo();
+        this.authorizationID = authorizationID;
+        this.authenticationID = (String) userInfo[0];
+        this.password = (byte[]) userInfo[1];
+        if (authenticationID == null || password == null)
+        {
+            throw new SaslException("PLAIN: authenticationID and password must be specified");
+        }
+    }
+
+    public byte[] evaluateChallenge(byte[] challenge) throws SaslException
+    {
+        if (completed)
+        {
+            throw new IllegalStateException("PLAIN: authentication already " +
+            "completed");
+        }
+        completed = true;
+        try 
+        {
+            byte authzid[] =
+                authorizationID == null ? null : authorizationID.getBytes("UTF8");
+            byte authnid[] = authenticationID.getBytes("UTF8");
+            byte response[] =
+                new byte[
+                         password.length +
+                         authnid.length +
+                         2 + // SEPARATOR
+                         (authzid != null ? authzid.length : 0)
+                         ];
+            int size = 0;
+            if (authzid != null) {
+                System.arraycopy(authzid, 0, response, 0, authzid.length);
+                size = authzid.length;
+            }
+            response[size++] = SEPARATOR;
+            System.arraycopy(authnid, 0, response, size, authnid.length);
+            size += authnid.length;
+            response[size++] = SEPARATOR;
+            System.arraycopy(password, 0, response, size, password.length);
+            clearPassword();
+            return response;
+        } catch (UnsupportedEncodingException e) {
+            throw new SaslException("PLAIN: Cannot get UTF-8 encoding of ids",
+                    e);
+        }
+    }
+
+    public String getMechanismName()
+    {
+        return "PLAIN";
+    }
+
+    public boolean hasInitialResponse()
+    {
+        return true;
+    }
+
+    public boolean isComplete()
+    {
+        return completed;
+    }
+
+    public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
+    {
+        if (completed) {
+            throw new IllegalStateException("PLAIN: this mechanism supports " +
+            "neither integrity nor privacy");
+        } else {
+            throw new IllegalStateException("PLAIN: authentication not " +
+            "completed");
+        }
+    }
+
+    public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
+    {
+        if (completed)
+        {
+            throw new IllegalStateException("PLAIN: this mechanism supports " +
+            "neither integrity nor privacy");
+        }
+        else
+        {
+            throw new IllegalStateException("PLAIN: authentication not " +
+            "completed");
+        }
+    }
+
+    public Object getNegotiatedProperty(String propName)
+    {
+        if (completed)
+        {
+            if (propName.equals(Sasl.QOP))
+            {
+                return "auth";
+            }
+            else
+            {
+                return null;
+            }
+        }
+        else 
+        {
+            throw new IllegalStateException("PLAIN: authentication not " +
+            "completed");
+        }
+    }
+
+    private void clearPassword()
+    {
+        if (password != null)
+        {
+            for (int i = 0 ; i < password.length ; i++)
+            {
+                password[i] = 0;
+            }
+            password = null;
+        }
+    }
+
+    public void dispose() throws SaslException
+    {
+        clearPassword();
+    }
+
+    protected void finalize()
+    {
+        clearPassword();
+    }
+
+    private Object[] getUserInfo() throws SaslException
+    {
+        try
+        {
+            final String userPrompt = "PLAIN authentication id: ";
+            final String pwPrompt = "PLAIN password: ";
+            NameCallback nameCb = new NameCallback(userPrompt);
+            PasswordCallback passwordCb = new PasswordCallback(pwPrompt, false);
+            cbh.handle(new Callback[] { nameCb, passwordCb });
+            String userid = nameCb.getName();
+            char pwchars[] = passwordCb.getPassword();
+            byte pwbytes[];
+            if (pwchars != null)
+            {
+                pwbytes = (new String(pwchars)).getBytes("UTF8");
+                passwordCb.clearPassword();
+            }
+            else 
+            {
+                pwbytes = null;
+            }
+            return (new Object[] { userid, pwbytes });
+        } 
+        catch (IOException e)
+        {
+            throw new SaslException("Cannot get password", e);
+        } 
+        catch (UnsupportedCallbackException e)
+        {
+            throw new SaslException("Cannot get userid/password", e);
+        }
+    }
+}
diff --git a/src/org/apache/qpid/management/common/sasl/SaslProvider.java b/src/org/apache/qpid/management/common/sasl/SaslProvider.java
new file mode 100644
index 0000000..1eb44e3
--- /dev/null
+++ b/src/org/apache/qpid/management/common/sasl/SaslProvider.java
@@ -0,0 +1,35 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.management.common.sasl;
+
+import java.security.Provider;
+
+public class SaslProvider extends Provider
+{
+    private static final long serialVersionUID = -6978096016899676466L;
+
+    public SaslProvider()
+    {
+        super("SaslClientFactory", 1.0, "SASL PLAIN CLIENT MECHANISM");
+        put("SaslClientFactory.PLAIN", "ClientSaslFactory");
+    }
+
+}
diff --git a/src/org/apache/qpid/management/common/sasl/UserPasswordCallbackHandler.java b/src/org/apache/qpid/management/common/sasl/UserPasswordCallbackHandler.java
new file mode 100644
index 0000000..a7886cf
--- /dev/null
+++ b/src/org/apache/qpid/management/common/sasl/UserPasswordCallbackHandler.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.management.common.sasl;
+
+import org.apache.harmony.javax.security.auth.callback.Callback;
+import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
+import org.apache.harmony.javax.security.auth.callback.NameCallback;
+import org.apache.harmony.javax.security.auth.callback.PasswordCallback;
+import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException;
+import java.io.IOException;
+
+public class UserPasswordCallbackHandler implements CallbackHandler
+{
+    private String user;
+    private char[] pwchars;
+    
+    public UserPasswordCallbackHandler(String user, String password)
+    {
+        this.user = user;
+        this.pwchars = password.toCharArray();
+    }
+
+    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
+    {
+        for (int i = 0; i < callbacks.length; i++)
+        {
+            if (callbacks[i] instanceof NameCallback)
+            {
+                NameCallback ncb = (NameCallback) callbacks[i];
+                ncb.setName(user);
+            } 
+            else if (callbacks[i] instanceof PasswordCallback)
+            {
+                PasswordCallback pcb = (PasswordCallback) callbacks[i];
+                pcb.setPassword(pwchars);
+            } 
+            else
+            {
+                throw new UnsupportedCallbackException(callbacks[i]);
+            }
+        }
+    }
+
+    private void clearPassword()
+    {
+        if (pwchars != null) 
+        {
+            for (int i = 0 ; i < pwchars.length ; i++)
+            {
+                pwchars[i] = 0;
+            }
+            pwchars = null;
+        }
+    }
+
+    protected void finalize()
+    {
+        clearPassword();
+    }
+}
diff --git a/src/org/apache/qpid/management/common/sasl/UsernameHashedPasswordCallbackHandler.java b/src/org/apache/qpid/management/common/sasl/UsernameHashedPasswordCallbackHandler.java
new file mode 100644
index 0000000..54d7374
--- /dev/null
+++ b/src/org/apache/qpid/management/common/sasl/UsernameHashedPasswordCallbackHandler.java
@@ -0,0 +1,107 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.qpid.management.common.sasl;
+
+import org.apache.harmony.javax.security.auth.callback.Callback;
+import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
+import org.apache.harmony.javax.security.auth.callback.NameCallback;
+import org.apache.harmony.javax.security.auth.callback.PasswordCallback;
+import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+
+public class UsernameHashedPasswordCallbackHandler implements CallbackHandler
+{
+    private String user;
+    private char[] pwchars;
+    
+    public UsernameHashedPasswordCallbackHandler(String user, String password) throws Exception
+    {
+        this.user = user;
+        this.pwchars = getHash(password);
+    }
+
+    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
+    {
+        for (int i = 0; i < callbacks.length; i++)
+        {
+            if (callbacks[i] instanceof NameCallback)
+            {
+                NameCallback ncb = (NameCallback) callbacks[i];
+                ncb.setName(user);
+            } 
+            else if (callbacks[i] instanceof PasswordCallback)
+            {
+                PasswordCallback pcb = (PasswordCallback) callbacks[i];
+                pcb.setPassword(pwchars);
+            } 
+            else
+            {
+                throw new UnsupportedCallbackException(callbacks[i]);
+            }
+        }
+    }
+
+    
+    private void clearPassword()
+    {
+        if (pwchars != null) 
+        {
+            for (int i = 0 ; i < pwchars.length ; i++)
+            {
+                pwchars[i] = 0;
+            }
+            pwchars = null;
+        }
+    }
+
+    protected void finalize()
+    {
+        clearPassword();
+    }
+    
+    public static char[] getHash(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException
+    {
+        byte[] data = text.getBytes("utf-8");
+
+        MessageDigest md = MessageDigest.getInstance("MD5");
+
+        for (byte b : data)
+        {
+            md.update(b);
+        }
+
+        byte[] digest = md.digest();
+
+        char[] hash = new char[digest.length ];
+
+        int index = 0;
+        for (byte b : digest)
+        {            
+            hash[index++] = (char) b;
+        }
+
+        return hash;
+    }
+}
diff --git a/src/org/jivesoftware/smack/AbstractConnectionListener.java b/src/org/jivesoftware/smack/AbstractConnectionListener.java
new file mode 100644
index 0000000..69acf90
--- /dev/null
+++ b/src/org/jivesoftware/smack/AbstractConnectionListener.java
@@ -0,0 +1,46 @@
+/**

+ * 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;

+

+/**

+ * The AbstractConnectionListener class provides an empty implementation for all

+ * methods defined by the {@link ConnectionListener} interface. This is a

+ * convenience class which should be used in case you do not need to implement

+ * all methods.

+ * 

+ * @author Henning Staib

+ */

+public class AbstractConnectionListener implements ConnectionListener {

+

+    public void connectionClosed() {

+        // do nothing

+    }

+

+    public void connectionClosedOnError(Exception e) {

+        // do nothing

+    }

+

+    public void reconnectingIn(int seconds) {

+        // do nothing

+    }

+

+    public void reconnectionFailed(Exception e) {

+        // do nothing

+    }

+

+    public void reconnectionSuccessful() {

+        // do nothing

+    }

+

+}

diff --git a/src/org/jivesoftware/smack/AccountManager.java b/src/org/jivesoftware/smack/AccountManager.java
new file mode 100644
index 0000000..4d9faa5
--- /dev/null
+++ b/src/org/jivesoftware/smack/AccountManager.java
@@ -0,0 +1,337 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * 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.AndFilter;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.PacketIDFilter;
+import org.jivesoftware.smack.filter.PacketTypeFilter;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Registration;
+import org.jivesoftware.smack.util.StringUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Allows creation and management of accounts on an XMPP server.
+ *
+ * @see Connection#getAccountManager()
+ * @author Matt Tucker
+ */
+public class AccountManager {
+
+    private Connection connection;
+    private Registration info = null;
+
+    /**
+     * Flag that indicates whether the server supports In-Band Registration.
+     * In-Band Registration may be advertised as a stream feature. If no stream feature
+     * was advertised from the server then try sending an IQ packet to discover if In-Band
+     * Registration is available.
+     */
+    private boolean accountCreationSupported = false;
+
+    /**
+     * Creates a new AccountManager instance.
+     *
+     * @param connection a connection to a XMPP server.
+     */
+    public AccountManager(Connection connection) {
+        this.connection = connection;
+    }
+
+    /**
+     * Sets whether the server supports In-Band Registration. In-Band Registration may be
+     * advertised as a stream feature. If no stream feature was advertised from the server
+     * then try sending an IQ packet to discover if In-Band Registration is available.
+     *
+     * @param accountCreationSupported true if the server supports In-Band Registration.
+     */
+    void setSupportsAccountCreation(boolean accountCreationSupported) {
+        this.accountCreationSupported = accountCreationSupported;
+    }
+
+    /**
+     * Returns true if the server supports creating new accounts. Many servers require
+     * that you not be currently authenticated when creating new accounts, so the safest
+     * behavior is to only create new accounts before having logged in to a server.
+     *
+     * @return true if the server support creating new accounts.
+     */
+    public boolean supportsAccountCreation() {
+        // Check if we already know that the server supports creating new accounts
+        if (accountCreationSupported) {
+            return true;
+        }
+        // No information is known yet (e.g. no stream feature was received from the server
+        // indicating that it supports creating new accounts) so send an IQ packet as a way
+        // to discover if this feature is supported
+        try {
+            if (info == null) {
+                getRegistrationInfo();
+                accountCreationSupported = info.getType() != IQ.Type.ERROR;
+            }
+            return accountCreationSupported;
+        }
+        catch (XMPPException xe) {
+            return false;
+        }
+    }
+
+    /**
+     * Returns an unmodifiable collection of the names of the required account attributes.
+     * All attributes must be set when creating new accounts. The standard set of possible
+     * attributes are as follows: <ul>
+     *      <li>name -- the user's name.
+     *      <li>first -- the user's first name.
+     *      <li>last -- the user's last name.
+     *      <li>email -- the user's email address.
+     *      <li>city -- the user's city.
+     *      <li>state -- the user's state.
+     *      <li>zip -- the user's ZIP code.
+     *      <li>phone -- the user's phone number.
+     *      <li>url -- the user's website.
+     *      <li>date -- the date the registration took place.
+     *      <li>misc -- other miscellaneous information to associate with the account.
+     *      <li>text -- textual information to associate with the account.
+     *      <li>remove -- empty flag to remove account.
+     * </ul><p>
+     *
+     * Typically, servers require no attributes when creating new accounts, or just
+     * the user's email address.
+     *
+     * @return the required account attributes.
+     */
+    public Collection<String> getAccountAttributes() {
+        try {
+            if (info == null) {
+                getRegistrationInfo();
+            }
+            Map<String, String> attributes = info.getAttributes();
+            if (attributes != null) {
+                return Collections.unmodifiableSet(attributes.keySet());
+            }
+        }
+        catch (XMPPException xe) {
+            xe.printStackTrace();
+        }
+        return Collections.emptySet();
+    }
+
+    /**
+     * Returns the value of a given account attribute or <tt>null</tt> if the account
+     * attribute wasn't found.
+     *
+     * @param name the name of the account attribute to return its value.
+     * @return the value of the account attribute or <tt>null</tt> if an account
+     * attribute wasn't found for the requested name.
+     */
+    public String getAccountAttribute(String name) {
+        try {
+            if (info == null) {
+                getRegistrationInfo();
+            }
+            return info.getAttributes().get(name);
+        }
+        catch (XMPPException xe) {
+            xe.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * Returns the instructions for creating a new account, or <tt>null</tt> if there
+     * are no instructions. If present, instructions should be displayed to the end-user
+     * that will complete the registration process.
+     *
+     * @return the account creation instructions, or <tt>null</tt> if there are none.
+     */
+    public String getAccountInstructions() {
+        try {
+            if (info == null) {
+                getRegistrationInfo();
+            }
+            return info.getInstructions();
+        }
+        catch (XMPPException xe) {
+            return null;
+        }
+    }
+
+    /**
+     * Creates a new account using the specified username and password. The server may
+     * require a number of extra account attributes such as an email address and phone
+     * number. In that case, Smack will attempt to automatically set all required
+     * attributes with blank values, which may or may not be accepted by the server.
+     * Therefore, it's recommended to check the required account attributes and to let
+     * the end-user populate them with real values instead.
+     *
+     * @param username the username.
+     * @param password the password.
+     * @throws XMPPException if an error occurs creating the account.
+     */
+    public void createAccount(String username, String password) throws XMPPException {
+        if (!supportsAccountCreation()) {
+            throw new XMPPException("Server does not support account creation.");
+        }
+        // Create a map for all the required attributes, but give them blank values.
+        Map<String, String> attributes = new HashMap<String, String>();
+        for (String attributeName : getAccountAttributes()) {
+            attributes.put(attributeName, "");
+        }
+        createAccount(username, password, attributes);
+    }
+
+    /**
+     * Creates a new account using the specified username, password and account attributes.
+     * The attributes Map must contain only String name/value pairs and must also have values
+     * for all required attributes.
+     *
+     * @param username the username.
+     * @param password the password.
+     * @param attributes the account attributes.
+     * @throws XMPPException if an error occurs creating the account.
+     * @see #getAccountAttributes()
+     */
+    public void createAccount(String username, String password, Map<String, String> attributes)
+            throws XMPPException
+    {
+        if (!supportsAccountCreation()) {
+            throw new XMPPException("Server does not support account creation.");
+        }
+        Registration reg = new Registration();
+        reg.setType(IQ.Type.SET);
+        reg.setTo(connection.getServiceName());
+        attributes.put("username",username);
+        attributes.put("password",password);
+        reg.setAttributes(attributes);
+        PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()),
+                new PacketTypeFilter(IQ.class));
+        PacketCollector collector = connection.createPacketCollector(filter);
+        connection.sendPacket(reg);
+        IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        collector.cancel();
+        if (result == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (result.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(result.getError());
+        }
+    }
+
+    /**
+     * Changes the password of the currently logged-in account. This operation can only
+     * be performed after a successful login operation has been completed. Not all servers
+     * support changing passwords; an XMPPException will be thrown when that is the case.
+     *
+     * @throws IllegalStateException if not currently logged-in to the server.
+     * @throws XMPPException if an error occurs when changing the password.
+     */
+    public void changePassword(String newPassword) throws XMPPException {
+        Registration reg = new Registration();
+        reg.setType(IQ.Type.SET);
+        reg.setTo(connection.getServiceName());
+        Map<String, String> map = new HashMap<String, String>();
+        map.put("username",StringUtils.parseName(connection.getUser()));
+        map.put("password",newPassword);
+        reg.setAttributes(map);
+        PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()),
+                new PacketTypeFilter(IQ.class));
+        PacketCollector collector = connection.createPacketCollector(filter);
+        connection.sendPacket(reg);
+        IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        collector.cancel();
+        if (result == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (result.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(result.getError());
+        }
+    }
+
+    /**
+     * Deletes the currently logged-in account from the server. This operation can only
+     * be performed after a successful login operation has been completed. Not all servers
+     * support deleting accounts; an XMPPException will be thrown when that is the case.
+     *
+     * @throws IllegalStateException if not currently logged-in to the server.
+     * @throws XMPPException if an error occurs when deleting the account.
+     */
+    public void deleteAccount() throws XMPPException {
+        if (!connection.isAuthenticated()) {
+            throw new IllegalStateException("Must be logged in to delete a account.");
+        }
+        Registration reg = new Registration();
+        reg.setType(IQ.Type.SET);
+        reg.setTo(connection.getServiceName());
+        Map<String, String> attributes = new HashMap<String, String>();
+        // To delete an account, we add a single attribute, "remove", that is blank.
+        attributes.put("remove", "");
+        reg.setAttributes(attributes);
+        PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()),
+                new PacketTypeFilter(IQ.class));
+        PacketCollector collector = connection.createPacketCollector(filter);
+        connection.sendPacket(reg);
+        IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        collector.cancel();
+        if (result == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (result.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(result.getError());
+        }
+    }
+
+    /**
+     * Gets the account registration info from the server.
+     *
+     * @throws XMPPException if an error occurs.
+     */
+    private synchronized void getRegistrationInfo() throws XMPPException {
+        Registration reg = new Registration();
+        reg.setTo(connection.getServiceName());
+        PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()),
+                new PacketTypeFilter(IQ.class));
+        PacketCollector collector = connection.createPacketCollector(filter);
+        connection.sendPacket(reg);
+        IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        collector.cancel();
+        if (result == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (result.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(result.getError());
+        }
+        else {
+            info = (Registration)result;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/org/jivesoftware/smack/AndroidConnectionConfiguration.java b/src/org/jivesoftware/smack/AndroidConnectionConfiguration.java
new file mode 100644
index 0000000..6ec05e0
--- /dev/null
+++ b/src/org/jivesoftware/smack/AndroidConnectionConfiguration.java
@@ -0,0 +1,113 @@
+package org.jivesoftware.smack;
+
+import java.io.File;
+
+import android.os.Build;
+
+import org.jivesoftware.smack.proxy.ProxyInfo;
+import org.jivesoftware.smack.util.DNSUtil;
+import org.jivesoftware.smack.util.dns.HostAddress;
+
+import java.util.List;
+
+/**
+ * This class wraps DNS SRV lookups for a new ConnectionConfiguration in a 
+ * new thread, since Android API >= 11 (Honeycomb) does not allow network 
+ * activity in the main thread. 
+ * 
+ * @author Florian Schmaus fschmaus@gmail.com
+ *
+ */
+public class AndroidConnectionConfiguration extends ConnectionConfiguration {
+    private static final int DEFAULT_TIMEOUT = 10000;
+    
+    /**
+     * Creates a new ConnectionConfiguration for the specified service name.
+     * A DNS SRV lookup will be performed to find out the actual host address
+     * and port to use for the connection.
+     *
+     * @param serviceName the name of the service provided by an XMPP server.
+     */
+    public AndroidConnectionConfiguration(String serviceName) throws XMPPException {
+        super();
+        AndroidInit(serviceName, DEFAULT_TIMEOUT);
+    }
+    
+    /**
+     * 
+     * @param serviceName
+     * @param timeout
+     * @throws XMPPException
+     */
+    public AndroidConnectionConfiguration(String serviceName, int timeout) throws XMPPException {
+        super();
+        AndroidInit(serviceName, timeout);
+    }
+
+    public AndroidConnectionConfiguration(String host, int port, String name) {
+	super(host, port, name);
+	AndroidInit();
+    }
+
+    private void AndroidInit() {
+    	// API 14 is Ice Cream Sandwich
+	if (Build.VERSION.SDK_INT >= 14) {
+	    setTruststoreType("AndroidCAStore");
+	    setTruststorePassword(null);
+	    setTruststorePath(null);
+	} else {
+	    setTruststoreType("BKS");
+	    String path = System.getProperty("javax.net.ssl.trustStore");
+	    if (path == null)
+		path = System.getProperty("java.home") + File.separator + "etc"
+		    + File.separator + "security" + File.separator
+		    + "cacerts.bks";
+	    setTruststorePath(path);
+	}
+    }
+
+    /**
+     * 
+     * @param serviceName
+     * @param timeout
+     * @throws XMPPException
+     */
+    private void AndroidInit(String serviceName, int timeout) throws XMPPException {
+	AndroidInit();
+        class DnsSrvLookupRunnable implements Runnable {
+            String serviceName;
+            List<HostAddress> addresses;
+
+            public DnsSrvLookupRunnable(String serviceName) {
+                this.serviceName = serviceName;
+            }
+
+            @Override
+            public void run() {
+                addresses = DNSUtil.resolveXMPPDomain(serviceName);
+            }
+
+            public List<HostAddress> getHostAddresses() {
+                return addresses;
+            }
+        }
+
+        DnsSrvLookupRunnable dnsSrv = new DnsSrvLookupRunnable(serviceName);
+        Thread t = new Thread(dnsSrv, "dns-srv-lookup");
+        t.start();
+        try {
+            t.join(timeout);
+        } catch (InterruptedException e) {
+            throw new XMPPException("DNS lookup timeout after " + timeout + "ms", e);
+        }
+
+        hostAddresses = dnsSrv.getHostAddresses();
+        if (hostAddresses == null) {
+        	throw new XMPPException("DNS lookup failure");
+        }
+
+        ProxyInfo proxy = ProxyInfo.forDefaultProxy();
+
+        init(serviceName, proxy);
+    }
+}
diff --git a/src/org/jivesoftware/smack/BOSHConfiguration.java b/src/org/jivesoftware/smack/BOSHConfiguration.java
new file mode 100644
index 0000000..0b033b4
--- /dev/null
+++ b/src/org/jivesoftware/smack/BOSHConfiguration.java
@@ -0,0 +1,124 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2009 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 java.net.URI;
+import java.net.URISyntaxException;
+
+import org.jivesoftware.smack.ConnectionConfiguration;
+import org.jivesoftware.smack.proxy.ProxyInfo;
+
+/**
+ * Configuration to use while establishing the connection to the XMPP server via
+ * HTTP binding.
+ * 
+ * @see BOSHConnection
+ * @author Guenther Niess
+ */
+public class BOSHConfiguration extends ConnectionConfiguration {
+
+    private boolean ssl;
+    private String file;
+
+    public BOSHConfiguration(String xmppDomain) {
+        super(xmppDomain, 7070);
+        setSASLAuthenticationEnabled(true);
+        ssl = false;
+        file = "/http-bind/";
+    }
+
+    public BOSHConfiguration(String xmppDomain, int port) {
+        super(xmppDomain, port);
+        setSASLAuthenticationEnabled(true);
+        ssl = false;
+        file = "/http-bind/";
+    }
+
+    /**
+     * Create a HTTP Binding configuration.
+     * 
+     * @param https true if you want to use SSL
+     *             (e.g. false for http://domain.lt:7070/http-bind).
+     * @param host the hostname or IP address of the connection manager
+     *             (e.g. domain.lt for http://domain.lt:7070/http-bind).
+     * @param port the port of the connection manager
+     *             (e.g. 7070 for http://domain.lt:7070/http-bind).
+     * @param filePath the file which is described by the URL
+     *             (e.g. /http-bind for http://domain.lt:7070/http-bind).
+     * @param xmppDomain the XMPP service name
+     *             (e.g. domain.lt for the user alice@domain.lt)
+     */
+    public BOSHConfiguration(boolean https, String host, int port, String filePath, String xmppDomain) {
+        super(host, port, xmppDomain);
+        setSASLAuthenticationEnabled(true);
+        ssl = https;
+        file = (filePath != null ? filePath : "/");
+    }
+
+    /**
+     * Create a HTTP Binding configuration.
+     * 
+     * @param https true if you want to use SSL
+     *             (e.g. false for http://domain.lt:7070/http-bind).
+     * @param host the hostname or IP address of the connection manager
+     *             (e.g. domain.lt for http://domain.lt:7070/http-bind).
+     * @param port the port of the connection manager
+     *             (e.g. 7070 for http://domain.lt:7070/http-bind).
+     * @param filePath the file which is described by the URL
+     *             (e.g. /http-bind for http://domain.lt:7070/http-bind).
+     * @param proxy the configuration of a proxy server.
+     * @param xmppDomain the XMPP service name
+     *             (e.g. domain.lt for the user alice@domain.lt)
+     */
+    public BOSHConfiguration(boolean https, String host, int port, String filePath, ProxyInfo proxy, String xmppDomain) {
+        super(host, port, xmppDomain, proxy);
+        setSASLAuthenticationEnabled(true);
+        ssl = https;
+        file = (filePath != null ? filePath : "/");
+    }
+
+    public boolean isProxyEnabled() {
+        return (proxy != null && proxy.getProxyType() != ProxyInfo.ProxyType.NONE);
+    }
+
+    public ProxyInfo getProxyInfo() {
+        return proxy;
+    }
+
+    public String getProxyAddress() {
+        return (proxy != null ? proxy.getProxyAddress() : null);
+    }
+
+    public int getProxyPort() {
+        return (proxy != null ? proxy.getProxyPort() : 8080);
+    }
+
+    public boolean isUsingSSL() {
+        return ssl;
+    }
+
+    public URI getURI() throws URISyntaxException {
+        if (file.charAt(0) != '/') {
+            file = '/' + file;
+        }
+        return new URI((ssl ? "https://" : "http://") + getHost() + ":" + getPort() + file);
+    }
+}
diff --git a/src/org/jivesoftware/smack/BOSHConnection.java b/src/org/jivesoftware/smack/BOSHConnection.java
new file mode 100644
index 0000000..594cf9d
--- /dev/null
+++ b/src/org/jivesoftware/smack/BOSHConnection.java
@@ -0,0 +1,779 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2009 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 java.io.IOException;
+import java.io.PipedReader;
+import java.io.PipedWriter;
+import java.io.Writer;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.ConnectionCreationListener;
+import org.jivesoftware.smack.ConnectionListener;
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.Roster;
+import org.jivesoftware.smack.XMPPException;
+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 com.kenai.jbosh.BOSHClient;
+import com.kenai.jbosh.BOSHClientConfig;
+import com.kenai.jbosh.BOSHClientConnEvent;
+import com.kenai.jbosh.BOSHClientConnListener;
+import com.kenai.jbosh.BOSHClientRequestListener;
+import com.kenai.jbosh.BOSHClientResponseListener;
+import com.kenai.jbosh.BOSHException;
+import com.kenai.jbosh.BOSHMessageEvent;
+import com.kenai.jbosh.BodyQName;
+import com.kenai.jbosh.ComposableBody;
+
+/**
+ * Creates a connection to a XMPP server via HTTP binding.
+ * This is specified in the XEP-0206: XMPP Over BOSH.
+ * 
+ * @see Connection
+ * @author Guenther Niess
+ */
+public class BOSHConnection extends Connection {
+
+    /**
+     * The XMPP Over Bosh namespace.
+     */
+    public static final String XMPP_BOSH_NS = "urn:xmpp:xbosh";
+
+    /**
+     * The BOSH namespace from XEP-0124.
+     */
+    public static final String BOSH_URI = "http://jabber.org/protocol/httpbind";
+
+    /**
+     * The used BOSH client from the jbosh library.
+     */
+    private BOSHClient client;
+
+    /**
+     * Holds the initial configuration used while creating the connection.
+     */
+    private final BOSHConfiguration config;
+
+    // Some flags which provides some info about the current state.
+    private boolean connected = false;
+    private boolean authenticated = false;
+    private boolean anonymous = false;
+    private boolean isFirstInitialization = true;
+    private boolean wasAuthenticated = false;
+    private boolean done = false;
+
+    /**
+     * The Thread environment for sending packet listeners.
+     */
+    private ExecutorService listenerExecutor;
+
+    // The readerPipe and consumer thread are used for the debugger.
+    private PipedWriter readerPipe;
+    private Thread readerConsumer;
+
+    /**
+     * The BOSH equivalent of the stream ID which is used for DIGEST authentication.
+     */
+    protected String authID = null;
+
+    /**
+     * The session ID for the BOSH session with the connection manager.
+     */
+    protected String sessionID = null;
+
+    /**
+     * The full JID of the authenticated user.
+     */
+    private String user = null;
+
+    /**
+     * The roster maybe also called buddy list holds the list of the users contacts.
+     */
+    private Roster roster = null;
+
+
+    /**
+     * Create a HTTP Binding connection to a XMPP server.
+     * 
+     * @param https true if you want to use SSL
+     *             (e.g. false for http://domain.lt:7070/http-bind).
+     * @param host the hostname or IP address of the connection manager
+     *             (e.g. domain.lt for http://domain.lt:7070/http-bind).
+     * @param port the port of the connection manager
+     *             (e.g. 7070 for http://domain.lt:7070/http-bind).
+     * @param filePath the file which is described by the URL
+     *             (e.g. /http-bind for http://domain.lt:7070/http-bind).
+     * @param xmppDomain the XMPP service name
+     *             (e.g. domain.lt for the user alice@domain.lt)
+     */
+    public BOSHConnection(boolean https, String host, int port, String filePath, String xmppDomain) {
+        super(new BOSHConfiguration(https, host, port, filePath, xmppDomain));
+        this.config = (BOSHConfiguration) getConfiguration();
+    }
+
+    /**
+     * Create a HTTP Binding connection to a XMPP server.
+     * 
+     * @param config The configuration which is used for this connection.
+     */
+    public BOSHConnection(BOSHConfiguration config) {
+        super(config);
+        this.config = config;
+    }
+
+    public void connect() throws XMPPException {
+        if (connected) {
+            throw new IllegalStateException("Already connected to a server.");
+        }
+        done = false;
+        try {
+            // Ensure a clean starting state
+            if (client != null) {
+                client.close();
+                client = null;
+            }
+            saslAuthentication.init();
+            sessionID = null;
+            authID = null;
+
+            // Initialize BOSH client
+            BOSHClientConfig.Builder cfgBuilder = BOSHClientConfig.Builder
+                    .create(config.getURI(), config.getServiceName());
+            if (config.isProxyEnabled()) {
+                cfgBuilder.setProxy(config.getProxyAddress(), config.getProxyPort());
+            }
+            client = BOSHClient.create(cfgBuilder.build());
+
+            // 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 ("
+                                            + connectionCounterValue + ")");
+                            thread.setDaemon(true);
+                            return thread;
+                        }
+                    });
+            client.addBOSHClientConnListener(new BOSHConnectionListener(this));
+            client.addBOSHClientResponseListener(new BOSHPacketReader(this));
+
+            // Initialize the debugger
+            if (config.isDebuggerEnabled()) {
+                initDebugger();
+                if (isFirstInitialization) {
+                    if (debugger.getReaderListener() != null) {
+                        addPacketListener(debugger.getReaderListener(), null);
+                    }
+                    if (debugger.getWriterListener() != null) {
+                        addPacketSendingListener(debugger.getWriterListener(), null);
+                    }
+                }
+            }
+
+            // Send the session creation request
+            client.send(ComposableBody.builder()
+                    .setNamespaceDefinition("xmpp", XMPP_BOSH_NS)
+                    .setAttribute(BodyQName.createWithPrefix(XMPP_BOSH_NS, "version", "xmpp"), "1.0")
+                    .build());
+        } catch (Exception e) {
+            throw new XMPPException("Can't connect to " + getServiceName(), e);
+        }
+
+        // Wait for the response from the server
+        synchronized (this) {
+            long endTime = System.currentTimeMillis() +
+                           SmackConfiguration.getPacketReplyTimeout() * 6;
+            while ((!connected) && (System.currentTimeMillis() < endTime)) {
+                try {
+                    wait(Math.abs(endTime - System.currentTimeMillis()));
+                }
+                catch (InterruptedException e) {}
+            }
+        }
+
+        // If there is no feedback, throw an remote server timeout error