netty-tcnative version 1.1.33, October 13, 2015
diff --git a/c/address.c b/c/address.c
new file mode 100644
index 0000000..7fa5a7d
--- /dev/null
+++ b/c/address.c
@@ -0,0 +1,146 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: address.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+
+#include "tcn.h"
+
+TCN_IMPLEMENT_CALL(jlong, Address, info)(TCN_STDARGS,
+                                         jstring hostname,
+                                         jint family, jint port,
+                                         jint flags, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    TCN_ALLOC_CSTRING(hostname);
+    char *sp = NULL;
+    int   scope_id = 0;
+    apr_sockaddr_t *sa = NULL;
+    apr_sockaddr_t *sl = NULL;
+    apr_int32_t f;
+
+
+    UNREFERENCED(o);
+    GET_S_FAMILY(f, family);
+#if APR_HAVE_IPV6
+    if (hostname) {
+        /* XXX: This only works for real scope_id's
+         */
+        if ((sp = strchr(J2S(hostname), '%'))) {
+            *sp++ = '\0';
+            scope_id = atoi(sp);
+        }
+    }
+#endif
+    TCN_THROW_IF_ERR(apr_sockaddr_info_get(&sa,
+            J2S(hostname), f, (apr_port_t)port,
+            (apr_int32_t)flags, p), sa);
+    sl = sa;
+    /* 
+     * apr_sockaddr_info_get may return several address so this is not
+     * go to work in some cases (but as least it works for Linux)
+     * XXX: with AP_ENABLE_V4_MAPPED it is going to work otherwise it won't.
+     */
+#if APR_HAVE_IPV6
+    if (hostname == NULL) {
+        /* Try all address using IPV6 one */
+        while (sl) {
+            if (sl->family == APR_INET6)
+                break; /* Done */
+            sl = sl->next;
+        }
+        /* If we don't find an IPv6 address, use the original one */
+        if (sl == NULL) {
+            sl = sa;
+        }
+    }
+    if (sp) {
+        /* Set the provided scope id
+         * APR lack the api for setting this directly so lets presume
+         * the sin6_scope_id is present everywhere
+         */
+        sl->sa.sin6.sin6_scope_id = scope_id;
+    }
+#endif
+
+cleanup:
+    TCN_FREE_CSTRING(hostname);
+    return P2J(sl);
+}
+
+TCN_IMPLEMENT_CALL(jstring, Address, getnameinfo)(TCN_STDARGS,
+                                                  jlong sa, jint flags)
+{
+    apr_sockaddr_t *s = J2P(sa, apr_sockaddr_t *);
+    char *hostname;
+
+    UNREFERENCED(o);
+    if (apr_getnameinfo(&hostname, s, (apr_int32_t)flags) == APR_SUCCESS)
+        return AJP_TO_JSTRING(hostname);
+    else
+        return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jstring, Address, getip)(TCN_STDARGS, jlong sa)
+{
+    apr_sockaddr_t *s = J2P(sa, apr_sockaddr_t *);
+    char *ipaddr;
+
+    UNREFERENCED(o);
+    if (apr_sockaddr_ip_get(&ipaddr, s) == APR_SUCCESS)
+        return AJP_TO_JSTRING(ipaddr);
+    else
+        return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jlong, Address, get)(TCN_STDARGS, jint which,
+                                        jlong sock)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_sockaddr_t *sa = NULL;
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_socket_addr_get(&sa,
+                        (apr_interface_e)which, s->sock), sa);
+cleanup:
+    return P2J(sa);
+}
+
+TCN_IMPLEMENT_CALL(jboolean, Address, equal)(TCN_STDARGS,
+                                             jlong a, jlong b)
+{
+    apr_sockaddr_t *sa = J2P(a, apr_sockaddr_t *);
+    apr_sockaddr_t *sb = J2P(b, apr_sockaddr_t *);
+
+    UNREFERENCED_STDARGS;
+    return apr_sockaddr_equal(sa, sb) ? JNI_TRUE : JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jint, Address, getservbyname)(TCN_STDARGS,
+                                                 jlong sa, jstring servname)
+{
+    apr_sockaddr_t *s = J2P(sa, apr_sockaddr_t *);
+    TCN_ALLOC_CSTRING(servname);
+    apr_status_t rv;
+
+    UNREFERENCED(o);
+    rv = apr_getservbyname(s, J2S(servname));
+    TCN_FREE_CSTRING(servname);
+    return (jint)rv;
+}
diff --git a/c/bb.c b/c/bb.c
new file mode 100644
index 0000000..bacb91f
--- /dev/null
+++ b/c/bb.c
@@ -0,0 +1,135 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: bb.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+
+#include "tcn.h"
+
+/**
+ * DirectByteBuffer utilities
+ */
+
+TCN_IMPLEMENT_CALL(jobject, Buffer, malloc)(TCN_STDARGS, jint size)
+{
+    void *mem;
+    size_t sz = (size_t)APR_ALIGN_DEFAULT(size);
+
+    UNREFERENCED(o);
+
+    if ((mem = malloc(sz)) != NULL) {
+        jobject rv = (*e)->NewDirectByteBuffer(e, mem, (jlong)sz);
+        if (rv == NULL)
+            free(mem);
+        return rv;
+    }
+    else {
+        return NULL;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jobject, Buffer, calloc)(TCN_STDARGS, jint num, jint size)
+{
+    size_t sz = (size_t)APR_ALIGN_DEFAULT((size * num));
+    void *mem;
+
+    UNREFERENCED(o);
+
+    if ((mem = calloc(1, sz)) != NULL) {
+        jobject rv = (*e)->NewDirectByteBuffer(e, mem, (jlong)sz);
+        if (rv == NULL)
+            free(mem);
+        return rv;
+    }
+    else {
+        return NULL;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jobject, Buffer, palloc)(TCN_STDARGS, jlong pool,
+                                            jint size)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_size_t sz = (apr_size_t)APR_ALIGN_DEFAULT(size);
+    void *mem;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pool != 0);
+
+    if ((mem = apr_palloc(p, sz)) != NULL)
+        return (*e)->NewDirectByteBuffer(e, mem, (jlong)sz);
+    else
+        return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jobject, Buffer, pcalloc)(TCN_STDARGS, jlong pool,
+                                             jint size)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_size_t sz = (apr_size_t)APR_ALIGN_DEFAULT(size);
+    void *mem;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pool != 0);
+
+    if ((mem = apr_pcalloc(p, sz)) != NULL)
+        return (*e)->NewDirectByteBuffer(e, mem, (jlong)sz);
+    else
+        return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jobject, Buffer, create)(TCN_STDARGS, jlong addr,
+                                            jint size)
+{
+    void *mem = J2P(addr, void *);
+
+    UNREFERENCED(o);
+    TCN_ASSERT(mem  != 0);
+    TCN_ASSERT(size != 0);
+
+    if (mem && size)
+        return (*e)->NewDirectByteBuffer(e, mem, (jlong)size);
+    else
+        return NULL;
+}
+
+TCN_IMPLEMENT_CALL(void, Buffer, free)(TCN_STDARGS, jobject bb)
+{
+    void *mem;
+
+    UNREFERENCED(o);
+    if ((mem = (*e)->GetDirectBufferAddress(e, bb)) != NULL) {
+        /* This can cause core dump if address was
+         * allocated from the APR pool.
+         */
+        free(mem);
+    }
+}
+
+TCN_IMPLEMENT_CALL(jlong, Buffer, address)(TCN_STDARGS, jobject bb)
+{
+    UNREFERENCED(o);
+    return P2J((*e)->GetDirectBufferAddress(e, bb));
+}
+
+TCN_IMPLEMENT_CALL(jlong, Buffer, size)(TCN_STDARGS, jobject bb)
+{
+    UNREFERENCED(o);
+    return (*e)->GetDirectBufferCapacity(e, bb);
+}
diff --git a/c/dir.c b/c/dir.c
new file mode 100644
index 0000000..ebea430
--- /dev/null
+++ b/c/dir.c
@@ -0,0 +1,105 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: dir.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+ 
+#include "tcn.h"
+#include "apr_file_io.h"
+
+TCN_IMPLEMENT_CALL(jint, Directory, make)(TCN_STDARGS, jstring path,
+                                          jint perm, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    TCN_ALLOC_CSTRING(path);
+    apr_status_t rv;
+
+    UNREFERENCED(o);
+    rv = apr_dir_make(J2S(path), (apr_fileperms_t)perm, p);
+    TCN_FREE_CSTRING(path);
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, Directory, makeRecursive)(TCN_STDARGS, jstring path,
+                                                    jint perm, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    TCN_ALLOC_CSTRING(path);
+    apr_status_t rv;
+
+    UNREFERENCED(o);
+    rv = apr_dir_make_recursive(J2S(path), (apr_fileperms_t)perm, p);
+    TCN_FREE_CSTRING(path);
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, Directory, remove)(TCN_STDARGS, jstring path,
+                                            jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    TCN_ALLOC_CSTRING(path);
+    apr_status_t rv;
+
+    UNREFERENCED(o);
+    rv = apr_dir_remove(J2S(path), p);
+    TCN_FREE_CSTRING(path);
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jstring, Directory, tempGet)(TCN_STDARGS, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    jstring name = NULL;
+    const char *tname;
+
+    UNREFERENCED(o);
+    if (apr_temp_dir_get(&tname, p) == APR_SUCCESS)
+        name = AJP_TO_JSTRING(tname);
+
+    return name;
+}
+
+TCN_IMPLEMENT_CALL(jlong, Directory, open)(TCN_STDARGS, jstring path,
+                                      jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_dir_t *d = NULL;
+    TCN_ALLOC_CSTRING(path);
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_dir_open(&d, J2S(path), p), d);
+
+cleanup:
+    TCN_FREE_CSTRING(path);
+    return P2J(d);
+}
+
+TCN_IMPLEMENT_CALL(jint, Directory, close)(TCN_STDARGS, jlong dir)
+{
+    apr_dir_t *d = J2P(dir, apr_dir_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_dir_close(d);
+}
+
+TCN_IMPLEMENT_CALL(jint, Directory, rewind)(TCN_STDARGS, jlong dir)
+{
+    apr_dir_t *d = J2P(dir, apr_dir_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_dir_rewind(d);
+}
diff --git a/c/error.c b/c/error.c
new file mode 100644
index 0000000..f93d10a
--- /dev/null
+++ b/c/error.c
@@ -0,0 +1,248 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: error.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+
+#include "tcn.h"
+
+static const char *tcn_errors[] = {
+                            "Unknown user error",
+    /* TCN_TIMEUP      */   "Operation timed out",
+    /* TCN_EAGAIN      */   "There is no data ready",
+    /* TCN_EINTR       */   "Interrupted system call",
+    /* TCN_EINPROGRESS */   "Operation in progress",
+    /* TCN_ETIMEDOUT   */   "Connection timed out",
+    NULL
+};
+
+/* Merge IS_ETIMEDOUT with APR_TIMEUP
+ */
+#define TCN_STATUS_IS_ETIMEDOUT(x) (APR_STATUS_IS_ETIMEDOUT((x)) || ((x) == APR_TIMEUP))
+/*
+ * Convenience function to help throw an java.lang.Exception.
+ */
+void tcn_ThrowException(JNIEnv *env, const char *msg)
+{
+    jclass javaExceptionClass;
+
+    javaExceptionClass = (*env)->FindClass(env, "java/lang/Exception");
+    if (javaExceptionClass == NULL) {
+        fprintf(stderr, "Cannot find java/lang/Exception class\n");
+        return;
+    }
+    (*env)->ThrowNew(env, javaExceptionClass, msg);
+    (*env)->DeleteLocalRef(env, javaExceptionClass);
+
+}
+
+void tcn_ThrowMemoryException(JNIEnv *env, const char *file, int line, const char *msg)
+{
+    jclass javaExceptionClass;
+    javaExceptionClass = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
+    if (javaExceptionClass == NULL) {
+        fprintf(stderr, "Cannot find java/lang/OutOfMemoryError\n");
+        return;
+    }
+
+    if (file) {
+        char fmt[TCN_BUFFER_SZ];
+        char *f = (char *)(file + strlen(file) - 1);
+        while (f != file && '\\' != *f && '/' != *f) {
+            f--;
+        }
+        if (f != file) {
+            f++;
+        }
+        sprintf(fmt, "%s for [%04d@%s]", msg, line, f);
+        (*env)->ThrowNew(env, javaExceptionClass, &fmt[0]);
+    }
+    else
+        (*env)->ThrowNew(env, javaExceptionClass, msg);
+    (*env)->DeleteLocalRef(env, javaExceptionClass);
+
+}
+
+
+void tcn_Throw(JNIEnv *env, const char *fmt, ...)
+{
+    char msg[TCN_BUFFER_SZ] = {'\0'};
+    va_list ap;
+
+    va_start(ap, fmt);
+    apr_vsnprintf(msg, TCN_BUFFER_SZ, fmt, ap);
+    tcn_ThrowException(env, msg);
+    va_end(ap);
+}
+
+/*
+ * Convenience function to help throw an APR Exception
+ * from native error code.
+ */
+void tcn_ThrowAPRException(JNIEnv *e, apr_status_t err)
+{
+    jclass aprErrorClass;
+    jmethodID constructorID = 0;
+    jobject throwObj;
+    jstring jdescription;
+    char serr[512] = {0};
+
+    aprErrorClass = (*e)->FindClass(e, TCN_ERROR_CLASS);
+    if (aprErrorClass == NULL) {
+        fprintf(stderr, "Cannot find " TCN_ERROR_CLASS " class\n");
+        return;
+    }
+
+    /* Find the constructor ID */
+    constructorID = (*e)->GetMethodID(e, aprErrorClass,
+                                      "<init>",
+                                      "(ILjava/lang/String;)V");
+    if (constructorID == NULL) {
+        fprintf(stderr,
+                "Cannot find constructor for " TCN_ERROR_CLASS " class\n");
+        goto cleanup;
+    }
+
+    apr_strerror(err, serr, 512);
+    /* Obtain the string objects */
+    jdescription = AJP_TO_JSTRING(serr);
+    if (jdescription == NULL) {
+        fprintf(stderr,
+                "Cannot allocate description for " TCN_ERROR_CLASS " class\n");
+        goto cleanup;
+    }
+    /* Create the APR Error object */
+    throwObj = (*e)->NewObject(e, aprErrorClass, constructorID,
+                               (jint)err, jdescription);
+    if (throwObj == NULL) {
+        fprintf(stderr,
+                "Cannot allocate new " TCN_ERROR_CLASS " object\n");
+        goto cleanup;
+    }
+
+    (*e)->Throw(e, throwObj);
+cleanup:
+    (*e)->DeleteLocalRef(e, aprErrorClass);
+}
+
+
+TCN_IMPLEMENT_CALL(jint, Error, osError)(TCN_STDARGS)
+{
+    UNREFERENCED_STDARGS;
+    return (jint)apr_get_os_error();
+}
+
+TCN_IMPLEMENT_CALL(jint, Error, netosError)(TCN_STDARGS)
+{
+    UNREFERENCED_STDARGS;
+    return (jint)apr_get_netos_error();
+}
+
+TCN_IMPLEMENT_CALL(jstring, Error, strerror)(TCN_STDARGS, jint err)
+{
+    char serr[512] = {0};
+    jstring jerr;
+
+    UNREFERENCED(o);
+    if (err >= TCN_TIMEUP && err <= TCN_ETIMEDOUT) {
+        err -= TCN_TIMEUP;
+        jerr = AJP_TO_JSTRING(tcn_errors[err + 1]);
+    }
+    else {
+        apr_strerror(err, serr, 512);
+        jerr = AJP_TO_JSTRING(serr);
+    }
+    return jerr;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, Status, is)(TCN_STDARGS, jint err, jint idx)
+{
+#define APR_IS(I, E) case I: if (E(err)) return JNI_TRUE; break
+#define APR_ISX(I, E, T) case I: if (E(err) || (err == T)) return JNI_TRUE; break
+
+    UNREFERENCED_STDARGS;
+    switch (idx) {
+        APR_IS(1,  APR_STATUS_IS_ENOSTAT);
+        APR_IS(2,  APR_STATUS_IS_ENOPOOL);
+        /* empty slot: +3 */
+        APR_IS(4,  APR_STATUS_IS_EBADDATE);
+        APR_IS(5,  APR_STATUS_IS_EINVALSOCK);
+        APR_IS(6,  APR_STATUS_IS_ENOPROC);
+        APR_IS(7,  APR_STATUS_IS_ENOTIME);
+        APR_IS(8,  APR_STATUS_IS_ENODIR);
+        APR_IS(9,  APR_STATUS_IS_ENOLOCK);
+        APR_IS(10, APR_STATUS_IS_ENOPOLL);
+        APR_IS(11, APR_STATUS_IS_ENOSOCKET);
+        APR_IS(12, APR_STATUS_IS_ENOTHREAD);
+        APR_IS(13, APR_STATUS_IS_ENOTHDKEY);
+        APR_IS(14, APR_STATUS_IS_EGENERAL);
+        APR_IS(15, APR_STATUS_IS_ENOSHMAVAIL);
+        APR_IS(16, APR_STATUS_IS_EBADIP);
+        APR_IS(17, APR_STATUS_IS_EBADMASK);
+        /* empty slot: +18 */
+        APR_IS(19, APR_STATUS_IS_EDSOOPEN);
+        APR_IS(20, APR_STATUS_IS_EABSOLUTE);
+        APR_IS(21, APR_STATUS_IS_ERELATIVE);
+        APR_IS(22, APR_STATUS_IS_EINCOMPLETE);
+        APR_IS(23, APR_STATUS_IS_EABOVEROOT);
+        APR_IS(24, APR_STATUS_IS_EBADPATH);
+        APR_IS(25, APR_STATUS_IS_EPATHWILD);
+        APR_IS(26, APR_STATUS_IS_ESYMNOTFOUND);
+        APR_IS(27, APR_STATUS_IS_EPROC_UNKNOWN);
+        APR_IS(28, APR_STATUS_IS_ENOTENOUGHENTROPY);
+
+
+        /* APR_Error */
+        APR_IS(51, APR_STATUS_IS_INCHILD);
+        APR_IS(52, APR_STATUS_IS_INPARENT);
+        APR_IS(53, APR_STATUS_IS_DETACH);
+        APR_IS(54, APR_STATUS_IS_NOTDETACH);
+        APR_IS(55, APR_STATUS_IS_CHILD_DONE);
+        APR_IS(56, APR_STATUS_IS_CHILD_NOTDONE);
+        APR_ISX(57, APR_STATUS_IS_TIMEUP, TCN_TIMEUP);
+        APR_IS(58, APR_STATUS_IS_INCOMPLETE);
+        /* empty slot: +9 */
+        /* empty slot: +10 */
+        /* empty slot: +11 */
+        APR_IS(62, APR_STATUS_IS_BADCH);
+        APR_IS(63, APR_STATUS_IS_BADARG);
+        APR_IS(64, APR_STATUS_IS_EOF);
+        APR_IS(65, APR_STATUS_IS_NOTFOUND);
+        /* empty slot: +16 */
+        /* empty slot: +17 */
+        /* empty slot: +18 */
+        APR_IS(69, APR_STATUS_IS_ANONYMOUS);
+        APR_IS(70, APR_STATUS_IS_FILEBASED);
+        APR_IS(71, APR_STATUS_IS_KEYBASED);
+        APR_IS(72, APR_STATUS_IS_EINIT);
+        APR_IS(73, APR_STATUS_IS_ENOTIMPL);
+        APR_IS(74, APR_STATUS_IS_EMISMATCH);
+        APR_IS(75, APR_STATUS_IS_EBUSY);
+        /* Socket errors */
+        APR_ISX(90, APR_STATUS_IS_EAGAIN, TCN_EAGAIN);
+        APR_ISX(91, TCN_STATUS_IS_ETIMEDOUT, TCN_ETIMEDOUT);
+        APR_IS(92, APR_STATUS_IS_ECONNABORTED);
+        APR_IS(93, APR_STATUS_IS_ECONNRESET);
+        APR_ISX(94, APR_STATUS_IS_EINPROGRESS, TCN_EINPROGRESS);
+        APR_ISX(95, APR_STATUS_IS_EINTR, TCN_EINTR);
+        APR_IS(96, APR_STATUS_IS_ENOTSOCK);
+        APR_IS(97, APR_STATUS_IS_EINVAL);
+    }
+    return JNI_FALSE;
+}
diff --git a/c/file.c b/c/file.c
new file mode 100644
index 0000000..05c8747
--- /dev/null
+++ b/c/file.c
@@ -0,0 +1,601 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: file.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+ 
+#include "tcn.h"
+#include "apr_file_io.h"
+
+
+TCN_IMPLEMENT_CALL(jint, File, close)(TCN_STDARGS, jlong file)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_file_close(f);
+}
+
+TCN_IMPLEMENT_CALL(jint, File, eof)(TCN_STDARGS, jlong file)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_file_eof(f);
+}
+
+TCN_IMPLEMENT_CALL(jint, File, flush)(TCN_STDARGS, jlong file)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_file_flush(f);
+}
+
+TCN_IMPLEMENT_CALL(jint, File, unlock)(TCN_STDARGS, jlong file)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_file_unlock(f);
+}
+
+TCN_IMPLEMENT_CALL(jint, File, flagsGet)(TCN_STDARGS, jlong file)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_file_flags_get(f);
+}
+
+TCN_IMPLEMENT_CALL(jint, File, lock)(TCN_STDARGS, jlong file, jint flags)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_file_lock(f, (int)flags);
+}
+
+TCN_IMPLEMENT_CALL(jint, File, trunc)(TCN_STDARGS, jlong file, jlong off)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_file_trunc(f, (apr_off_t)off);
+}
+
+TCN_IMPLEMENT_CALL(jlong, File, open)(TCN_STDARGS, jstring fname,
+                                      jint flag, jint perm,
+                                      jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_file_t *f = NULL;
+    TCN_ALLOC_CSTRING(fname);
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_file_open(&f, J2S(fname), (apr_int32_t)flag,
+                     (apr_fileperms_t)perm, p), f);
+
+cleanup:
+    TCN_FREE_CSTRING(fname);
+    return P2J(f);
+}
+
+TCN_IMPLEMENT_CALL(jlong, File, mktemp)(TCN_STDARGS, jstring templ,
+                                      jint flags,
+                                      jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_file_t *f = NULL;
+    char *ctempl = tcn_strdup(e, templ);
+
+    UNREFERENCED(o);
+    if (!ctempl) {
+       TCN_THROW_OS_ERROR(e);
+       return 0;
+    }
+    TCN_THROW_IF_ERR(apr_file_mktemp(&f, ctempl,
+                     (apr_int32_t)flags, p), f);
+
+cleanup:
+    free(ctempl);
+    return P2J(f);
+}
+
+TCN_IMPLEMENT_CALL(jint, File, remove)(TCN_STDARGS, jstring path, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    TCN_ALLOC_CSTRING(path);
+    apr_status_t rv;
+
+    UNREFERENCED(o);
+    rv = apr_file_remove(J2S(path), p);
+    TCN_FREE_CSTRING(path);
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, rename)(TCN_STDARGS, jstring from,
+                                       jstring to, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    TCN_ALLOC_CSTRING(from);
+    TCN_ALLOC_CSTRING(to);
+    apr_status_t rv;
+
+    UNREFERENCED(o);
+    rv = apr_file_rename(J2S(from), J2S(to), p);
+    TCN_FREE_CSTRING(from);
+    TCN_FREE_CSTRING(to);
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, copy)(TCN_STDARGS, jstring from,
+                                     jstring to, jint perms, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    TCN_ALLOC_CSTRING(from);
+    TCN_ALLOC_CSTRING(to);
+    apr_status_t rv;
+
+    UNREFERENCED(o);
+    rv = apr_file_copy(J2S(from), J2S(to), (apr_fileperms_t)perms, p);
+    TCN_FREE_CSTRING(from);
+    TCN_FREE_CSTRING(to);
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, append)(TCN_STDARGS, jstring from,
+                                       jstring to, jint perms, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    TCN_ALLOC_CSTRING(from);
+    TCN_ALLOC_CSTRING(to);
+    apr_status_t rv;
+
+    UNREFERENCED(o);
+    rv = apr_file_append(J2S(from), J2S(to), (apr_fileperms_t)perms, p);
+    TCN_FREE_CSTRING(from);
+    TCN_FREE_CSTRING(to);
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jstring, File, nameGet)(TCN_STDARGS, jlong file)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    jstring name = NULL;
+    const char *fname;
+
+    UNREFERENCED(o);
+    if (apr_file_name_get(&fname, f) == APR_SUCCESS)
+        name = AJP_TO_JSTRING(fname);
+
+    return name;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, permsSet)(TCN_STDARGS, jstring file, jint perms)
+{
+    TCN_ALLOC_CSTRING(file);
+    apr_status_t rv;
+
+    UNREFERENCED(o);
+    rv = apr_file_perms_set(J2S(file), (apr_fileperms_t)perms);
+    TCN_FREE_CSTRING(file);
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, attrsSet)(TCN_STDARGS, jstring file, jint attrs,
+                                          jint mask, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    TCN_ALLOC_CSTRING(file);
+    apr_status_t rv;
+
+    UNREFERENCED(o);
+    rv = apr_file_attrs_set(J2S(file), (apr_fileattrs_t)attrs,
+                            (apr_fileattrs_t)mask, p);
+    TCN_FREE_CSTRING(file);
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, mtimeSet)(TCN_STDARGS, jstring file, jlong mtime,
+                                          jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    TCN_ALLOC_CSTRING(file);
+    apr_status_t rv;
+
+    UNREFERENCED(o);
+    rv = apr_file_mtime_set(J2S(file), J2T(mtime), p);
+    TCN_FREE_CSTRING(file);
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, File, seek)(TCN_STDARGS, jlong file,
+                                      jint where, jlong offset)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_off_t pos = (apr_off_t)offset;
+    apr_seek_where_t w;
+    UNREFERENCED(o);
+    switch (where) {
+        case 1:
+            w = APR_CUR;
+            break;
+        case 2:
+            w = APR_END;
+            break;
+        default:
+            w = APR_SET;
+            break;
+    }
+    TCN_THROW_IF_ERR(apr_file_seek(f, w, &pos), pos);
+
+cleanup:
+    return (jlong)pos;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, putc)(TCN_STDARGS, jbyte c, jlong file)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_file_putc((char)c, f);
+}
+
+TCN_IMPLEMENT_CALL(jint, File, getc)(TCN_STDARGS, jlong file)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    char ch;
+
+    UNREFERENCED_STDARGS;
+    TCN_THROW_IF_ERR(apr_file_getc(&ch, f), ch);
+
+cleanup:
+    return (jint)ch;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, ungetc)(TCN_STDARGS, jbyte c, jlong file)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_file_ungetc((char)c, f);
+}
+
+TCN_IMPLEMENT_CALL(jint, File, puts)(TCN_STDARGS, jbyteArray str, jlong file)
+{
+    apr_status_t rv = APR_EINVAL;
+    apr_file_t *f = J2P(file, apr_file_t *);
+    jbyte *bytes = (*e)->GetPrimitiveArrayCritical(e, str, NULL);
+
+    UNREFERENCED(o);
+    if (bytes) {
+        rv = apr_file_puts((const char *)bytes, f);
+        (*e)->ReleasePrimitiveArrayCritical(e, str, bytes, JNI_ABORT);
+    }
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, write)(TCN_STDARGS, jlong file,
+                                      jbyteArray buf, jint offset, jint towrite)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_size_t nbytes = (apr_size_t)towrite;
+    jbyte *bytes = (*e)->GetPrimitiveArrayCritical(e, buf, NULL);
+    apr_status_t ss;
+
+    UNREFERENCED(o);
+    if (towrite < 0)
+        towrite = (*e)->GetArrayLength(e, buf);
+    ss = apr_file_write(f, bytes + offset, &nbytes);
+
+    (*e)->ReleasePrimitiveArrayCritical(e, buf, bytes, JNI_ABORT);
+    if (ss == APR_SUCCESS)
+        return (jint)nbytes;
+    else
+        return -(jint)ss;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, writeb)(TCN_STDARGS, jlong file,
+                                       jobject buf, jint offset, jint towrite)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_size_t nbytes = (apr_size_t)towrite;
+    char *bytes = (char *)(*e)->GetDirectBufferAddress(e, buf);
+    apr_status_t ss = APR_EINVAL;
+
+    UNREFERENCED(o);
+    if (bytes)
+        ss = apr_file_write(f, bytes + offset, &nbytes);
+
+    if (ss == APR_SUCCESS)
+        return (jint)nbytes;
+    else
+        return -(jint)ss;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, writeFull)(TCN_STDARGS, jlong file,
+                                          jbyteArray buf, jint offset, jint towrite)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_size_t nbytes = (apr_size_t)towrite;
+    apr_size_t written = 0;
+    apr_status_t ss;
+    jbyte *bytes = (*e)->GetByteArrayElements(e, buf, NULL);
+
+    UNREFERENCED(o);
+    if (towrite < 0)
+        towrite = (*e)->GetArrayLength(e, buf);
+    ss = apr_file_write_full(f, bytes + offset, nbytes, &written);
+
+    (*e)->ReleaseByteArrayElements(e, buf, bytes, JNI_ABORT);
+    if (ss == APR_SUCCESS)
+        return (jint)written;
+    else
+        return -(jint)ss;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, writeFullb)(TCN_STDARGS, jlong file,
+                                           jobject buf, jint offset, jint towrite)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_size_t nbytes = (apr_size_t)towrite;
+    apr_size_t written = 0;
+    apr_status_t ss = APR_EINVAL;
+    char *bytes = (char *)(*e)->GetDirectBufferAddress(e, buf);
+
+    UNREFERENCED(o);
+    if (bytes)
+        ss = apr_file_write_full(f, bytes + offset, nbytes, &written);
+
+    if (ss == APR_SUCCESS)
+        return (jint)written;
+    else
+        return -(jint)ss;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, writev)(TCN_STDARGS, jlong file,
+                                       jobjectArray bufs)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    jsize nvec = (*e)->GetArrayLength(e, bufs);
+    jsize i;
+    struct iovec vec[APR_MAX_IOVEC_SIZE];
+    jobject ba[APR_MAX_IOVEC_SIZE];
+    apr_size_t written = 0;
+    apr_status_t ss;
+
+    UNREFERENCED(o);
+
+    if (nvec >= APR_MAX_IOVEC_SIZE) {
+        /* TODO: Throw something here */
+        return 0;
+    }
+    for (i = 0; i < nvec; i++) {
+        ba[i] = (*e)->GetObjectArrayElement(e, bufs, i);
+        vec[i].iov_len  = (*e)->GetArrayLength(e, ba[i]);
+        vec[i].iov_base = (void *)((*e)->GetByteArrayElements(e, ba[i], NULL));
+    }
+
+    ss = apr_file_writev(f, vec, nvec, &written);
+
+    for (i = 0; i < nvec; i++) {
+        (*e)->ReleaseByteArrayElements(e, ba[i], (jbyte *)vec[i].iov_base, JNI_ABORT);
+    }
+    if (ss == APR_SUCCESS)
+        return (jint)written;
+    else
+        return -(jint)ss;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, writevFull)(TCN_STDARGS, jlong file,
+                                           jobjectArray bufs)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    jsize nvec = (*e)->GetArrayLength(e, bufs);
+    jsize i;
+    struct iovec vec[APR_MAX_IOVEC_SIZE];
+    jobject ba[APR_MAX_IOVEC_SIZE];
+    apr_size_t written = 0;
+    apr_status_t ss;
+
+    UNREFERENCED(o);
+
+    if (nvec >= APR_MAX_IOVEC_SIZE) {
+        /* TODO: Throw something here */
+        return 0;
+    }
+    for (i = 0; i < nvec; i++) {
+        ba[i] = (*e)->GetObjectArrayElement(e, bufs, i);
+        vec[i].iov_len  = (*e)->GetArrayLength(e, ba[i]);
+        vec[i].iov_base = (void *)((*e)->GetByteArrayElements(e, ba[i], NULL));
+    }
+#if (APR_VERSION_MAJOR >= 1) && (APR_VERSION_MINOR >= 1)
+    ss = apr_file_writev_full(f, vec, nvec, &written);
+#else
+    ss = apr_file_writev(f, vec, nvec, &written);
+#endif
+
+    for (i = 0; i < nvec; i++) {
+        (*e)->ReleaseByteArrayElements(e, ba[i], (jbyte *)vec[i].iov_base,
+                                       JNI_ABORT);
+    }
+    if (ss == APR_SUCCESS)
+        return (jint)written;
+    else
+        return -(jint)ss;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, read)(TCN_STDARGS, jlong file,
+                                     jbyteArray buf, jint offset,
+                                     jint toread)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_size_t nbytes = (apr_size_t)toread;
+    jbyte *bytes = (*e)->GetByteArrayElements(e, buf, NULL);
+    apr_status_t ss;
+
+    UNREFERENCED(o);
+    ss = apr_file_read(f, bytes + offset, &nbytes);
+
+    (*e)->ReleaseByteArrayElements(e, buf, bytes,
+                                   ss == APR_SUCCESS ? 0 : JNI_ABORT);
+    if (ss == APR_SUCCESS)
+        return (jint)nbytes;
+    else
+        return -(jint)ss;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, readb)(TCN_STDARGS, jlong file,
+                                      jobject buf, jint offset,
+                                      jint toread)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_size_t nbytes = (apr_size_t)toread;
+    char *bytes = (char *)(*e)->GetDirectBufferAddress(e, buf);
+    apr_status_t ss = APR_EINVAL;
+
+    UNREFERENCED(o);
+    if (bytes)
+        ss = apr_file_read(f, bytes + offset, &nbytes);
+
+    if (ss == APR_SUCCESS)
+        return (jint)nbytes;
+    else
+        return -(jint)ss;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, readFull)(TCN_STDARGS, jlong file,
+                                         jbyteArray buf, jint offset,
+                                         jint toread)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_size_t nbytes = (apr_size_t)toread;
+    apr_size_t nread  = 0;
+    apr_status_t ss;
+    jbyte *bytes = (*e)->GetByteArrayElements(e, buf, NULL);
+
+    UNREFERENCED(o);
+    ss = apr_file_read_full(f, bytes + offset, nbytes, &nread);
+
+    (*e)->ReleaseByteArrayElements(e, buf, bytes,
+                                   ss == APR_SUCCESS ? 0 : JNI_ABORT);
+    if (ss == APR_SUCCESS)
+        return (jint)nread;
+    else
+        return -(jint)ss;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, readFullb)(TCN_STDARGS, jlong file,
+                                          jobject buf, jint offset,
+                                          jint toread)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_size_t nbytes = (apr_size_t)toread;
+    apr_size_t nread  = 0;
+    char *bytes = (char *)(*e)->GetDirectBufferAddress(e, buf);
+    apr_status_t ss = APR_EINVAL;
+
+    UNREFERENCED(o);
+    if (bytes)
+        ss = apr_file_read_full(f, bytes + offset, nbytes, &nread);
+
+    if (ss == APR_SUCCESS)
+        return (jint)nread;
+    else
+        return -(jint)ss;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, gets)(TCN_STDARGS, jbyteArray buf, jint offset,
+                                     jlong file)
+{
+    apr_status_t rv;
+    apr_file_t *f = J2P(file, apr_file_t *);
+    jsize nbytes = (*e)->GetArrayLength(e, buf);
+    jbyte *bytes = (*e)->GetByteArrayElements(e, buf, NULL);
+
+    UNREFERENCED(o);
+    rv = apr_file_gets((char*)(bytes + offset),nbytes - offset, f);
+    (*e)->ReleaseByteArrayElements(e, buf, bytes,
+                                   rv == APR_SUCCESS ? 0 : JNI_ABORT);
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, pipeCreate)(TCN_STDARGS, jlongArray io, jlong pool)
+{
+    apr_status_t rv;
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    jsize npipes = (*e)->GetArrayLength(e, io);
+    jlong *pipes = (*e)->GetLongArrayElements(e, io, NULL);
+    apr_file_t *in;
+    apr_file_t *out;
+
+    UNREFERENCED(o);
+    if (npipes < 2) {
+        (*e)->ReleaseLongArrayElements(e, io, pipes, JNI_ABORT);
+        return APR_EINVAL;
+    }
+
+    rv = apr_file_pipe_create(&in, &out, p);
+    if (rv == APR_SUCCESS) {
+        pipes[0] = P2J(in);
+        pipes[1] = P2J(out);
+        (*e)->ReleaseLongArrayElements(e, io, pipes, 0);
+    }
+    else
+        (*e)->ReleaseLongArrayElements(e, io, pipes, JNI_ABORT);
+
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, pipeTimeoutSet)(TCN_STDARGS, jlong pipe,
+                                               jlong timeout)
+{
+    apr_file_t *f = J2P(pipe, apr_file_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_file_pipe_timeout_set(f, J2T(timeout));
+}
+
+TCN_IMPLEMENT_CALL(jlong, File, pipeTimeoutGet)(TCN_STDARGS, jlong pipe)
+{
+    apr_file_t *f = J2P(pipe, apr_file_t *);
+    apr_interval_time_t timeout;
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_file_pipe_timeout_get(f, &timeout), timeout);
+
+cleanup:
+    return (jlong)timeout;
+}
+
+TCN_IMPLEMENT_CALL(jlong, File, dup)(TCN_STDARGS, jlong newf, jlong file,
+                                     jlong pool)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_file_t *d = J2P(newf, apr_file_t *);
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_file_dup(&d, f, p), d);
+
+cleanup:
+    return P2J(d);
+}
+
+TCN_IMPLEMENT_CALL(jint, File, dup2)(TCN_STDARGS, jlong newf, jlong file,
+                                     jlong pool)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_file_t *d = J2P(newf, apr_file_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_file_dup2(d, f, p);
+}
diff --git a/c/info.c b/c/info.c
new file mode 100644
index 0000000..222c27e
--- /dev/null
+++ b/c/info.c
@@ -0,0 +1,357 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: info.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+ 
+#include "tcn.h"
+#include "apr_file_io.h"
+
+#define DECLARE_FINFO_FIELD(name) static jfieldID _fid##name = NULL
+#define FINFO_FIELD(name)         _fid##name
+
+#define GET_FINFO_I(N)      \
+    _fid##N = (*e)->GetFieldID(e, finfo, #N, "I");  \
+    if (_fid##N == NULL) {                          \
+        (*e)->ExceptionClear(e);                    \
+        goto cleanup;                               \
+    } else (void)(0)
+
+#define GET_FINFO_J(N)      \
+    _fid##N = (*e)->GetFieldID(e, finfo, #N, "J");  \
+    if (_fid##N == NULL) {                          \
+        (*e)->ExceptionClear(e);                    \
+        goto cleanup;                               \
+    } else (void)(0)
+
+#define GET_FINFO_S(N)      \
+    _fid##N = (*e)->GetFieldID(e, finfo, #N,        \
+                             "Ljava/lang/String;"); \
+    if (_fid##N == NULL) {                          \
+        (*e)->ExceptionClear(e);                    \
+        goto cleanup;                               \
+    } else (void)(0)
+
+#define SET_FINFO_I(N, V)  \
+    (*e)->SetIntField(e, obj, _fid##N, (jint)(V))
+
+#define SET_FINFO_J(N, V)  \
+    (*e)->SetLongField(e, obj, _fid##N, (jlong)(V))
+
+#define SET_FINFO_S(N, V)                 \
+    (*e)->SetObjectField(e, obj, _fid##N, \
+        (V) ? AJP_TO_JSTRING((V)) : NULL)
+
+
+#define DECLARE_AINFO_FIELD(name) static jfieldID _aid##name = NULL
+#define AINFO_FIELD(name)         _aid##name
+
+#define GET_AINFO_I(N)      \
+    _aid##N = (*e)->GetFieldID(e, ainfo, #N, "I");  \
+    if (_aid##N == NULL) {                          \
+        (*e)->ExceptionClear(e);                    \
+        goto cleanup;                               \
+    } else (void)(0)
+
+#define GET_AINFO_J(N)      \
+    _aid##N = (*e)->GetFieldID(e, ainfo, #N, "J");  \
+    if (_aid##N == NULL) {                          \
+        (*e)->ExceptionClear(e);                    \
+        goto cleanup;                               \
+    } else (void)(0)
+
+#define GET_AINFO_S(N)      \
+    _aid##N = (*e)->GetFieldID(e, ainfo, #N,        \
+                             "Ljava/lang/String;"); \
+    if (_aid##N == NULL) {                          \
+        (*e)->ExceptionClear(e);                    \
+        goto cleanup;                               \
+    } else (void)(0)
+
+#define SET_AINFO_I(N, V)  \
+    (*e)->SetIntField(e, obj, _aid##N, (jint)(V))
+
+#define SET_AINFO_J(N, V)  \
+    (*e)->SetLongField(e, obj, _aid##N, (jlong)(V))
+
+#define SET_AINFO_S(N, V)                 \
+    (*e)->SetObjectField(e, obj, _aid##N, \
+        (V) ? AJP_TO_JSTRING((V)) : NULL)
+
+
+DECLARE_FINFO_FIELD(pool);
+DECLARE_FINFO_FIELD(valid);
+DECLARE_FINFO_FIELD(protection);
+DECLARE_FINFO_FIELD(filetype);
+DECLARE_FINFO_FIELD(user);
+DECLARE_FINFO_FIELD(group);
+DECLARE_FINFO_FIELD(inode);
+DECLARE_FINFO_FIELD(device);
+DECLARE_FINFO_FIELD(nlink);
+DECLARE_FINFO_FIELD(size);
+DECLARE_FINFO_FIELD(csize);
+DECLARE_FINFO_FIELD(atime);
+DECLARE_FINFO_FIELD(mtime);
+DECLARE_FINFO_FIELD(ctime);
+DECLARE_FINFO_FIELD(fname);
+DECLARE_FINFO_FIELD(name);
+DECLARE_FINFO_FIELD(filehand);
+
+DECLARE_AINFO_FIELD(pool);
+DECLARE_AINFO_FIELD(hostname);
+DECLARE_AINFO_FIELD(servname);
+DECLARE_AINFO_FIELD(port);
+DECLARE_AINFO_FIELD(family);
+DECLARE_AINFO_FIELD(next);
+
+static int finfo_class_initialized = 0;
+static int ainfo_class_initialized = 0;
+static jmethodID finfo_class_init = NULL;
+static jmethodID ainfo_class_init = NULL;
+static jclass finfo_class = NULL;
+static jclass ainfo_class = NULL;
+
+apr_status_t tcn_load_finfo_class(JNIEnv *e, jclass finfo)
+{
+    GET_FINFO_J(pool);
+    GET_FINFO_I(valid);
+    GET_FINFO_I(protection);
+    GET_FINFO_I(filetype);
+    GET_FINFO_I(user);
+    GET_FINFO_I(group);
+    GET_FINFO_I(inode);
+    GET_FINFO_I(device);
+    GET_FINFO_I(nlink);
+    GET_FINFO_J(size);
+    GET_FINFO_J(csize);
+    GET_FINFO_J(atime);
+    GET_FINFO_J(mtime);
+    GET_FINFO_J(ctime);
+    GET_FINFO_S(fname);
+    GET_FINFO_S(name);
+    GET_FINFO_J(filehand);
+    
+    finfo_class_init = (*e)->GetMethodID(e, finfo,
+                                      "<init>", "()V");
+    if (finfo_class_init == NULL)
+        goto cleanup;
+    finfo_class_initialized = 1;
+    finfo_class = finfo;
+cleanup:
+    return APR_SUCCESS;
+}
+
+apr_status_t tcn_load_ainfo_class(JNIEnv *e, jclass ainfo)
+{
+    GET_AINFO_J(pool);
+    GET_AINFO_S(hostname);
+    GET_AINFO_S(servname);
+    GET_AINFO_I(port);
+    GET_AINFO_I(family);
+    GET_AINFO_J(next);
+    ainfo_class_init = (*e)->GetMethodID(e, ainfo,
+                                      "<init>", "()V");
+
+    if (ainfo_class_init == NULL)
+        goto cleanup;
+    ainfo_class_initialized = 1;
+    ainfo_class = ainfo;
+cleanup:
+    return APR_SUCCESS;
+}
+
+static void fill_finfo(JNIEnv *e, jobject obj, apr_finfo_t *info)
+{
+
+    SET_FINFO_J(pool, P2J(info->pool));
+    SET_FINFO_I(valid, info->valid);
+    SET_FINFO_I(protection, info->protection);
+    SET_FINFO_I(filetype, info->filetype);
+    SET_FINFO_I(user, ((jlong)info->user));
+    SET_FINFO_I(group, ((jlong)info->group));
+    SET_FINFO_I(inode, info->inode);
+    SET_FINFO_I(device, info->device);
+    SET_FINFO_I(nlink, info->nlink);
+    SET_FINFO_J(size, info->size);
+    SET_FINFO_J(csize, info->csize);
+    SET_FINFO_J(atime, info->atime);
+    SET_FINFO_J(mtime, info->mtime);
+    SET_FINFO_J(ctime, info->ctime);
+    SET_FINFO_S(fname, info->fname);
+    SET_FINFO_S(name, info->name);
+    SET_FINFO_J(filehand, P2J(info->filehand));
+}
+
+static void fill_ainfo(JNIEnv *e, jobject obj, apr_sockaddr_t *info)
+{
+    apr_int32_t f;
+    if (info->family == APR_UNSPEC) f = 0;
+    else if (info->family == APR_INET) f = 1;
+    else if (info->family == APR_INET6) f = 2;
+    else f = info->family;
+
+    SET_AINFO_J(pool, P2J(info->pool));
+    SET_AINFO_S(hostname, info->hostname);
+    SET_AINFO_S(servname, info->servname);
+    SET_AINFO_I(port, info->port);
+    SET_AINFO_I(family, f);
+    SET_AINFO_J(next, P2J(info->next));
+
+}
+
+TCN_IMPLEMENT_CALL(jint, File, stat)(TCN_STDARGS, jobject finfo,
+                                     jstring fname, jint wanted,
+                                     jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    TCN_ALLOC_CSTRING(fname);
+    apr_status_t rv;
+    apr_finfo_t info;
+
+    UNREFERENCED(o);
+
+    if ((rv =  apr_stat(&info, J2S(fname), wanted, p)) == APR_SUCCESS) {
+        jobject io = (*e)->NewLocalRef(e, finfo);
+        fill_finfo(e, io, &info);
+        (*e)->DeleteLocalRef(e, io);
+    }
+    TCN_FREE_CSTRING(fname);
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jobject, File, getStat)(TCN_STDARGS, jstring fname,
+                                           jint wanted, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    TCN_ALLOC_CSTRING(fname);
+    apr_status_t rv;
+    apr_finfo_t info;
+    jobject finfo = NULL;
+
+    UNREFERENCED(o);
+
+    if ((rv =  apr_stat(&info, J2S(fname), wanted, p)) == APR_SUCCESS) {
+        finfo = (*e)->NewObject(e, finfo_class, finfo_class_init);
+        if (finfo == NULL)
+            goto cleanup;
+        fill_finfo(e, finfo, &info);
+    }
+    else
+        tcn_ThrowAPRException(e, rv);
+cleanup:
+    TCN_FREE_CSTRING(fname);
+    return finfo;
+}
+
+TCN_IMPLEMENT_CALL(jint, File, infoGet)(TCN_STDARGS, jobject finfo,
+                                        jint wanted, jlong file)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_status_t rv;
+    apr_finfo_t info;
+
+    UNREFERENCED(o);
+
+    if ((rv =  apr_file_info_get(&info, wanted, f)) == APR_SUCCESS) {
+        jobject io = (*e)->NewLocalRef(e, finfo);
+        fill_finfo(e, io, &info);
+        (*e)->DeleteLocalRef(e, io);
+    }
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jobject, File, getInfo)(TCN_STDARGS, jint wanted, jlong file)
+{
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_status_t rv;
+    apr_finfo_t  info;
+
+    UNREFERENCED(o);
+
+    if ((rv =  apr_file_info_get(&info, wanted, f)) == APR_SUCCESS) {
+        jobject finfo;
+        finfo = (*e)->NewObject(e, finfo_class, finfo_class_init);
+        if (finfo == NULL)
+            return NULL;
+        fill_finfo(e, finfo, &info);
+        return finfo;
+    }
+    else
+        tcn_ThrowAPRException(e, rv);
+    return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jint, Directory, read)(TCN_STDARGS, jobject finfo,
+                                          jint wanted, jlong dir)
+{
+    apr_dir_t *d = J2P(dir, apr_dir_t *);
+    apr_status_t rv;
+    apr_finfo_t info;
+
+    UNREFERENCED(o);
+
+    if ((rv =  apr_dir_read(&info, wanted, d)) == APR_SUCCESS) {
+        jobject io = (*e)->NewLocalRef(e, finfo);
+        fill_finfo(e, io, &info);
+        if ((*e)->ExceptionCheck(e)) {
+            (*e)->ExceptionClear(e);
+        }
+        else
+            rv = APR_EGENERAL;
+        (*e)->DeleteLocalRef(e, io);
+    }
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, Address, fill)(TCN_STDARGS,
+                                            jobject addr, jlong info)
+{
+    apr_sockaddr_t *i = J2P(info, apr_sockaddr_t *);
+    jobject ao;
+    jboolean rv = JNI_FALSE;
+
+    UNREFERENCED(o);
+
+    if (i) {
+        ao = (*e)->NewLocalRef(e, addr);
+        fill_ainfo(e, ao, i);
+        if ((*e)->ExceptionCheck(e)) {
+            (*e)->ExceptionClear(e);
+        }
+        else
+            rv = JNI_TRUE;
+        (*e)->DeleteLocalRef(e, ao);
+    }
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jobject, Address, getInfo)(TCN_STDARGS, jlong info)
+{
+    apr_sockaddr_t *i = J2P(info, apr_sockaddr_t *);
+    jobject sockaddrObj = NULL;
+
+    UNREFERENCED(o);
+
+    /* Create the APR Error object */
+    sockaddrObj = (*e)->NewObject(e, ainfo_class, ainfo_class_init);
+    if (sockaddrObj == NULL)
+        return NULL;
+    fill_ainfo(e, sockaddrObj, i);
+    return sockaddrObj;
+}
diff --git a/c/jnilib.c b/c/jnilib.c
new file mode 100644
index 0000000..0110ad0
--- /dev/null
+++ b/c/jnilib.c
@@ -0,0 +1,481 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: jnilib.c 1666217 2015-03-12 15:03:31Z rjung $
+ */
+
+#include "tcn.h"
+#include "apr_version.h"
+#include "apr_file_io.h"
+#include "apr_mmap.h"
+#include "apr_atomic.h"
+
+#include "tcn_version.h"
+
+#ifdef TCN_DO_STATISTICS
+extern void sp_poll_dump_statistics();
+extern void sp_network_dump_statistics();
+extern void ssl_network_dump_statistics();
+#endif
+
+apr_pool_t *tcn_global_pool = NULL;
+static JavaVM     *tcn_global_vm = NULL;
+
+static jclass    jString_class;
+static jclass    jFinfo_class;
+static jclass    jAinfo_class;
+static jmethodID jString_init;
+static jmethodID jString_getBytes;
+
+int tcn_parent_pid = 0;
+
+/* Called by the JVM when APR_JAVA is loaded */
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
+{
+    JNIEnv *env;
+    apr_version_t apv;
+    int apvn;
+
+    UNREFERENCED(reserved);
+    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4)) {
+        return JNI_ERR;
+    }
+    tcn_global_vm = vm;
+
+    /* Before doing anything else check if we have a valid
+     * APR version.
+     */
+    apr_version(&apv);
+    apvn = apv.major * 1000 + apv.minor * 100 + apv.patch;
+    if (apvn < 1201) {
+        tcn_Throw(env, "Unupported APR version (%s)",
+                  apr_version_string());
+        return JNI_ERR;
+    }
+
+
+    /* Initialize global java.lang.String class */
+    TCN_LOAD_CLASS(env, jString_class, "java/lang/String", JNI_ERR);
+    TCN_LOAD_CLASS(env, jFinfo_class, TCN_FINFO_CLASS, JNI_ERR);
+    TCN_LOAD_CLASS(env, jAinfo_class, TCN_AINFO_CLASS, JNI_ERR);
+
+    TCN_GET_METHOD(env, jString_class, jString_init,
+                   "<init>", "([B)V", JNI_ERR);
+    TCN_GET_METHOD(env, jString_class, jString_getBytes,
+                   "getBytes", "()[B", JNI_ERR);
+
+    if(tcn_load_finfo_class(env, jFinfo_class) != APR_SUCCESS)
+        return JNI_ERR;
+    if(tcn_load_ainfo_class(env, jAinfo_class) != APR_SUCCESS)
+        return JNI_ERR;
+#ifdef WIN32
+    {
+        char *ppid = getenv(TCN_PARENT_IDE);
+        if (ppid)
+            tcn_parent_pid = atoi(ppid);
+    }
+#else
+    tcn_parent_pid = getppid();
+#endif
+
+    return  JNI_VERSION_1_4;
+}
+
+
+/* Called by the JVM before the APR_JAVA is unloaded */
+JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved)
+{
+    JNIEnv *env;
+
+    UNREFERENCED(reserved);
+
+    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_2)) {
+        return;
+    }
+    if (tcn_global_pool) {
+        TCN_UNLOAD_CLASS(env, jString_class);
+        TCN_UNLOAD_CLASS(env, jFinfo_class);
+        TCN_UNLOAD_CLASS(env, jAinfo_class);
+        apr_terminate();
+    }
+}
+
+jstring tcn_new_stringn(JNIEnv *env, const char *str, size_t l)
+{
+    jstring result;
+    jbyteArray bytes = 0;
+
+    if (!str)
+        return NULL;
+    if ((*env)->EnsureLocalCapacity(env, 2) < 0) {
+        return NULL; /* out of memory error */
+    }
+    bytes = (*env)->NewByteArray(env, l);
+    if (bytes != NULL) {
+        (*env)->SetByteArrayRegion(env, bytes, 0, l, (jbyte *)str);
+        result = (*env)->NewObject(env, jString_class, jString_init, bytes);
+        (*env)->DeleteLocalRef(env, bytes);
+        return result;
+    } /* else fall through */
+    return NULL;
+}
+
+jbyteArray tcn_new_arrayb(JNIEnv *env, const unsigned char *data, size_t len)
+{
+    jbyteArray bytes = (*env)->NewByteArray(env, (jsize)len);
+    if (bytes != NULL) {
+        (*env)->SetByteArrayRegion(env, bytes, 0, (jint)len, (jbyte *)data);
+    }
+    return bytes;
+}
+
+jobjectArray tcn_new_arrays(JNIEnv *env, size_t len)
+{
+    return (*env)->NewObjectArray(env, (jsize)len, jString_class, NULL);
+}
+
+jstring tcn_new_string(JNIEnv *env, const char *str)
+{
+    if (!str)
+        return NULL;
+    else
+        return (*env)->NewStringUTF(env, str);
+}
+
+char *tcn_get_string(JNIEnv *env, jstring jstr)
+{
+    jbyteArray bytes = NULL;
+    jthrowable exc;
+    char *result = NULL;
+
+    if ((*env)->EnsureLocalCapacity(env, 2) < 0) {
+        return NULL; /* out of memory error */
+    }
+    bytes = (*env)->CallObjectMethod(env, jstr, jString_getBytes);
+    exc = (*env)->ExceptionOccurred(env);
+    if (!exc) {
+        jint len = (*env)->GetArrayLength(env, bytes);
+        result = (char *)malloc(len + 1);
+        if (result == NULL) {
+            TCN_THROW_OS_ERROR(env);
+            (*env)->DeleteLocalRef(env, bytes);
+            return 0;
+        }
+        (*env)->GetByteArrayRegion(env, bytes, 0, len, (jbyte *)result);
+        result[len] = '\0'; /* NULL-terminate */
+    }
+    else {
+        (*env)->DeleteLocalRef(env, exc);
+    }
+    (*env)->DeleteLocalRef(env, bytes);
+
+    return result;
+}
+
+char *tcn_strdup(JNIEnv *env, jstring jstr)
+{
+    char *result = NULL;
+    const char *cjstr;
+
+    cjstr = (const char *)((*env)->GetStringUTFChars(env, jstr, 0));
+    if (cjstr) {
+        result = strdup(cjstr);
+        (*env)->ReleaseStringUTFChars(env, jstr, cjstr);
+    }
+    return result;
+}
+
+char *tcn_pstrdup(JNIEnv *env, jstring jstr, apr_pool_t *pool)
+{
+    char *result = NULL;
+    const char *cjstr;
+
+    cjstr = (const char *)((*env)->GetStringUTFChars(env, jstr, 0));
+    if (cjstr) {
+        result = apr_pstrdup(pool, cjstr);
+        (*env)->ReleaseStringUTFChars(env, jstr, cjstr);
+    }
+    return result;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, Library, initialize)(TCN_STDARGS)
+{
+
+    UNREFERENCED_STDARGS;
+    if (!tcn_global_pool) {
+        apr_initialize();
+        if (apr_pool_create(&tcn_global_pool, NULL) != APR_SUCCESS) {
+            return JNI_FALSE;
+        }
+        apr_atomic_init(tcn_global_pool);
+    }
+    return JNI_TRUE;
+}
+
+TCN_IMPLEMENT_CALL(void, Library, terminate)(TCN_STDARGS)
+{
+
+    UNREFERENCED_STDARGS;
+    if (tcn_global_pool) {        
+        apr_pool_t *p = tcn_global_pool;
+        tcn_global_pool = NULL;
+#ifdef TCN_DO_STATISTICS
+        fprintf(stderr, "APR Statistical data ....\n");
+#endif
+        apr_pool_destroy(p);
+#ifdef TCN_DO_STATISTICS
+        sp_poll_dump_statistics();
+        sp_network_dump_statistics();
+        ssl_network_dump_statistics();
+        fprintf(stderr, "APR Terminated\n");
+#endif
+        apr_terminate();
+    }
+}
+
+TCN_IMPLEMENT_CALL(jlong, Library, globalPool)(TCN_STDARGS)
+{
+    UNREFERENCED_STDARGS;
+    return P2J(tcn_global_pool);
+}
+
+TCN_IMPLEMENT_CALL(jint, Library, version)(TCN_STDARGS, jint what)
+{
+    apr_version_t apv;
+
+    UNREFERENCED_STDARGS;
+    apr_version(&apv);
+
+    switch (what) {
+        case 0x01:
+            return TCN_MAJOR_VERSION;
+        break;
+        case 0x02:
+            return TCN_MINOR_VERSION;
+        break;
+        case 0x03:
+            return TCN_PATCH_VERSION;
+        break;
+        case 0x04:
+            return TCN_IS_DEV_VERSION;
+        break;
+        case 0x11:
+            return apv.major;
+        break;
+        case 0x12:
+            return apv.minor;
+        break;
+        case 0x13:
+            return apv.patch;
+        break;
+        case 0x14:
+            return apv.is_dev;
+        break;
+    }
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jstring, Library, versionString)(TCN_STDARGS)
+{
+    UNREFERENCED(o);
+    return AJP_TO_JSTRING(TCN_VERSION_STRING);
+}
+
+TCN_IMPLEMENT_CALL(jstring, Library, aprVersionString)(TCN_STDARGS)
+{
+    UNREFERENCED(o);
+    return AJP_TO_JSTRING(apr_version_string());
+}
+
+TCN_IMPLEMENT_CALL(jboolean, Library, has)(TCN_STDARGS, jint what)
+{
+    jboolean rv = JNI_FALSE;
+    UNREFERENCED_STDARGS;
+    switch (what) {
+        case 0:
+#if APR_HAVE_IPV6
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 1:
+#if APR_HAS_SHARED_MEMORY
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 2:
+#if APR_HAS_THREADS
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 3:
+#if APR_HAS_SENDFILE
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 4:
+#if APR_HAS_MMAP
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 5:
+#if APR_HAS_FORK
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 6:
+#if APR_HAS_RANDOM
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 7:
+#if APR_HAS_OTHER_CHILD
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 8:
+#if APR_HAS_DSO
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 9:
+#if APR_HAS_SO_ACCEPTFILTER
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 10:
+#if APR_HAS_UNICODE_FS
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 11:
+#if APR_HAS_PROC_INVOKED
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 12:
+#if APR_HAS_USER
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 13:
+#if APR_HAS_LARGE_FILES
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 14:
+#if APR_HAS_XTHREAD_FILES
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 15:
+#if APR_HAS_OS_UUID
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 16:
+#if APR_IS_BIGENDIAN
+            rv = JNI_TRUE;
+#endif
+        break;
+
+        case 17:
+#if APR_FILES_AS_SOCKETS
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 18:
+#if APR_CHARSET_EBCDIC
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 19:
+#if APR_TCP_NODELAY_INHERITED
+            rv = JNI_TRUE;
+#endif
+        break;
+        case 20:
+#if APR_O_NONBLOCK_INHERITED
+            rv = JNI_TRUE;
+#endif
+        break;
+    }
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, Library, size)(TCN_STDARGS, jint what)
+{
+
+    UNREFERENCED_STDARGS;
+
+    switch (what) {
+        case 1:
+            return APR_SIZEOF_VOIDP;
+        break;
+        case 2:
+            return APR_PATH_MAX;
+        break;
+        case 3:
+            return APRMAXHOSTLEN;
+        break;
+        case 4:
+            return APR_MAX_IOVEC_SIZE;
+        break;
+        case 5:
+            return APR_MAX_SECS_TO_LINGER;
+        break;
+        case 6:
+            return APR_MMAP_THRESHOLD;
+        break;
+        case 7:
+            return APR_MMAP_LIMIT;
+        break;
+
+    }
+    return 0;
+}
+
+apr_pool_t *tcn_get_global_pool()
+{
+    if (!tcn_global_pool) {
+        if (apr_pool_create(&tcn_global_pool, NULL) != APR_SUCCESS) {
+            return NULL;
+        }
+        apr_atomic_init(tcn_global_pool);
+    }
+    return tcn_global_pool;
+}
+
+jclass tcn_get_string_class()
+{
+    return jString_class;
+}
+
+JavaVM * tcn_get_java_vm()
+{
+    return tcn_global_vm;
+}
+
+jint tcn_get_java_env(JNIEnv **env)
+{
+    if ((*tcn_global_vm)->GetEnv(tcn_global_vm, (void **)env,
+                                 JNI_VERSION_1_4)) {
+        return JNI_ERR;
+    }
+    return JNI_OK;
+}
diff --git a/c/lock.c b/c/lock.c
new file mode 100644
index 0000000..c574d96
--- /dev/null
+++ b/c/lock.c
@@ -0,0 +1,202 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: lock.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+ 
+#include "tcn.h"
+#include "apr_proc_mutex.h"
+#include "apr_global_mutex.h"
+
+TCN_IMPLEMENT_CALL(jlong, Lock, create)(TCN_STDARGS,
+                                        jstring fname,
+                                        jint mech, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_proc_mutex_t *mutex;
+    TCN_ALLOC_CSTRING(fname);
+
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_proc_mutex_create(&mutex, J2S(fname),
+                                (apr_lockmech_e)mech, p), mutex);
+
+cleanup:
+    TCN_FREE_CSTRING(fname);
+    return P2J(mutex);
+}
+
+TCN_IMPLEMENT_CALL(jlong, Lock, childInit)(TCN_STDARGS,
+                                           jstring fname,
+                                           jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_proc_mutex_t *mutex;
+    TCN_ALLOC_CSTRING(fname);
+
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_proc_mutex_child_init(&mutex,
+                                   J2S(fname), p), mutex);
+
+cleanup:
+    TCN_FREE_CSTRING(fname);
+    return P2J(mutex);
+}
+
+TCN_IMPLEMENT_CALL(jint, Lock, lock)(TCN_STDARGS, jlong mutex)
+{
+    apr_proc_mutex_t *m = J2P(mutex, apr_proc_mutex_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_proc_mutex_lock(m);
+}
+
+TCN_IMPLEMENT_CALL(jint, Lock, trylock)(TCN_STDARGS, jlong mutex)
+{
+    apr_proc_mutex_t *m = J2P(mutex, apr_proc_mutex_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_proc_mutex_trylock(m);
+}
+
+TCN_IMPLEMENT_CALL(jint, Lock, unlock)(TCN_STDARGS, jlong mutex)
+{
+    apr_proc_mutex_t *m = J2P(mutex, apr_proc_mutex_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_proc_mutex_unlock(m);
+}
+
+TCN_IMPLEMENT_CALL(jint, Lock, destroy)(TCN_STDARGS, jlong mutex)
+{
+    apr_proc_mutex_t *m = J2P(mutex, apr_proc_mutex_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_proc_mutex_destroy(m);
+}
+
+#if 0
+/* There is bug in APR implementing that function */
+TCN_IMPLEMENT_CALL(jint, Lock, cleanup)(TCN_STDARGS, jlong mutex)
+{
+   void *m = J2P(mutex, void *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_proc_mutex_cleanup(m);
+}
+#endif
+
+TCN_IMPLEMENT_CALL(jstring, Lock, lockfile)(TCN_STDARGS, jlong mutex)
+{
+    apr_proc_mutex_t *m = J2P(mutex, apr_proc_mutex_t *);
+    const char *s = apr_proc_mutex_lockfile(m);
+
+    UNREFERENCED_STDARGS;
+    if (s)
+        return AJP_TO_JSTRING(s);
+    else
+        return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jstring, Lock, name)(TCN_STDARGS, jlong mutex)
+{
+    apr_proc_mutex_t *m = J2P(mutex, apr_proc_mutex_t *);
+
+    UNREFERENCED(o);
+    return AJP_TO_JSTRING(apr_proc_mutex_name(m));
+}
+
+TCN_IMPLEMENT_CALL(jstring, Lock, defname)(TCN_STDARGS)
+{
+
+    UNREFERENCED(o);
+    return AJP_TO_JSTRING(apr_proc_mutex_defname());
+}
+
+
+
+TCN_IMPLEMENT_CALL(jlong, Global, create)(TCN_STDARGS,
+                                          jstring fname,
+                                          jint mech, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_global_mutex_t *mutex;
+    TCN_ALLOC_CSTRING(fname);
+
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_global_mutex_create(&mutex, J2S(fname),
+                                (apr_lockmech_e)mech, p), mutex);
+
+cleanup:
+    TCN_FREE_CSTRING(fname);
+    return P2J(mutex);
+}
+
+TCN_IMPLEMENT_CALL(jlong, Global, childInit)(TCN_STDARGS,
+                                             jstring fname,
+                                             jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_global_mutex_t *mutex;
+    TCN_ALLOC_CSTRING(fname);
+
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_global_mutex_child_init(&mutex,
+                                   J2S(fname), p), mutex);
+
+cleanup:
+    TCN_FREE_CSTRING(fname);
+    return P2J(mutex);
+}
+
+TCN_IMPLEMENT_CALL(jint, Global, lock)(TCN_STDARGS, jlong mutex)
+{
+    apr_global_mutex_t *m = J2P(mutex, apr_global_mutex_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_global_mutex_lock(m);
+}
+
+TCN_IMPLEMENT_CALL(jint, Global, trylock)(TCN_STDARGS, jlong mutex)
+{
+    apr_global_mutex_t *m = J2P(mutex, apr_global_mutex_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_global_mutex_trylock(m);
+}
+
+TCN_IMPLEMENT_CALL(jint, Global, unlock)(TCN_STDARGS, jlong mutex)
+{
+    apr_global_mutex_t *m = J2P(mutex, apr_global_mutex_t*);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_global_mutex_unlock(m);
+}
+
+TCN_IMPLEMENT_CALL(jint, Global, destroy)(TCN_STDARGS, jlong mutex)
+{
+    apr_global_mutex_t *m = J2P(mutex, apr_global_mutex_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_global_mutex_destroy(m);
+}
+
diff --git a/c/misc.c b/c/misc.c
new file mode 100644
index 0000000..1db02ba
--- /dev/null
+++ b/c/misc.c
@@ -0,0 +1,81 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: misc.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+ 
+#include "tcn.h"
+#include "apr_time.h"
+
+TCN_IMPLEMENT_CALL(void, Time, sleep)(TCN_STDARGS, jlong t)
+{
+
+    UNREFERENCED_STDARGS;
+    apr_sleep((apr_interval_time_t)t);
+}
+
+TCN_IMPLEMENT_CALL(jint, OS, random)(TCN_STDARGS, jbyteArray buf,
+                                     jint len)
+{
+#if APR_HAS_RANDOM
+    apr_status_t rv;
+    jbyte *b = (*e)->GetPrimitiveArrayCritical(e, buf, NULL);
+
+    UNREFERENCED(o);
+    if ((rv = apr_generate_random_bytes((unsigned char *)b,
+            (apr_size_t)len)) == APR_SUCCESS)
+        (*e)->ReleasePrimitiveArrayCritical(e, buf, b, 0);
+    else
+        (*e)->ReleasePrimitiveArrayCritical(e, buf, b, JNI_ABORT);
+
+    if ((*e)->ExceptionCheck(e)) {
+        (*e)->ExceptionClear(e);
+        rv = APR_EGENERAL;
+    }
+    return (jint)rv;
+#else
+    return APR_ENOTIMPL;
+#endif
+}
+
+TCN_IMPLEMENT_CALL(jlong, Time, now)(TCN_STDARGS)
+{
+    UNREFERENCED_STDARGS;
+    return (jlong)apr_time_now();
+}
+
+TCN_IMPLEMENT_CALL(jstring, Time, rfc822)(TCN_STDARGS, jlong t)
+{
+    char ts[APR_RFC822_DATE_LEN];
+    UNREFERENCED(o);
+    if (apr_rfc822_date(ts, J2T(t)) == APR_SUCCESS)
+        return AJP_TO_JSTRING(ts);
+    else
+        return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jstring, Time, ctime)(TCN_STDARGS, jlong t)
+{
+    char ts[APR_CTIME_LEN];
+    UNREFERENCED(o);
+    if (apr_ctime(ts, J2T(t)) == APR_SUCCESS)
+        return AJP_TO_JSTRING(ts);
+    else
+        return NULL;
+}
diff --git a/c/mmap.c b/c/mmap.c
new file mode 100644
index 0000000..b560cba
--- /dev/null
+++ b/c/mmap.c
@@ -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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: mmap.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+ 
+#include "tcn.h"
+#include "apr_mmap.h"
+
+TCN_IMPLEMENT_CALL(jlong, Mmap, create)(TCN_STDARGS, jlong file,
+                                        jlong offset, jlong size,
+                                        jint flag, jlong pool)
+{
+#if APR_HAS_MMAP
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_mmap_t *m = NULL;
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_mmap_create(&m, f, (apr_off_t)offset,
+                                     (apr_size_t)size,
+                                     (apr_uint32_t)flag, p), m);
+
+cleanup:
+    return P2J(m);
+#else
+    UNREFERENCED(o);
+    tcn_ThrowAPRException(e, APR_ENOTIMPL);
+    return 0;
+#endif
+}
+
+TCN_IMPLEMENT_CALL(jlong, Mmap, dup)(TCN_STDARGS, jlong mmap,
+                                     jlong pool)
+{
+#if APR_HAS_MMAP
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_mmap_t *m = J2P(mmap, apr_mmap_t *);
+    apr_mmap_t *newm = NULL;
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_mmap_dup(&newm, m, p), newm);
+
+cleanup:
+    return P2J(newm);
+#else
+    UNREFERENCED(o);
+    tcn_ThrowAPRException(e, APR_ENOTIMPL);
+    return 0;
+#endif
+}
+
+TCN_IMPLEMENT_CALL(jint, Mmap, delete)(TCN_STDARGS, jlong mmap)
+{
+#if APR_HAS_MMAP
+    apr_mmap_t *m = J2P(mmap, apr_mmap_t *);
+
+    UNREFERENCED_STDARGS;
+    return apr_mmap_delete(m);
+
+#else
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(mmap);
+    return APR_ENOTIMPL;
+#endif
+}
+
+TCN_IMPLEMENT_CALL(jlong, Mmap, offset)(TCN_STDARGS, jlong mmap,
+                                        jlong offset)
+{
+#if APR_HAS_MMAP
+    apr_mmap_t *m = J2P(mmap, apr_mmap_t *);
+    void *r;
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_mmap_offset(&r, m, (apr_off_t)offset), r);
+
+cleanup:
+    return P2J(r);
+
+#else
+    UNREFERENCED(o);
+    tcn_ThrowAPRException(e, APR_ENOTIMPL);
+    return 0;
+#endif
+}
diff --git a/c/multicast.c b/c/multicast.c
new file mode 100644
index 0000000..755db9b
--- /dev/null
+++ b/c/multicast.c
@@ -0,0 +1,75 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: multicast.c 1650120 2015-01-07 17:17:37Z schultz $
+ */
+
+#include "tcn.h"
+
+TCN_IMPLEMENT_CALL(jint, Multicast, join)(TCN_STDARGS,
+                                          jlong sock, jlong join,
+                                          jlong iface, jlong source)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_sockaddr_t *ja = J2P(join, apr_sockaddr_t *);
+    apr_sockaddr_t *ia = J2P(iface, apr_sockaddr_t *);
+    apr_sockaddr_t *sa = J2P(source, apr_sockaddr_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_mcast_join(s->sock, ja, ia, sa);
+}
+
+TCN_IMPLEMENT_CALL(jint, Multicast, leave)(TCN_STDARGS,
+                                           jlong sock, jlong addr,
+                                           jlong iface, jlong source)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_sockaddr_t *aa = J2P(addr, apr_sockaddr_t *);
+    apr_sockaddr_t *ia = J2P(iface, apr_sockaddr_t *);
+    apr_sockaddr_t *sa = J2P(source, apr_sockaddr_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_mcast_leave(s->sock, aa, ia, sa);
+}
+
+TCN_IMPLEMENT_CALL(jint, Multicast, hops)(TCN_STDARGS,
+                                          jlong sock, jint ttl)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_mcast_hops(s->sock, (apr_byte_t)ttl);
+}
+
+TCN_IMPLEMENT_CALL(jint, Multicast, loopback)(TCN_STDARGS,
+                                              jlong sock, jboolean opt)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_byte_t on = 0;
+    UNREFERENCED_STDARGS;
+    if (opt)
+        on = 1;
+    return (jint)apr_mcast_loopback(s->sock, on);
+}
+
+TCN_IMPLEMENT_CALL(jint, Multicast, ointerface)(TCN_STDARGS,
+                                                jlong sock, jlong iface)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_sockaddr_t *ia = J2P(iface, apr_sockaddr_t *);
+    UNREFERENCED_STDARGS;
+    return (jint)apr_mcast_interface(s->sock, ia);
+}
diff --git a/c/network.c b/c/network.c
new file mode 100644
index 0000000..98aec7c
--- /dev/null
+++ b/c/network.c
@@ -0,0 +1,1445 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: network.c 1607296 2014-07-02 08:45:42Z kkolinko $
+ */
+
+#include "tcn.h"
+
+#ifdef TCN_DO_STATISTICS
+
+#include "apr_atomic.h"
+
+static volatile apr_uint32_t sp_created  = 0;
+static volatile apr_uint32_t sp_closed   = 0;
+static volatile apr_uint32_t sp_cleared  = 0;
+static volatile apr_uint32_t sp_accepted = 0;
+static volatile apr_uint32_t sp_max_send = 0;
+static volatile apr_uint32_t sp_min_send = 10000000;
+static volatile apr_uint32_t sp_num_send = 0;
+static volatile apr_off_t    sp_tot_send = 0;
+static volatile apr_uint32_t sp_max_recv = 0;
+static volatile apr_uint32_t sp_min_recv = 10000000;
+static volatile apr_uint32_t sp_num_recv = 0;
+static volatile apr_off_t    sp_tot_recv = 0;
+static volatile apr_uint32_t sp_err_recv = 0;
+static volatile apr_uint32_t sp_tmo_recv = 0;
+static volatile apr_uint32_t sp_rst_recv = 0;
+static volatile apr_status_t sp_erl_recv = 0;
+
+static volatile apr_size_t   sf_max_send = 0;
+static volatile apr_size_t   sf_min_send = 10000000;
+static volatile apr_uint32_t sf_num_send = 0;
+static volatile apr_off_t    sf_tot_send = 0;
+
+void sp_network_dump_statistics()
+{
+    fprintf(stderr, "Network Statistics ......\n");
+    fprintf(stderr, "Sockets created         : %d\n", sp_created);
+    fprintf(stderr, "Sockets accepted        : %d\n", sp_accepted);
+    fprintf(stderr, "Sockets closed          : %d\n", sp_closed);
+    fprintf(stderr, "Sockets cleared         : %d\n", sp_cleared);
+    fprintf(stderr, "Total send calls        : %d\n", sp_num_send);
+    fprintf(stderr, "Minimum send length     : %d\n", sp_min_send);
+    fprintf(stderr, "Maximum send length     : %d\n", sp_max_send);
+    fprintf(stderr, "Average send length     : %.2f\n", (double)sp_tot_send/(double)sp_num_send);
+    fprintf(stderr, "Total recv calls        : %d\n", sp_num_recv);
+    fprintf(stderr, "Minimum recv length     : %d\n", sp_min_recv);
+    fprintf(stderr, "Maximum recv length     : %d\n", sp_max_recv);
+    fprintf(stderr, "Average recv length     : %.2f\n", (double)sp_tot_recv/(double)sp_num_recv);
+    fprintf(stderr, "Receive timeouts        : %d\n", sp_tmo_recv);
+    fprintf(stderr, "Receive errors          : %d\n", sp_err_recv);
+    fprintf(stderr, "Receive resets          : %d\n", sp_rst_recv);
+    fprintf(stderr, "Last receive error      : %d\n", sp_erl_recv);
+
+    fprintf(stderr, "Total sendfile calls    : %d\n", sf_num_send);
+    fprintf(stderr, "Minimum sendfile length : %" APR_SIZE_T_FMT "\n", sf_min_send);
+    fprintf(stderr, "Maximum sendfile length : %" APR_SIZE_T_FMT "\n", sf_max_send);
+
+}
+
+#endif /* TCN_DO_STATISTICS */
+
+extern apr_pool_t *tcn_global_pool;
+static apr_status_t sp_socket_cleanup(void *data)
+{
+    tcn_socket_t *s = (tcn_socket_t *)data;
+
+    if (s->net && s->net->cleanup)
+        (*s->net->cleanup)(s->opaque);
+    if (s->sock) {
+        apr_socket_t *as = s->sock;
+        s->sock = NULL;
+        apr_socket_close(as);
+    }
+#ifdef TCN_DO_STATISTICS
+    apr_atomic_inc32(&sp_cleared);
+#endif
+    return APR_SUCCESS;
+}
+
+#if defined(DEBUG) || defined(_DEBUG)
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+APR_socket_send(apr_socket_t *sock, const char *buf, apr_size_t *len)
+{
+    return apr_socket_send(sock, buf, len);
+}
+
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+APR_socket_recv(apr_socket_t *sock, char *buf, apr_size_t *len)
+{
+    return apr_socket_recv(sock, buf, len);
+}
+
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+APR_socket_sendv(apr_socket_t *sock, const struct iovec *vec,
+                 apr_int32_t nvec, apr_size_t *len)
+{
+    return apr_socket_sendv(sock, vec, nvec, len);
+}
+
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+APR_socket_shutdown(apr_socket_t *sock, apr_shutdown_how_e how)
+{
+    return apr_socket_shutdown(sock, how);
+}
+
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+APR_socket_timeout_set(apr_socket_t *sock, apr_interval_time_t t)
+{
+    return apr_socket_timeout_set(sock, t);
+}
+
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+APR_socket_timeout_get(apr_socket_t *sock, apr_interval_time_t *t)
+{
+    return apr_socket_timeout_get(sock, t);
+}
+
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+APR_socket_opt_set(apr_socket_t *sock, apr_int32_t opt, apr_int32_t on)
+{
+    return apr_socket_opt_set(sock, opt, on);
+}
+
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+APR_socket_opt_get(apr_socket_t *sock, apr_int32_t opt, apr_int32_t *on)
+{
+    return apr_socket_opt_get(sock, opt, on);
+}
+
+#else
+#define APR_socket_send         apr_socket_send
+#define APR_socket_recv         apr_socket_recv
+#define APR_socket_sendv        apr_socket_sendv
+#define APR_socket_shutdown     apr_socket_shutdown
+#define APR_socket_timeout_set  apr_socket_timeout_set
+#define APR_socket_timeout_get  apr_socket_timeout_get
+#define APR_socket_opt_set      apr_socket_opt_set
+#define APR_socket_opt_get      apr_socket_opt_get
+#endif
+
+static tcn_nlayer_t apr_socket_layer = {
+    TCN_SOCKET_APR,
+    NULL,
+    NULL,
+    APR_socket_shutdown,
+    APR_socket_opt_get,
+    APR_socket_opt_set,
+    APR_socket_timeout_get,
+    APR_socket_timeout_set,
+    APR_socket_send,
+    APR_socket_sendv,
+    APR_socket_recv
+};
+
+TCN_IMPLEMENT_CALL(jlong, Socket, create)(TCN_STDARGS, jint family,
+                                          jint type, jint protocol,
+                                          jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_pool_t *c = NULL;
+    apr_socket_t *s = NULL;
+    tcn_socket_t *a = NULL;
+    apr_int32_t f, t;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pool != 0);
+    GET_S_FAMILY(f, family);
+    GET_S_TYPE(t, type);
+
+    TCN_THROW_IF_ERR(apr_pool_create(&c, p), c);
+
+    a = (tcn_socket_t *)apr_pcalloc(c, sizeof(tcn_socket_t));
+    TCN_CHECK_ALLOCATED(a);
+    TCN_THROW_IF_ERR(apr_pool_create(&a->child, c), a->child);
+    a->pool = c;
+
+    if (family >= 0) {
+        a->net = &apr_socket_layer;
+        TCN_THROW_IF_ERR(apr_socket_create(&s,
+                         f, t, protocol, c), a);
+    }
+    apr_pool_cleanup_register(c, (const void *)a,
+                              sp_socket_cleanup,
+                              apr_pool_cleanup_null);
+
+#ifdef TCN_DO_STATISTICS
+    sp_created++;
+#endif
+    a->sock = s;
+    if (family >= 0)
+        a->net = &apr_socket_layer;
+    a->opaque  = s;
+    return P2J(a);
+cleanup:
+    if (c)
+        apr_pool_destroy(c);
+    return 0;
+
+}
+
+TCN_IMPLEMENT_CALL(void, Socket, destroy)(TCN_STDARGS, jlong sock)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_socket_t *as;
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(sock != 0);
+
+    as = s->sock;
+    s->sock = NULL;
+    apr_pool_cleanup_kill(s->pool, s, sp_socket_cleanup);
+    if (s->net && s->net->cleanup) {
+        (*s->net->cleanup)(s->opaque);
+        s->net = NULL;
+    }
+    if (as) {
+        apr_socket_close(as);
+    }
+
+    apr_pool_destroy(s->pool);
+}
+
+TCN_IMPLEMENT_CALL(jlong, Socket, pool)(TCN_STDARGS, jlong sock)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_pool_t *n;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+    TCN_THROW_IF_ERR(apr_pool_create(&n, s->pool), n);
+cleanup:
+    return P2J(n);
+}
+
+TCN_IMPLEMENT_CALL(jlong, Socket, get)(TCN_STDARGS, jlong sock, jint what)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(sock != 0);
+
+    switch (what) {
+        case TCN_SOCKET_GET_POOL:
+            return P2J(s->pool);
+        break;
+        case TCN_SOCKET_GET_IMPL:
+            return P2J(s->opaque);
+        break;
+        case TCN_SOCKET_GET_APRS:
+            return P2J(s->sock);
+        break;
+        case TCN_SOCKET_GET_TYPE:
+            return (jlong)(s->net->type);
+        break;
+    }
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, shutdown)(TCN_STDARGS, jlong sock,
+                                           jint how)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(sock != 0);
+    return (jint)(*s->net->shutdown)(s->opaque, how);
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, close)(TCN_STDARGS, jlong sock)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    jint rv = APR_SUCCESS;
+    apr_socket_t *as;
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(sock != 0);
+
+    as = s->sock;
+    s->sock = NULL;
+    apr_pool_cleanup_kill(s->pool, s, sp_socket_cleanup);
+    if (s->child) {
+        apr_pool_clear(s->child);
+    }
+#ifdef TCN_DO_STATISTICS
+    apr_atomic_inc32(&sp_closed);
+#endif
+    if (s->net && s->net->close) {
+        rv = (*s->net->close)(s->opaque);
+        s->net = NULL;
+    }
+    if (as) {
+        rv = (jint)apr_socket_close(as);
+    }
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, bind)(TCN_STDARGS, jlong sock,
+                                       jlong sa)
+{
+    jint rv = APR_SUCCESS;
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_sockaddr_t *a = J2P(sa, apr_sockaddr_t *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(sock != 0);
+    TCN_ASSERT(s->sock != NULL);
+    rv = (jint)apr_socket_bind(s->sock, a);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, listen)(TCN_STDARGS, jlong sock,
+                                         jint backlog)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(sock != 0);
+    TCN_ASSERT(s->sock != NULL);
+    return (jint)apr_socket_listen(s->sock, backlog);
+}
+
+TCN_IMPLEMENT_CALL(jlong, Socket, acceptx)(TCN_STDARGS, jlong sock,
+                                           jlong pool)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_pool_t   *p = J2P(pool, apr_pool_t *);
+    apr_socket_t *n = NULL;
+    tcn_socket_t *a = NULL;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+
+    if (s->net->type == TCN_SOCKET_APR) {
+        TCN_ASSERT(s->sock != NULL);
+        a = (tcn_socket_t *)apr_pcalloc(p, sizeof(tcn_socket_t));
+        TCN_CHECK_ALLOCATED(a);
+        a->pool   = p;
+        apr_pool_cleanup_register(a->pool, (const void *)a,
+                                  sp_socket_cleanup,
+                                  apr_pool_cleanup_null);
+
+        TCN_THROW_IF_ERR(apr_socket_accept(&n, s->sock, p), n);
+    }
+    else {
+        tcn_ThrowAPRException(e, APR_ENOTIMPL);
+        goto cleanup;
+    }
+    if (n) {
+#ifdef TCN_DO_STATISTICS
+        apr_atomic_inc32(&sp_accepted);
+#endif
+        a->net    = &apr_socket_layer;
+        a->sock   = n;
+        a->opaque = n;
+    }
+
+cleanup:
+    return P2J(a);
+}
+
+TCN_IMPLEMENT_CALL(jlong, Socket, accept)(TCN_STDARGS, jlong sock)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_pool_t   *p = NULL;
+    apr_socket_t *n = NULL;
+    tcn_socket_t *a = NULL;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+
+    TCN_THROW_IF_ERR(apr_pool_create(&p, s->child), p);
+    if (s->net->type == TCN_SOCKET_APR) {
+        TCN_ASSERT(s->sock != NULL);
+        a = (tcn_socket_t *)apr_pcalloc(p, sizeof(tcn_socket_t));
+        TCN_CHECK_ALLOCATED(a);
+        TCN_THROW_IF_ERR(apr_socket_accept(&n, s->sock, p), n);
+
+        a->pool = p;
+        apr_pool_cleanup_register(a->pool, (const void *)a,
+                                  sp_socket_cleanup,
+                                  apr_pool_cleanup_null);
+
+    }
+    else {
+        tcn_ThrowAPRException(e, APR_ENOTIMPL);
+        goto cleanup;
+    }
+    if (n) {
+#ifdef TCN_DO_STATISTICS
+        apr_atomic_inc32(&sp_accepted);
+#endif
+        a->net    = &apr_socket_layer;
+        a->sock   = n;
+        a->opaque = n;
+    }
+    return P2J(a);
+cleanup:
+    if (tcn_global_pool && p && s->sock)
+        apr_pool_destroy(p);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, connect)(TCN_STDARGS, jlong sock,
+                                          jlong sa)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_sockaddr_t *a = J2P(sa, apr_sockaddr_t *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(sock != 0);
+    TCN_ASSERT(s->sock != NULL);
+    return (jint)apr_socket_connect(s->sock, a);
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, send)(TCN_STDARGS, jlong sock,
+                                      jbyteArray buf, jint offset, jint tosend)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_size_t nbytes = (apr_size_t)tosend;
+    apr_status_t ss;
+
+    UNREFERENCED(o);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return -(jint)APR_ENOTSOCK;
+    }
+    TCN_ASSERT(s->opaque != NULL);
+    if(!s->net) {
+        tcn_ThrowAPRException(e, APR_EINVALSOCK);
+        return -(jint)APR_EINVALSOCK;
+    }
+#ifdef TCN_DO_STATISTICS
+    sp_max_send = TCN_MAX(sp_max_send, nbytes);
+    sp_min_send = TCN_MIN(sp_min_send, nbytes);
+    sp_tot_send += nbytes;
+    sp_num_send++;
+#endif
+
+    if (tosend <= TCN_BUFFER_SZ) {
+        jbyte sb[TCN_BUFFER_SZ];
+        (*e)->GetByteArrayRegion(e, buf, offset, tosend, &sb[0]);
+        ss = (*s->net->send)(s->opaque, (const char *)&sb[0], &nbytes);
+    }
+    else {
+        jbyte *sb = (jbyte *)malloc(nbytes);
+        if (sb == NULL)
+            return -APR_ENOMEM;
+        (*e)->GetByteArrayRegion(e, buf, offset, tosend, sb);
+        ss = (*s->net->send)(s->opaque, (const char *)sb, &nbytes);
+        free(sb);
+    }
+    if (ss == APR_SUCCESS || ((APR_STATUS_IS_EAGAIN(ss) || ss == TCN_EAGAIN) && nbytes > 0))
+        return (jint)nbytes;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(void, Socket, setsbb)(TCN_STDARGS, jlong sock,
+                                         jobject buf)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    UNREFERENCED(o);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return;
+    }
+    TCN_ASSERT(s->opaque != NULL);
+    if (buf)
+        s->jsbbuff = (char *)(*e)->GetDirectBufferAddress(e, buf);
+    else
+        s->jsbbuff = NULL;
+}
+
+TCN_IMPLEMENT_CALL(void, Socket, setrbb)(TCN_STDARGS, jlong sock,
+                                         jobject buf)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    UNREFERENCED(o);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return;
+    }
+    TCN_ASSERT(s->opaque != NULL);
+    if (buf)
+        s->jrbbuff = (char *)(*e)->GetDirectBufferAddress(e, buf);
+    else
+        s->jrbbuff = NULL;
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, sendb)(TCN_STDARGS, jlong sock,
+                                        jobject buf, jint offset, jint len)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_size_t nbytes = (apr_size_t)len;
+    apr_size_t sent = 0;
+    char *bytes;
+    apr_status_t ss = APR_SUCCESS;
+
+    UNREFERENCED(o);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return -(jint)APR_ENOTSOCK;
+    }
+    TCN_ASSERT(s->opaque != NULL);
+    TCN_ASSERT(buf != NULL);
+    if(!s->net) {
+        tcn_ThrowAPRException(e, APR_EINVALSOCK);
+        return -(jint)APR_EINVALSOCK;
+    }
+#ifdef TCN_DO_STATISTICS
+    sp_max_send = TCN_MAX(sp_max_send, nbytes);
+    sp_min_send = TCN_MIN(sp_min_send, nbytes);
+    sp_tot_send += nbytes;
+    sp_num_send++;
+#endif
+
+    bytes  = (char *)(*e)->GetDirectBufferAddress(e, buf);
+
+    while (sent < nbytes) {
+        apr_size_t wr = nbytes - sent;
+        ss = (*s->net->send)(s->opaque, bytes + offset + sent, &wr);
+        if (ss != APR_SUCCESS)
+            break;
+        sent += wr;
+    }
+
+    if (ss == APR_SUCCESS || ((APR_STATUS_IS_EAGAIN(ss) || ss == TCN_EAGAIN) && sent > 0))
+        return (jint)sent;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, sendib)(TCN_STDARGS, jlong sock,
+                                         jobject buf, jint offset, jint len)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_size_t nbytes = (apr_size_t)len;
+    char *bytes;
+    apr_status_t ss = APR_SUCCESS;
+
+    UNREFERENCED(o);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return -(jint)APR_ENOTSOCK;
+    }
+    TCN_ASSERT(s->opaque != NULL);
+    TCN_ASSERT(buf != NULL);
+    if(!s->net) {
+        tcn_ThrowAPRException(e, APR_EINVALSOCK);
+        return -(jint)APR_EINVALSOCK;
+    }
+#ifdef TCN_DO_STATISTICS
+    sp_max_send = TCN_MAX(sp_max_send, nbytes);
+    sp_min_send = TCN_MIN(sp_min_send, nbytes);
+    sp_tot_send += nbytes;
+    sp_num_send++;
+#endif
+
+    bytes  = (char *)(*e)->GetDirectBufferAddress(e, buf);
+
+    ss = (*s->net->send)(s->opaque, bytes + offset, &nbytes);
+
+    if (ss == APR_SUCCESS || ((APR_STATUS_IS_EAGAIN(ss) || ss == TCN_EAGAIN) && nbytes > 0))
+        return (jint)nbytes;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, sendbb)(TCN_STDARGS, jlong sock,
+                                         jint offset, jint len)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_size_t nbytes = (apr_size_t)len;
+    apr_size_t sent = 0;
+    apr_status_t ss = APR_SUCCESS;
+
+    UNREFERENCED(o);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return -(jint)APR_ENOTSOCK;
+    }
+    TCN_ASSERT(s->opaque != NULL);
+    TCN_ASSERT(s->jsbbuff != NULL);
+    if(!s->net) {
+        tcn_ThrowAPRException(e, APR_EINVALSOCK);
+        return -(jint)APR_EINVALSOCK;
+    }
+#ifdef TCN_DO_STATISTICS
+    sp_max_send = TCN_MAX(sp_max_send, nbytes);
+    sp_min_send = TCN_MIN(sp_min_send, nbytes);
+    sp_tot_send += nbytes;
+    sp_num_send++;
+#endif
+
+    while (sent < nbytes) {
+        apr_size_t wr = nbytes - sent;
+        ss = (*s->net->send)(s->opaque, s->jsbbuff + offset + sent, &wr);
+        if (ss != APR_SUCCESS || wr == 0)
+            break;
+        sent += wr;
+    }
+    if (ss == APR_SUCCESS || ((APR_STATUS_IS_EAGAIN(ss) || ss == TCN_EAGAIN) && sent > 0))
+        return (jint)sent;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, sendibb)(TCN_STDARGS, jlong sock,
+                                          jint offset, jint len)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_size_t nbytes = (apr_size_t)len;
+    apr_status_t ss = APR_SUCCESS;
+
+    UNREFERENCED(o);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return -(jint)APR_ENOTSOCK;
+    }
+    TCN_ASSERT(s->opaque != NULL);
+    TCN_ASSERT(s->jsbbuff != NULL);
+    if(!s->net) {
+        tcn_ThrowAPRException(e, APR_EINVALSOCK);
+        return -(jint)APR_EINVALSOCK;
+    }
+#ifdef TCN_DO_STATISTICS
+    sp_max_send = TCN_MAX(sp_max_send, nbytes);
+    sp_min_send = TCN_MIN(sp_min_send, nbytes);
+    sp_tot_send += nbytes;
+    sp_num_send++;
+#endif
+
+    ss = (*s->net->send)(s->opaque, s->jsbbuff + offset, &nbytes);
+
+    if (ss == APR_SUCCESS || ((APR_STATUS_IS_EAGAIN(ss) || ss == TCN_EAGAIN) && nbytes > 0))
+        return (jint)nbytes;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, sendv)(TCN_STDARGS, jlong sock,
+                                        jobjectArray bufs)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    jsize nvec;
+    jsize i;
+    struct iovec vec[APR_MAX_IOVEC_SIZE];
+    jobject ba[APR_MAX_IOVEC_SIZE];
+    apr_size_t written = 0;
+    apr_status_t ss;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+    TCN_ASSERT(s->opaque != NULL);
+    if(!s->net) {
+        tcn_ThrowAPRException(e, APR_EINVALSOCK);
+        return -(jint)APR_EINVALSOCK;
+    }
+
+    nvec = (*e)->GetArrayLength(e, bufs);
+    if (nvec >= APR_MAX_IOVEC_SIZE)
+        return (jint)(-APR_ENOMEM);
+
+    for (i = 0; i < nvec; i++) {
+        ba[i] = (*e)->GetObjectArrayElement(e, bufs, i);
+        vec[i].iov_len  = (*e)->GetArrayLength(e, ba[i]);
+        vec[i].iov_base = (void *)((*e)->GetByteArrayElements(e, ba[i], NULL));
+    }
+
+    ss = (*s->net->sendv)(s->opaque, vec, nvec, &written);
+
+    for (i = 0; i < nvec; i++) {
+        (*e)->ReleaseByteArrayElements(e, ba[i], (jbyte*)vec[i].iov_base, JNI_ABORT);
+    }
+    if (ss == APR_SUCCESS || ((APR_STATUS_IS_EAGAIN(ss) || ss == TCN_EAGAIN) && written > 0))
+        return (jint)written;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, sendto)(TCN_STDARGS, jlong sock,
+                                         jlong where, jint flag,
+                                         jbyteArray buf, jint offset, jint tosend)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_sockaddr_t *w = J2P(where, apr_sockaddr_t *);
+    apr_size_t nbytes = (apr_size_t)tosend;
+    jbyte *bytes;
+    apr_int32_t nb;
+    apr_status_t ss;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+    TCN_ASSERT(s->sock != NULL);
+
+    apr_socket_opt_get(s->sock, APR_SO_NONBLOCK, &nb);
+    if (nb)
+         bytes = (*e)->GetPrimitiveArrayCritical(e, buf, NULL);
+    else
+         bytes = (*e)->GetByteArrayElements(e, buf, NULL);
+    TCN_ASSERT(bytes != NULL);
+    ss = apr_socket_sendto(s->sock, w, flag, (char *)(bytes + offset), &nbytes);
+
+    if (nb)
+        (*e)->ReleasePrimitiveArrayCritical(e, buf, bytes, 0);
+    else
+        (*e)->ReleaseByteArrayElements(e, buf, bytes, JNI_ABORT);
+    if (ss == APR_SUCCESS)
+        return (jint)nbytes;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, recv)(TCN_STDARGS, jlong sock,
+                                       jbyteArray buf, jint offset, jint toread)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_size_t nbytes = (apr_size_t)toread;
+    apr_status_t ss;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+    TCN_ASSERT(s->opaque != NULL);
+    if(!s->net) {
+        tcn_ThrowAPRException(e, APR_EINVALSOCK);
+        return -(jint)APR_EINVALSOCK;
+    }
+
+    if (toread <= TCN_BUFFER_SZ) {
+        char sb[TCN_BUFFER_SZ];
+
+        if ((ss = (*s->net->recv)(s->opaque, sb, &nbytes)) == APR_SUCCESS)
+            (*e)->SetByteArrayRegion(e, buf, offset, (jsize)nbytes, (jbyte*)&sb[0]);
+    }
+    else {
+        jbyte *bytes = (*e)->GetByteArrayElements(e, buf, NULL);
+        if ((ss = (*s->net->recv)(s->opaque, (char*)(bytes + offset),
+                                  &nbytes)) == APR_SUCCESS)
+            (*e)->ReleaseByteArrayElements(e, buf, bytes,
+                                           nbytes ? 0 : JNI_ABORT);
+    }
+#ifdef TCN_DO_STATISTICS
+    if (ss == APR_SUCCESS) {
+        sp_max_recv = TCN_MAX(sp_max_recv, nbytes);
+        sp_min_recv = TCN_MIN(sp_min_recv, nbytes);
+        sp_tot_recv += nbytes;
+        sp_num_recv++;
+    }
+    else {
+        if (APR_STATUS_IS_ETIMEDOUT(ss) ||
+            APR_STATUS_IS_TIMEUP(ss))
+            sp_tmo_recv++;
+        else if (APR_STATUS_IS_ECONNABORTED(ss) ||
+                 APR_STATUS_IS_ECONNRESET(ss) ||
+                 APR_STATUS_IS_EOF(ss))
+            sp_rst_recv++;
+        else {
+            sp_err_recv++;
+            sp_erl_recv = ss;
+        }
+    }
+#endif
+    if (ss == APR_SUCCESS)
+        return (jint)nbytes;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, recvt)(TCN_STDARGS, jlong sock,
+                                        jbyteArray buf, jint offset,
+                                        jint toread, jlong timeout)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_size_t nbytes = (apr_size_t)toread;
+    apr_status_t ss;
+    apr_interval_time_t pt;
+    apr_interval_time_t nt = J2T(timeout);
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+    TCN_ASSERT(s->opaque != NULL);
+    TCN_ASSERT(buf != NULL);
+    if(!s->net) {
+        tcn_ThrowAPRException(e, APR_EINVALSOCK);
+        return -(jint)APR_EINVALSOCK;
+    }
+
+    if ((ss = (*s->net->timeout_get)(s->opaque, &pt)) != APR_SUCCESS) {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+    if (pt != nt) {
+        if ((ss = (*s->net->timeout_set)(s->opaque, nt)) != APR_SUCCESS)
+            goto cleanup;
+    }
+    if (toread <= TCN_BUFFER_SZ) {
+        jbyte sb[TCN_BUFFER_SZ];
+        if ((ss = (*s->net->recv)(s->opaque, (char *)&sb[0], &nbytes)) == APR_SUCCESS)
+            (*e)->SetByteArrayRegion(e, buf, offset, (jsize)nbytes, &sb[0]);
+    }
+    else {
+        jbyte *sb = (jbyte *)malloc(nbytes);
+        if (sb == NULL)
+            return -APR_ENOMEM;
+        if ((ss = (*s->net->recv)(s->opaque, (char *)sb, &nbytes)) == APR_SUCCESS)
+            (*e)->SetByteArrayRegion(e, buf, offset, (jsize)nbytes, &sb[0]);
+        free(sb);
+    }
+    if (pt != nt) {
+        if ((ss = (*s->net->timeout_set)(s->opaque, pt)) != APR_SUCCESS)
+            goto cleanup;
+    }
+
+#ifdef TCN_DO_STATISTICS
+    if (ss == APR_SUCCESS) {
+        sp_max_recv = TCN_MAX(sp_max_recv, nbytes);
+        sp_min_recv = TCN_MIN(sp_min_recv, nbytes);
+        sp_tot_recv += nbytes;
+        sp_num_recv++;
+    }
+    else {
+        if (APR_STATUS_IS_ETIMEDOUT(ss) ||
+            APR_STATUS_IS_TIMEUP(ss))
+            sp_tmo_recv++;
+        else if (APR_STATUS_IS_ECONNABORTED(ss) ||
+                 APR_STATUS_IS_ECONNRESET(ss) ||
+                 APR_STATUS_IS_EOF(ss))
+            sp_rst_recv++;
+        else {
+            sp_err_recv++;
+            sp_erl_recv = ss;
+        }
+    }
+#endif
+cleanup:
+    if (ss == APR_SUCCESS)
+        return (jint)nbytes;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, recvb)(TCN_STDARGS, jlong sock,
+                                        jobject buf, jint offset, jint len)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_status_t ss;
+    apr_size_t nbytes = (apr_size_t)len;
+    char *bytes;
+
+    UNREFERENCED(o);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return -(jint)APR_ENOTSOCK;
+    }
+    TCN_ASSERT(s->opaque != NULL);
+    TCN_ASSERT(buf != NULL);
+    if(!s->net) {
+        tcn_ThrowAPRException(e, APR_EINVALSOCK);
+        return -(jint)APR_EINVALSOCK;
+    }
+
+    bytes  = (char *)(*e)->GetDirectBufferAddress(e, buf);
+    TCN_ASSERT(bytes != NULL);
+    ss = (*s->net->recv)(s->opaque, bytes + offset, &nbytes);
+#ifdef TCN_DO_STATISTICS
+    if (ss == APR_SUCCESS) {
+        sp_max_recv = TCN_MAX(sp_max_recv, nbytes);
+        sp_min_recv = TCN_MIN(sp_min_recv, nbytes);
+        sp_tot_recv += nbytes;
+        sp_num_recv++;
+    }
+    else {
+        if (APR_STATUS_IS_ETIMEDOUT(ss) ||
+            APR_STATUS_IS_TIMEUP(ss))
+            sp_tmo_recv++;
+        else if (APR_STATUS_IS_ECONNABORTED(ss) ||
+                 APR_STATUS_IS_ECONNRESET(ss) ||
+                 APR_STATUS_IS_EOF(ss))
+            sp_rst_recv++;
+        else {
+            sp_err_recv++;
+            sp_erl_recv = ss;
+        }
+    }
+#endif
+    if (ss == APR_SUCCESS)
+        return (jint)nbytes;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, recvbb)(TCN_STDARGS, jlong sock,
+                                         jint offset, jint len)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_status_t ss;
+    apr_size_t nbytes = (apr_size_t)len;
+
+    UNREFERENCED(o);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return -(jint)APR_ENOTSOCK;
+    }
+    TCN_ASSERT(sock != 0);
+    TCN_ASSERT(s->opaque != NULL);
+    TCN_ASSERT(s->jrbbuff != NULL);
+    if(!s->net) {
+        tcn_ThrowAPRException(e, APR_EINVALSOCK);
+        return -(jint)APR_EINVALSOCK;
+    }
+
+    ss = (*s->net->recv)(s->opaque, s->jrbbuff + offset, &nbytes);
+#ifdef TCN_DO_STATISTICS
+    if (ss == APR_SUCCESS) {
+        sp_max_recv = TCN_MAX(sp_max_recv, nbytes);
+        sp_min_recv = TCN_MIN(sp_min_recv, nbytes);
+        sp_tot_recv += nbytes;
+        sp_num_recv++;
+    }
+    else {
+        if (APR_STATUS_IS_ETIMEDOUT(ss) ||
+            APR_STATUS_IS_TIMEUP(ss))
+            sp_tmo_recv++;
+        else if (APR_STATUS_IS_ECONNABORTED(ss) ||
+                 APR_STATUS_IS_ECONNRESET(ss) ||
+                 APR_STATUS_IS_EOF(ss))
+            sp_rst_recv++;
+        else {
+            sp_err_recv++;
+            sp_erl_recv = ss;
+        }
+    }
+#endif
+    if (ss == APR_SUCCESS)
+        return (jint)nbytes;
+    else if (APR_STATUS_IS_EOF(ss))
+        return 0;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, recvbt)(TCN_STDARGS, jlong sock,
+                                         jobject buf, jint offset,
+                                         jint len, jlong timeout)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_status_t ss;
+    apr_size_t nbytes = (apr_size_t)len;
+    char *bytes;
+    apr_interval_time_t pt;
+    apr_interval_time_t nt = J2T(timeout);
+
+    UNREFERENCED(o);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return -(jint)APR_ENOTSOCK;
+    }
+    TCN_ASSERT(buf != NULL);
+    TCN_ASSERT(s->opaque != NULL);
+    if(!s->net) {
+        tcn_ThrowAPRException(e, APR_EINVALSOCK);
+        return -(jint)APR_EINVALSOCK;
+    }
+
+    bytes  = (char *)(*e)->GetDirectBufferAddress(e, buf);
+    TCN_ASSERT(bytes != NULL);
+
+    if ((ss = (*s->net->timeout_get)(s->opaque, &pt)) != APR_SUCCESS) {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+    if (pt != nt) {
+        if ((ss = (*s->net->timeout_set)(s->opaque, nt)) != APR_SUCCESS) {
+            TCN_ERROR_WRAP(ss);
+            return -(jint)ss;
+        }
+    }
+    ss = (*s->net->recv)(s->opaque, bytes + offset, &nbytes);
+    if (pt != nt) {
+        if ((ss = (*s->net->timeout_set)(s->opaque, pt)) != APR_SUCCESS) {
+            TCN_ERROR_WRAP(ss);
+            return -(jint)ss;
+        }
+    }
+
+#ifdef TCN_DO_STATISTICS
+    if (ss == APR_SUCCESS) {
+        sp_max_recv = TCN_MAX(sp_max_recv, nbytes);
+        sp_min_recv = TCN_MIN(sp_min_recv, nbytes);
+        sp_tot_recv += nbytes;
+        sp_num_recv++;
+    }
+    else {
+        if (APR_STATUS_IS_ETIMEDOUT(ss) ||
+            APR_STATUS_IS_TIMEUP(ss))
+            sp_tmo_recv++;
+        else if (APR_STATUS_IS_ECONNABORTED(ss) ||
+                 APR_STATUS_IS_ECONNRESET(ss) ||
+                 APR_STATUS_IS_EOF(ss))
+            sp_rst_recv++;
+        else {
+            sp_err_recv++;
+            sp_erl_recv = ss;
+        }
+    }
+#endif
+    if (ss == APR_SUCCESS)
+        return (jint)nbytes;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, recvbbt)(TCN_STDARGS, jlong sock,
+                                          jint offset,
+                                          jint len, jlong timeout)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_status_t ss;
+    apr_size_t nbytes = (apr_size_t)len;
+    apr_interval_time_t pt;
+    apr_interval_time_t nt = J2T(timeout);
+
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(o);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return -(jint)APR_ENOTSOCK;
+    }
+    TCN_ASSERT(s->jrbbuff != NULL);
+    TCN_ASSERT(s->opaque != NULL);
+    if(!s->net) {
+        tcn_ThrowAPRException(e, APR_EINVALSOCK);
+        return -(jint)APR_EINVALSOCK;
+    }
+
+    if ((ss = (*s->net->timeout_get)(s->opaque, &pt)) != APR_SUCCESS) {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+    if (pt != nt) {
+        if ((ss = (*s->net->timeout_set)(s->opaque, nt)) != APR_SUCCESS) {
+            TCN_ERROR_WRAP(ss);
+            return -(jint)ss;
+        }
+    }
+    ss = (*s->net->recv)(s->opaque, s->jrbbuff + offset, &nbytes);
+    if (pt != nt) {
+        if ((ss = (*s->net->timeout_set)(s->opaque, pt)) != APR_SUCCESS) {
+            TCN_ERROR_WRAP(ss);
+            return -(jint)ss;
+        }
+    }
+
+#ifdef TCN_DO_STATISTICS
+    if (ss == APR_SUCCESS) {
+        sp_max_recv = TCN_MAX(sp_max_recv, nbytes);
+        sp_min_recv = TCN_MIN(sp_min_recv, nbytes);
+        sp_tot_recv += nbytes;
+        sp_num_recv++;
+    }
+    else {
+        if (APR_STATUS_IS_ETIMEDOUT(ss) ||
+            APR_STATUS_IS_TIMEUP(ss))
+            sp_tmo_recv++;
+        else if (APR_STATUS_IS_ECONNABORTED(ss) ||
+                 APR_STATUS_IS_ECONNRESET(ss) ||
+                 APR_STATUS_IS_EOF(ss))
+            sp_rst_recv++;
+        else {
+            sp_err_recv++;
+            sp_erl_recv = ss;
+        }
+    }
+#endif
+    if (ss == APR_SUCCESS)
+        return (jint)nbytes;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, recvfrom)(TCN_STDARGS, jlong from,
+                                          jlong sock, jint flags,
+                                          jbyteArray buf, jint offset, jint toread)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_sockaddr_t *f = J2P(from, apr_sockaddr_t *);
+    apr_size_t nbytes = (apr_size_t)toread;
+    jbyte *bytes = (*e)->GetByteArrayElements(e, buf, NULL);
+    apr_status_t ss;
+
+    UNREFERENCED(o);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return -(jint)APR_ENOTSOCK;
+    }
+    TCN_ASSERT(s->sock != NULL);
+    TCN_ASSERT(buf != NULL);
+    ss = apr_socket_recvfrom(f, s->sock, (apr_int32_t)flags, (char*)(bytes + offset), &nbytes);
+
+    (*e)->ReleaseByteArrayElements(e, buf, bytes,
+                                   nbytes ? 0 : JNI_ABORT);
+    if (ss == APR_SUCCESS)
+        return (jint)nbytes;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jint)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, optSet)(TCN_STDARGS, jlong sock,
+                                         jint opt, jint on)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+
+    UNREFERENCED(o);
+    if (!s->sock) {
+        return APR_ENOTSOCK;
+    }
+    else
+        return (jint)(*s->net->opt_set)(s->opaque, (apr_int32_t)opt, (apr_int32_t)on);
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, optGet)(TCN_STDARGS, jlong sock,
+                                         jint opt)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_int32_t on = 0;
+
+    UNREFERENCED(o);
+    if (!s->sock)
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+    else {
+        TCN_THROW_IF_ERR((*s->net->opt_get)(s->opaque, (apr_int32_t)opt,
+                                            &on), on);
+    }
+cleanup:
+    return (jint)on;
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, timeoutSet)(TCN_STDARGS, jlong sock,
+                                             jlong timeout)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+
+    UNREFERENCED(o);
+    TCN_ASSERT(s->opaque != NULL);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return APR_ENOTSOCK;
+    }
+    return (jint)(*s->net->timeout_set)(s->opaque, J2T(timeout));
+}
+
+TCN_IMPLEMENT_CALL(jlong, Socket, timeoutGet)(TCN_STDARGS, jlong sock)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_interval_time_t timeout;
+
+    UNREFERENCED(o);
+    if (!sock) {
+        tcn_ThrowAPRException(e, APR_ENOTSOCK);
+        return 0;
+    }
+    TCN_ASSERT(s->opaque != NULL);
+
+    TCN_THROW_IF_ERR((*s->net->timeout_get)(s->opaque, &timeout), timeout);
+cleanup:
+    return (jlong)timeout;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, Socket, atmark)(TCN_STDARGS, jlong sock)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_int32_t mark;
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(sock != 0);
+    TCN_ASSERT(s->sock != NULL);
+
+    if (apr_socket_atmark(s->sock, &mark) != APR_SUCCESS)
+        return JNI_FALSE;
+    return mark ? JNI_TRUE : JNI_FALSE;
+}
+
+#if APR_HAS_SENDFILE
+
+TCN_IMPLEMENT_CALL(jlong, Socket, sendfile)(TCN_STDARGS, jlong sock,
+                                            jlong file,
+                                            jobjectArray headers,
+                                            jobjectArray trailers,
+                                            jlong offset, jlong len,
+                                            jint flags)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_file_t *f = J2P(file, apr_file_t *);
+    jsize nh = 0;
+    jsize nt = 0;
+    jsize i;
+    struct iovec hvec[APR_MAX_IOVEC_SIZE];
+    struct iovec tvec[APR_MAX_IOVEC_SIZE];
+    jobject hba[APR_MAX_IOVEC_SIZE];
+    jobject tba[APR_MAX_IOVEC_SIZE];
+    apr_off_t off = (apr_off_t)offset;
+    apr_size_t written = (apr_size_t)len;
+    apr_hdtr_t hdrs;
+    apr_status_t ss;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+    TCN_ASSERT(file != 0);
+
+    if (s->net->type != TCN_SOCKET_APR)
+        return (jint)(-APR_ENOTIMPL);
+    if (headers)
+        nh = (*e)->GetArrayLength(e, headers);
+    if (trailers)
+        nt = (*e)->GetArrayLength(e, trailers);
+    /* Check for overflow */
+    if (nh >= APR_MAX_IOVEC_SIZE || nt >= APR_MAX_IOVEC_SIZE)
+        return (jint)(-APR_ENOMEM);
+
+    for (i = 0; i < nh; i++) {
+        hba[i] = (*e)->GetObjectArrayElement(e, headers, i);
+        hvec[i].iov_len  = (*e)->GetArrayLength(e, hba[i]);
+        hvec[i].iov_base = (void *)((*e)->GetByteArrayElements(e, hba[i], NULL));
+    }
+    for (i = 0; i < nt; i++) {
+        tba[i] = (*e)->GetObjectArrayElement(e, trailers, i);
+        tvec[i].iov_len  = (*e)->GetArrayLength(e, tba[i]);
+        tvec[i].iov_base = (void *)((*e)->GetByteArrayElements(e, tba[i], NULL));
+    }
+    hdrs.headers = &hvec[0];
+    hdrs.numheaders = nh;
+    hdrs.trailers = &tvec[0];
+    hdrs.numtrailers = nt;
+
+
+    ss = apr_socket_sendfile(s->sock, f, &hdrs, &off, &written, (apr_int32_t)flags);
+
+#ifdef TCN_DO_STATISTICS
+    sf_max_send = TCN_MAX(sf_max_send, written);
+    sf_min_send = TCN_MIN(sf_min_send, written);
+    sf_tot_send += written;
+    sf_num_send++;
+#endif
+
+    for (i = 0; i < nh; i++) {
+        (*e)->ReleaseByteArrayElements(e, hba[i], (jbyte*)hvec[i].iov_base, JNI_ABORT);
+    }
+
+    for (i = 0; i < nt; i++) {
+        (*e)->ReleaseByteArrayElements(e, tba[i], (jbyte*)tvec[i].iov_base, JNI_ABORT);
+    }
+    /* Return Number of bytes actually sent,
+     * including headers, file, and trailers
+     */
+    if (ss == APR_SUCCESS)
+        return (jlong)written;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jlong)ss;
+    }
+}
+
+TCN_IMPLEMENT_CALL(jlong, Socket, sendfilen)(TCN_STDARGS, jlong sock,
+                                             jlong file,
+                                             jlong offset, jlong len,
+                                             jint flags)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_file_t *f = J2P(file, apr_file_t *);
+    apr_off_t off = (apr_off_t)offset;
+    apr_size_t written = (apr_size_t)len;
+    apr_hdtr_t hdrs;
+    apr_status_t ss;
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(sock != 0);
+    TCN_ASSERT(file != 0);
+
+    if (s->net->type != TCN_SOCKET_APR)
+        return (jint)(-APR_ENOTIMPL);
+
+    hdrs.headers = NULL;
+    hdrs.numheaders = 0;
+    hdrs.trailers = NULL;
+    hdrs.numtrailers = 0;
+
+
+    ss = apr_socket_sendfile(s->sock, f, &hdrs, &off, &written, (apr_int32_t)flags);
+
+#ifdef TCN_DO_STATISTICS
+    sf_max_send = TCN_MAX(sf_max_send, written);
+    sf_min_send = TCN_MIN(sf_min_send, written);
+    sf_tot_send += written;
+    sf_num_send++;
+#endif
+
+    /* Return Number of bytes actually sent,
+     * including headers, file, and trailers
+     */
+    if (ss == APR_SUCCESS)
+        return (jlong)written;
+    else {
+        TCN_ERROR_WRAP(ss);
+        return -(jlong)ss;
+    }
+}
+
+#else /* APR_HAS_SENDIFLE */
+
+TCN_IMPLEMENT_CALL(jlong, Socket, sendfile)(TCN_STDARGS, jlong sock,
+                                            jlong file,
+                                            jobjectArray headers,
+                                            jobjectArray trailers,
+                                            jlong offset, jlong len,
+                                            jint flags)
+{
+
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(sock);
+    UNREFERENCED(file);
+    UNREFERENCED(headers);
+    UNREFERENCED(trailers);
+    UNREFERENCED(offset);
+    UNREFERENCED(len);
+    UNREFERENCED(flags);
+    return -(jlong)APR_ENOTIMPL;
+}
+
+TCN_IMPLEMENT_CALL(jlong, Socket, sendfilen)(TCN_STDARGS, jlong sock,
+                                             jlong file,
+                                             jlong offset, jlong len,
+                                             jint flags)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(sock);
+    UNREFERENCED(file);
+    UNREFERENCED(offset);
+    UNREFERENCED(len);
+    UNREFERENCED(flags);
+    return -(jlong)APR_ENOTIMPL;
+}
+
+#endif  /* APR_HAS_SENDIFLE */
+
+
+TCN_IMPLEMENT_CALL(jint, Socket, acceptfilter)(TCN_STDARGS,
+                                               jlong sock,
+                                               jstring name,
+                                               jstring args)
+{
+#if APR_HAS_SO_ACCEPTFILTER
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    TCN_ALLOC_CSTRING(name);
+    TCN_ALLOC_CSTRING(args);
+    apr_status_t rv;
+
+
+    UNREFERENCED(o);
+    rv = apr_socket_accept_filter(s->sock, J2S(name),
+                                  J2S(args) ? J2S(args) : "");
+    TCN_FREE_CSTRING(name);
+    TCN_FREE_CSTRING(args);
+    return (jint)rv;
+#else
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(sock);
+    UNREFERENCED(name);
+    UNREFERENCED(args);
+    return (jint)APR_ENOTIMPL;
+#endif
+}
+
+TCN_IMPLEMENT_CALL(jint, Socket, dataSet)(TCN_STDARGS, jlong sock,
+                                          jstring key, jobject data)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_status_t rv = APR_SUCCESS;
+    TCN_ALLOC_CSTRING(key);
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+
+    rv = apr_socket_data_set(s->sock, data, J2S(key), NULL);
+    TCN_FREE_CSTRING(key);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jobject, Socket, dataGet)(TCN_STDARGS, jlong socket,
+                                             jstring key)
+{
+    tcn_socket_t *s = J2P(socket, tcn_socket_t *);
+    TCN_ALLOC_CSTRING(key);
+    void *rv = NULL;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(socket != 0);
+
+    if (apr_socket_data_get(&rv, J2S(key), s->sock) != APR_SUCCESS) {
+        rv = NULL;
+    }
+    TCN_FREE_CSTRING(key);
+    return rv;
+}
diff --git a/c/os.c b/c/os.c
new file mode 100644
index 0000000..611442e
--- /dev/null
+++ b/c/os.c
@@ -0,0 +1,40 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: os.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+
+#include "tcn.h"
+
+TCN_IMPLEMENT_CALL(jstring, OS, defaultEncoding)(TCN_STDARGS, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+
+    UNREFERENCED(o);
+    return AJP_TO_JSTRING(apr_os_default_encoding(p));
+}
+
+TCN_IMPLEMENT_CALL(jstring, OS, localeEncoding)(TCN_STDARGS, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+
+    UNREFERENCED(o);
+    return AJP_TO_JSTRING(apr_os_locale_encoding(p));
+}
+
diff --git a/c/os_unix_system.c b/c/os_unix_system.c
new file mode 100644
index 0000000..dec3ffa
--- /dev/null
+++ b/c/os_unix_system.c
@@ -0,0 +1,451 @@
+#ifndef _WINDOWS
+
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: system.c 1445974 2013-02-13 23:11:53Z rjung $
+ */
+
+#include "apr.h"
+#include "apr_pools.h"
+#include "apr_network_io.h"
+#include "apr_poll.h"
+
+#include "tcn.h"
+#if defined(__linux__)
+#include <sys/sysinfo.h>
+#elif defined(sun)
+#include <unistd.h>
+#include <sys/swap.h>
+#include <procfs.h>
+#include <kstat.h>
+#include <sys/sysinfo.h>
+#endif
+
+#if defined(DARWIN)
+#include <mach/mach_init.h>
+#include <mach/mach_host.h>
+#include <mach/host_info.h>
+#include <sys/sysctl.h>
+#include <sys/stat.h>
+#endif
+
+#include <syslog.h>
+#include <stdarg.h>
+
+#ifndef LOG_WARN
+#define LOG_WARN LOG_WARNING
+#endif
+
+#if defined(sun)
+#define MAX_PROC_PATH_LEN 64
+#define MAX_CPUS 512
+#define PSINFO_T_SZ sizeof(psinfo_t)
+#define PRUSAGE_T_SZ sizeof(prusage_t)
+
+static int proc_open(const char *type)
+{
+    char proc_path[MAX_PROC_PATH_LEN+1];
+
+    sprintf(proc_path, "/proc/self/%s", type);
+    return open(proc_path, O_RDONLY);
+}
+
+static int proc_read(void *buf, const size_t size, int filedes)
+{
+    ssize_t bytes;
+
+    if (filedes >= 0) {
+        bytes = pread(filedes, buf, size, 0);
+        if (bytes != size)
+            return -1;
+        else
+            return 0;
+    }
+    else
+        return -1;
+}
+
+#endif
+
+TCN_IMPLEMENT_CALL(jboolean, OS, is)(TCN_STDARGS, jint type)
+{
+    UNREFERENCED_STDARGS;
+    if (type == 1)
+        return JNI_TRUE;
+#if defined(__linux__)
+    else if (type == 5)
+        return JNI_TRUE;
+#endif
+#if defined(sun)
+    else if (type == 6)
+        return JNI_TRUE;
+#endif
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+    else if (type == 7)
+        return JNI_TRUE;
+#endif
+#if defined(__APPLE__) || defined(DARWIN)
+    else if (type == 8)
+        return JNI_TRUE;
+#endif
+    else
+        return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jint, OS, info)(TCN_STDARGS,
+                                   jlongArray inf)
+{
+    jint rv;
+    int  i;
+    jsize ilen = (*e)->GetArrayLength(e, inf);
+    jlong *pvals = (*e)->GetLongArrayElements(e, inf, NULL);
+
+    UNREFERENCED(o);
+    if (ilen < 16) {
+        return APR_EINVAL;
+    }
+    for (i = 0; i < 16; i++)
+        pvals[i] = 0;
+#if defined(__linux__)
+    {
+        struct sysinfo info;
+        if (sysinfo(&info))
+            rv = apr_get_os_error();
+        else {
+            static char buf[1024];
+            unsigned long user = 0;
+            unsigned long system = 0;
+            long idle = 0;
+            long long starttime = 0;
+            int fd;
+            int len;
+            long sys_clk_tck = sysconf(_SC_CLK_TCK); /* number of system ticks per second */
+
+            pvals[0] = (jlong)(info.totalram  * info.mem_unit);
+            pvals[1] = (jlong)(info.freeram   * info.mem_unit);
+            pvals[2] = (jlong)(info.totalswap * info.mem_unit);
+            pvals[3] = (jlong)(info.freeswap  * info.mem_unit);
+            pvals[4] = (jlong)(info.sharedram * info.mem_unit);
+            pvals[5] = (jlong)(info.bufferram * info.mem_unit);
+            pvals[6] = (jlong)(100 - (info.freeram * 100 / info.totalram));
+
+            if (sys_clk_tck >= 0) {
+                /* Get total CPU times from /proc/stat */
+                /* Example for the first line: cpu  2095497 8176 3280198 908667841 1543576 28867 375399 0 0 */
+                /* According to the man pages, the numbers are given in units of USER_HZ:
+                 * user mode, user mode with low priority (nice), system mode, and the idle task.
+                 * Additional values can be ignored. */
+                fd = open("/proc/stat", O_RDONLY);
+                if (fd != -1) {
+                    len = read(fd, buf, sizeof buf - 1);
+                    if (len > 0) {
+                        buf[len] = '\0';
+                        if (sscanf(buf, "cpu %lu %*d %lu %ld", &user, &system, &idle) == 3) {
+                            pvals[7] = (jlong)(idle * 1000 / sys_clk_tck * 1000); /* Idle Time in microseconds */
+                            pvals[8] = (jlong)(system * 1000 / sys_clk_tck * 1000); /* Kernel Time in microseconds */
+                            pvals[9] = (jlong)(user * 1000 / sys_clk_tck * 1000); /* User Time in microseconds */
+                        }
+                    }
+                    close(fd);
+                }
+                /* Get process CPU times from /proc/self/stat */
+                /* Example for the first line:
+                 * 6309 (csh) S 6308 6309 6309 34816 7124 4202496 15119 252261 1 30 21 58 1537 1447 20 0 1 0 916031966 ... */
+                /* Parsing it according to man -s 5 proci:
+                 * pid %d, comm %s, state %c, ppid %d pgrp %d, session %d, tty_nr %d, tpgid %d, flags %u,
+                 * minflt %lu, cminflt %lu,  majflt %lu, cmajflt %lu,
+                 * utime %lu (!), stime %lu (!), cutime %ld (!), cstime %ld (!),
+                 * priority %ld, nice %ld, num_threads %ld, itrealvalue %ld,
+                 * starttime %llu (!) */
+                fd = open("/proc/self/stat", O_RDONLY);
+                if (fd != -1) {
+                    len = read(fd, buf, sizeof buf - 1);
+                    if (len > 0) {
+                        buf[len] = '\0';
+                        if (sscanf(buf, "%*d %*s %*c %*d %*d %*d %*d %*d %*u"
+                                        " %*u %*u %*u %*u"
+                                        " %lu %lu %*d %*d"
+                                        " %*d %*d %*d %*d"
+                                        "%llu", &user, &system, &starttime) == 3) {
+                            pvals[10] = (jlong)(apr_time_now() - apr_time_make(info.uptime - starttime / sys_clk_tck, 0)); /* Process creation time (apr_time_t) */
+                            pvals[11] = (jlong)(system * 1000 / sys_clk_tck * 1000); /* Process System Time in microseconds */
+                            pvals[12] = (jlong)(user * 1000 / sys_clk_tck * 1000); /* Process User Time in microseconds */
+                        }
+                    }
+                    close(fd);
+                }
+            }
+
+            rv = APR_SUCCESS;
+        }
+    }
+#elif defined(sun)
+    {
+        /* static variables with basic procfs info */
+        static long creation = 0;              /* unix timestamp of process creation */
+        static int psinf_fd = 0;               /* file descriptor for the psinfo procfs file */
+        static int prusg_fd = 0;               /* file descriptor for the usage procfs file */
+        static size_t rss = 0;                 /* maximum of resident set size from previous calls */
+        /* static variables with basic kstat info */
+        static kstat_ctl_t *kstat_ctl = NULL;  /* kstat control object, only initialized once */
+        static kstat_t *kstat_cpu[MAX_CPUS];   /* array of kstat objects for per cpu statistics */
+        static int cpu_count = 0;              /* number of cpu structures found in kstat */
+        static kid_t kid = 0;                  /* kstat ID, for which the kstat_ctl holds the correct chain */
+        /* non-static variables - general use */
+        int res = 0;                           /* general result state */
+        /* non-static variables - sysinfo/swapctl use */
+        long ret_sysconf;                      /* value returned from sysconf call */
+        long tck_dividend;                     /* factor used by transforming tick numbers to microseconds */
+        long tck_divisor;                      /* divisor used by transforming tick numbers to microseconds */
+        long sys_pagesize = sysconf(_SC_PAGESIZE); /* size of a system memory page in bytes */
+        long sys_clk_tck = sysconf(_SC_CLK_TCK); /* number of system ticks per second */
+        struct anoninfo info;                  /* structure for information about sizes in anonymous memory system */
+        /* non-static variables - procfs use */
+        psinfo_t psinf;                        /* psinfo structure from procfs */
+        prusage_t prusg;                       /* usage structure from procfs */
+        size_t new_rss = 0;                    /* resident set size read from procfs */
+        time_t now;                            /* time needed for calculating process creation time */
+        /* non-static variables - kstat use */
+        kstat_t *kstat = NULL;                 /* kstat working pointer */
+        cpu_sysinfo_t cpu;                     /* cpu sysinfo working pointer */
+        kid_t new_kid = 0;                     /* kstat ID returned from chain update */
+        int new_kstat = 0;                     /* flag indicating, if kstat structure has changed since last call */
+
+        rv = APR_SUCCESS;
+
+        if (sys_pagesize <= 0) {
+            rv = apr_get_os_error();
+        }
+        else {
+            ret_sysconf = sysconf(_SC_PHYS_PAGES);
+            if (ret_sysconf >= 0) {
+                pvals[0] = (jlong)((jlong)sys_pagesize * ret_sysconf);
+            }
+            else {
+                rv = apr_get_os_error();
+            }
+            ret_sysconf = sysconf(_SC_AVPHYS_PAGES);
+            if (ret_sysconf >= 0) {
+                pvals[1] = (jlong)((jlong)sys_pagesize * ret_sysconf);
+            }
+            else {
+                rv = apr_get_os_error();
+            }
+            res=swapctl(SC_AINFO, &info);
+            if (res >= 0) {
+                pvals[2] = (jlong)((jlong)sys_pagesize * info.ani_max);
+                pvals[3] = (jlong)((jlong)sys_pagesize * info.ani_free);
+                pvals[6] = (jlong)(100 - (jlong)info.ani_free * 100 / info.ani_max);
+            }
+            else {
+                rv = apr_get_os_error();
+            }
+        }
+
+        if (psinf_fd == 0) {
+            psinf_fd = proc_open("psinfo");
+        }
+        res = proc_read(&psinf, PSINFO_T_SZ, psinf_fd);
+        if (res >= 0) {
+            new_rss = psinf.pr_rssize*1024;
+            pvals[13] = (jlong)(new_rss);
+            if (new_rss > rss) {
+                rss = new_rss;
+            }
+            pvals[14] = (jlong)(rss);
+        }
+        else {
+            psinf_fd = 0;
+            rv = apr_get_os_error();
+        }
+        if (prusg_fd == 0) {
+            prusg_fd = proc_open("usage");
+        }
+        res = proc_read(&prusg, PRUSAGE_T_SZ, prusg_fd);
+        if (res >= 0) {
+            if (creation <= 0) {
+                time(&now);
+                creation = (long)(now - (prusg.pr_tstamp.tv_sec -
+                                         prusg.pr_create.tv_sec));
+            }
+            pvals[10] = (jlong)(creation * 1000000L);
+            pvals[11] = (jlong)((jlong)prusg.pr_stime.tv_sec * 1000000L +
+                                (prusg.pr_stime.tv_nsec / 1000L));
+            pvals[12] = (jlong)((jlong)prusg.pr_utime.tv_sec * 1000000L +
+                                (prusg.pr_utime.tv_nsec / 1000L));
+            pvals[15] = (jlong)(prusg.pr_majf);
+        }
+        else {
+            prusg_fd = 0;
+            rv = apr_get_os_error();
+        }
+
+        if (sys_clk_tck <= 0) {
+            rv = apr_get_os_error();
+        }
+        else {
+            tck_dividend = 1000000L;
+            tck_divisor = sys_clk_tck;
+            for (i = 0; i < 3; i++) {
+                if (tck_divisor % 2 == 0) {
+                    tck_divisor = tck_divisor / 2;
+                    tck_dividend = tck_dividend / 2;
+                }
+                if (tck_divisor % 5 == 0) {
+                    tck_divisor = tck_divisor / 5;
+                    tck_dividend = tck_dividend / 5;
+                }
+            }
+            if (kstat_ctl == NULL) {
+                kstat_ctl = kstat_open();
+                kid = kstat_ctl->kc_chain_id;
+                new_kstat = 1;
+            } else {
+                new_kid = kstat_chain_update(kstat_ctl);
+                if (new_kid < 0) {
+                    res=kstat_close(kstat_ctl);
+                    kstat_ctl = kstat_open();
+                    kid = kstat_ctl->kc_chain_id;
+                    new_kstat = 1;
+                } else if (new_kid > 0 && kid != new_kid) {
+                    kid = new_kid;
+                    new_kstat = 1;
+                }
+            }
+            if (new_kstat) {
+                cpu_count = 0;
+                for (kstat = kstat_ctl->kc_chain; kstat; kstat = kstat->ks_next) {
+                    if (strncmp(kstat->ks_name, "cpu_stat", 8) == 0) {
+                        kstat_cpu[cpu_count++]=kstat;
+                    }
+                }
+            }
+            for (i = 0; i < cpu_count; i++) {
+                new_kid = kstat_read(kstat_ctl, kstat_cpu[i], NULL);
+                if (new_kid >= 0) {
+                    cpu = ((cpu_stat_t *)kstat_cpu[i]->ks_data)->cpu_sysinfo;
+                    if ( tck_divisor == 1 ) {
+                        pvals[7] += (jlong)(((jlong)cpu.cpu[CPU_IDLE]) * tck_dividend);
+                        pvals[7] += (jlong)(((jlong)cpu.cpu[CPU_WAIT]) * tck_dividend);
+                        pvals[8] += (jlong)(((jlong)cpu.cpu[CPU_KERNEL]) * tck_dividend);
+                        pvals[9] += (jlong)(((jlong)cpu.cpu[CPU_USER]) * tck_dividend);
+                    } else {
+                        pvals[7] += (jlong)(((jlong)cpu.cpu[CPU_IDLE]) * tck_dividend / tck_divisor);
+                        pvals[7] += (jlong)(((jlong)cpu.cpu[CPU_WAIT]) * tck_dividend / tck_divisor);
+                        pvals[8] += (jlong)(((jlong)cpu.cpu[CPU_KERNEL]) * tck_dividend / tck_divisor);
+                        pvals[9] += (jlong)(((jlong)cpu.cpu[CPU_USER]) * tck_dividend / tck_divisor);
+                    }
+                }
+            }
+        }
+
+        /*
+         * The next two are not implemented yet for Solaris
+         * inf[4]  - Amount of shared memory
+         * inf[5]  - Memory used by buffers
+         *
+         */
+    }
+
+#elif defined(DARWIN)
+
+    uint64_t mem_total;
+    size_t len = sizeof(mem_total);
+
+    vm_statistics_data_t vm_info;
+    mach_msg_type_number_t info_count = HOST_VM_INFO_COUNT;
+
+    sysctlbyname("hw.memsize", &mem_total, &len, NULL, 0);
+    pvals[0] = (jlong)mem_total;
+
+    host_statistics(mach_host_self (), HOST_VM_INFO, (host_info_t)&vm_info, &info_count);
+    pvals[1] = (jlong)(((double)vm_info.free_count)*vm_page_size);
+    pvals[6] = (jlong)(100 - (pvals[1] * 100 / mem_total));
+    rv = APR_SUCCESS;
+
+/* DARWIN */
+#else
+    rv = APR_ENOTIMPL;
+#endif
+   (*e)->ReleaseLongArrayElements(e, inf, pvals, 0);
+    return rv;
+}
+
+#define LOG_MSG_DOMAIN                   "Native"
+
+
+TCN_IMPLEMENT_CALL(jstring, OS, expand)(TCN_STDARGS, jstring val)
+{
+    jstring str;
+    TCN_ALLOC_CSTRING(val);
+
+    UNREFERENCED(o);
+
+    /* TODO: Make ${ENVAR} expansion */
+    str = (*e)->NewStringUTF(e, J2S(val));
+
+    TCN_FREE_CSTRING(val);
+    return str;
+}
+
+TCN_IMPLEMENT_CALL(void, OS, sysloginit)(TCN_STDARGS, jstring domain)
+{
+    const char *d;
+    TCN_ALLOC_CSTRING(domain);
+
+    UNREFERENCED(o);
+    if ((d = J2S(domain)) == NULL)
+        d = LOG_MSG_DOMAIN;
+
+    openlog(d, LOG_CONS | LOG_PID, LOG_LOCAL0);
+    TCN_FREE_CSTRING(domain);
+}
+
+TCN_IMPLEMENT_CALL(void, OS, syslog)(TCN_STDARGS, jint level,
+                                     jstring msg)
+{
+    TCN_ALLOC_CSTRING(msg);
+    int id = LOG_DEBUG;
+    UNREFERENCED(o);
+
+    switch (level) {
+        case TCN_LOG_EMERG:
+            id = LOG_EMERG;
+        break;
+        case TCN_LOG_ERROR:
+            id = LOG_ERR;
+        break;
+        case TCN_LOG_NOTICE:
+            id = LOG_NOTICE;
+        break;
+        case TCN_LOG_WARN:
+            id = LOG_WARN;
+        break;
+        case TCN_LOG_INFO:
+            id = LOG_INFO;
+        break;
+    }
+    syslog (id, "%s", J2S(msg));
+
+    TCN_FREE_CSTRING(msg);
+}
+
+#endif
+
diff --git a/c/os_unix_uxpipe.c b/c/os_unix_uxpipe.c
new file mode 100644
index 0000000..9ff31fa
--- /dev/null
+++ b/c/os_unix_uxpipe.c
@@ -0,0 +1,360 @@
+#ifndef _WINDOWS
+
+/* 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.
+ */
+
+/** UNIX AF_LOCAL network wrapper
+ *
+ * @author Mladen Turk
+ * @version $Id: uxpipe.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+
+
+#include "tcn.h"
+#include "apr_thread_mutex.h"
+#include "apr_poll.h"
+
+/* ### should be tossed in favor of APR */
+#include <sys/stat.h>
+#include <sys/un.h> /* for sockaddr_un */
+
+#ifdef TCN_DO_STATISTICS
+#include "apr_atomic.h"
+
+static volatile apr_uint32_t uxp_created  = 0;
+static volatile apr_uint32_t uxp_closed   = 0;
+static volatile apr_uint32_t uxp_cleared  = 0;
+static volatile apr_uint32_t uxp_accepted = 0;
+
+void uxp_network_dump_statistics()
+{
+    fprintf(stderr, "NT Network Statistics ..\n");
+    fprintf(stderr, "Sockets created         : %d\n", uxp_created);
+    fprintf(stderr, "Sockets accepted        : %d\n", uxp_accepted);
+    fprintf(stderr, "Sockets closed          : %d\n", uxp_closed);
+    fprintf(stderr, "Sockets cleared         : %d\n", uxp_cleared);
+}
+
+#endif
+
+#define DEFNAME     "/var/run/tomcatnativesock"
+#define DEFNAME_FMT "/var/run/tomcatnativesock%08x%08x"
+#define DEFSIZE     8192
+#define DEFTIMEOUT  60000
+
+#define TCN_UXP_UNKNOWN     0
+#define TCN_UXP_CLIENT      1
+#define TCN_UXP_ACCEPTED    2
+#define TCN_UXP_SERVER      3
+
+#define TCN_UNIX_MAXPATH    1024
+typedef struct {
+    apr_pool_t          *pool;
+    apr_socket_t        *sock;               /* APR socket */
+    int                 sd;
+    struct sockaddr_un  uxaddr;
+    int                 timeout;
+    int                 mode;                 /* Client or server mode */
+    char                name[TCN_UNIX_MAXPATH+1];
+} tcn_uxp_conn_t;
+
+static apr_status_t APR_THREAD_FUNC
+uxp_socket_timeout_set(apr_socket_t *sock, apr_interval_time_t t)
+{
+    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
+    if (t < 0)
+        con->timeout = -1;
+    else
+        con->timeout = (int)(apr_time_as_msec(t));
+    return APR_SUCCESS;
+}
+
+static apr_status_t APR_THREAD_FUNC
+uxp_socket_timeout_get(apr_socket_t *sock, apr_interval_time_t *t)
+{
+    tcn_uxp_conn_t *con = (tcn_uxp_conn_t*)sock;
+    if (con->timeout < 0)
+        *t = -1;
+    else
+        *t = con->timeout * 1000;
+    return APR_SUCCESS;
+}
+
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+uxp_socket_opt_set(apr_socket_t *sock, apr_int32_t opt, apr_int32_t on)
+{
+    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
+    return apr_socket_opt_set(con->sock, opt, on);
+}
+
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+uxp_socket_opt_get(apr_socket_t *sock, apr_int32_t opt, apr_int32_t *on)
+{
+    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
+    return apr_socket_opt_get(con->sock, opt, on);
+}
+
+static apr_status_t uxp_cleanup(void *data)
+{
+    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)data;
+
+    if (con) {
+        if (con->sock) {
+            apr_socket_close(con->sock);
+            con->sock = NULL;
+        }
+        if (con->mode == TCN_UXP_SERVER) {
+            unlink(con->name);
+            con->mode = TCN_UXP_UNKNOWN;
+        }
+    }
+
+#ifdef TCN_DO_STATISTICS
+    apr_atomic_inc32(&uxp_cleared);
+#endif
+    return APR_SUCCESS;
+}
+
+static apr_status_t APR_THREAD_FUNC
+uxp_socket_shutdown(apr_socket_t *sock, apr_shutdown_how_e how)
+{
+    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
+    return apr_socket_shutdown(con->sock, how);
+}
+
+static apr_status_t APR_THREAD_FUNC
+uxp_socket_close(apr_socket_t *sock)
+{
+#ifdef TCN_DO_STATISTICS
+    apr_atomic_inc32(&uxp_closed);
+#endif
+    return uxp_cleanup(sock);
+}
+
+static apr_status_t APR_THREAD_FUNC
+uxp_socket_recv(apr_socket_t *sock, char *buf, apr_size_t *len)
+{
+    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
+    return apr_socket_recv(con->sock, buf, len);
+}
+
+
+static apr_status_t APR_THREAD_FUNC
+uxp_socket_send(apr_socket_t *sock, const char *buf,
+                apr_size_t *len)
+{
+    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
+    return apr_socket_send(con->sock, buf, len);
+}
+
+static apr_status_t APR_THREAD_FUNC
+uxp_socket_sendv(apr_socket_t *sock,
+                 const struct iovec *vec,
+                 apr_int32_t nvec, apr_size_t *len)
+{
+    tcn_uxp_conn_t *con = (tcn_uxp_conn_t *)sock;
+    return apr_socket_sendv(con->sock, vec, nvec, len);
+}
+
+static apr_status_t uxp_socket_cleanup(void *data)
+{
+    tcn_socket_t *s = (tcn_socket_t *)data;
+
+    if (s->net->cleanup) {
+        (*s->net->cleanup)(s->opaque);
+        s->net->cleanup = NULL;
+    }
+#ifdef TCN_DO_STATISTICS
+    apr_atomic_inc32(&uxp_cleared);
+#endif
+    return APR_SUCCESS;
+}
+
+static tcn_nlayer_t uxp_socket_layer = {
+    TCN_SOCKET_UNIX,
+    uxp_cleanup,
+    uxp_socket_close,
+    uxp_socket_shutdown,
+    uxp_socket_opt_get,
+    uxp_socket_opt_set,
+    uxp_socket_timeout_get,
+    uxp_socket_timeout_set,
+    uxp_socket_send,
+    uxp_socket_sendv,
+    uxp_socket_recv
+};
+
+TCN_IMPLEMENT_CALL(jlong, Local, create)(TCN_STDARGS, jstring name,
+                                         jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    tcn_socket_t   *s   = NULL;
+    tcn_uxp_conn_t *con = NULL;
+    int sd;
+    TCN_ALLOC_CSTRING(name);
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pool != 0);
+
+    if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+        tcn_ThrowAPRException(e, apr_get_netos_error());
+        return 0;
+    }
+#ifdef TCN_DO_STATISTICS
+    uxp_created++;
+#endif
+    con = (tcn_uxp_conn_t *)apr_pcalloc(p, sizeof(tcn_uxp_conn_t));
+    con->pool = p;
+    con->mode = TCN_UXP_UNKNOWN;
+    con->timeout = DEFTIMEOUT;
+    con->sd = sd;
+    con->uxaddr.sun_family = AF_UNIX;
+    if (J2S(name)) {
+        strcpy(con->uxaddr.sun_path, J2S(name));
+        TCN_FREE_CSTRING(name);
+    }
+    else
+        strcpy(con->uxaddr.sun_path, DEFNAME);
+    s = (tcn_socket_t *)apr_pcalloc(p, sizeof(tcn_socket_t));
+    s->pool   = p;
+    s->net    = &uxp_socket_layer;
+    s->opaque = con;
+    apr_pool_cleanup_register(p, (const void *)s,
+                              uxp_socket_cleanup,
+                              apr_pool_cleanup_null);
+
+    apr_os_sock_put(&(con->sock), &(con->sd), p);
+
+    return P2J(s);
+
+}
+
+TCN_IMPLEMENT_CALL(jint, Local, bind)(TCN_STDARGS, jlong sock,
+                                      jlong sa)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(sa);
+    TCN_ASSERT(sock != 0);
+    if (s->net->type == TCN_SOCKET_UNIX) {
+        int rc;
+        tcn_uxp_conn_t *c = (tcn_uxp_conn_t *)s->opaque;
+        c->mode = TCN_UXP_SERVER;
+        rc = bind(c->sd, (struct sockaddr *)&(c->uxaddr), sizeof(c->uxaddr));
+        if (rc < 0)
+            return errno;
+        else
+            return APR_SUCCESS;
+    }
+    else
+        return APR_EINVAL;
+}
+
+TCN_IMPLEMENT_CALL(jint, Local, listen)(TCN_STDARGS, jlong sock,
+                                        jint backlog)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    UNREFERENCED_STDARGS;
+
+    TCN_ASSERT(sock != 0);
+    if (s->net->type == TCN_SOCKET_UNIX) {
+        tcn_uxp_conn_t *c = (tcn_uxp_conn_t *)s->opaque;
+        c->mode = TCN_UXP_SERVER;
+        return apr_socket_listen(c->sock, (apr_int32_t)backlog);
+    }
+    else
+        return APR_EINVAL;
+}
+
+TCN_IMPLEMENT_CALL(jlong, Local, accept)(TCN_STDARGS, jlong sock)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_pool_t   *p = NULL;
+    tcn_socket_t *a = NULL;
+    tcn_uxp_conn_t *con = NULL;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+
+    TCN_THROW_IF_ERR(apr_pool_create(&p, s->pool), p);
+    if (s->net->type == TCN_SOCKET_UNIX) {
+        apr_socklen_t len;
+        tcn_uxp_conn_t *c = (tcn_uxp_conn_t *)s->opaque;
+        con = (tcn_uxp_conn_t *)apr_pcalloc(p, sizeof(tcn_uxp_conn_t));
+        con->pool = p;
+        con->mode = TCN_UXP_ACCEPTED;
+        con->timeout = c->timeout;
+        len = sizeof(c->uxaddr);
+        /* Block until a client connects */
+        con->sd = accept(c->sd, (struct sockaddr *)&(con->uxaddr), &len);
+        if (con->sd < 0) {
+            tcn_ThrowAPRException(e, apr_get_os_error());
+            goto cleanup;
+        }
+    }
+    else {
+        tcn_ThrowAPRException(e, APR_ENOTIMPL);
+        goto cleanup;
+    }
+    if (con) {
+#ifdef TCN_DO_STATISTICS
+        apr_atomic_inc32(&uxp_accepted);
+#endif
+        a = (tcn_socket_t *)apr_pcalloc(p, sizeof(tcn_socket_t));
+        a->pool   = p;
+	    a->net    = &uxp_socket_layer;
+        a->opaque = con;
+        apr_pool_cleanup_register(p, (const void *)a,
+                                  uxp_socket_cleanup,
+                                  apr_pool_cleanup_null);
+        apr_os_sock_put(&(con->sock), &(con->sd), p);
+    }
+    return P2J(a);
+cleanup:
+    if (p)
+        apr_pool_destroy(p);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, Local, connect)(TCN_STDARGS, jlong sock,
+                                         jlong sa)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    tcn_uxp_conn_t *con = NULL;
+    int rc;
+
+    UNREFERENCED(o);
+    UNREFERENCED(sa);
+    TCN_ASSERT(sock != 0);
+    if (s->net->type != TCN_SOCKET_UNIX)
+        return APR_ENOTSOCK;
+    con = (tcn_uxp_conn_t *)s->opaque;
+    if (con->mode != TCN_UXP_UNKNOWN)
+        return APR_EINVAL;
+    do {
+        rc = connect(con->sd, (const struct sockaddr *)&(con->uxaddr),
+                     sizeof(con->uxaddr));
+    } while (rc == -1 && errno == EINTR);
+
+    if (rc == -1 && errno != EISCONN)
+        return errno;
+    con->mode = TCN_UXP_CLIENT;
+
+    return APR_SUCCESS;
+}
+
+#endif
+
diff --git a/c/os_win32_apache.ico b/c/os_win32_apache.ico
new file mode 100644
index 0000000..bfb4f63
--- /dev/null
+++ b/c/os_win32_apache.ico
Binary files differ
diff --git a/c/os_win32_libtcnative.rc b/c/os_win32_libtcnative.rc
new file mode 100644
index 0000000..69aedc9
--- /dev/null
+++ b/c/os_win32_libtcnative.rc
@@ -0,0 +1,71 @@
+#include <windows.h>
+
+LANGUAGE 0x9,0x1
+1 11 logmessages.bin
+
+#define TCN_COPYRIGHT "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."
+
+#define TCN_LICENSE  "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\r\n\r\n" \
+                     "http://www.apache.org/licenses/LICENSE-2.0\r\n\r\n" \
+                     "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."
+
+#define TCN_VERSION "1.1.33"
+1000 ICON "apache.ico"
+
+1001 DIALOGEX 0, 0, 252, 51
+STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_CAPTION
+CAPTION "Password prompt"
+FONT 8, "MS Shell Dlg", 0, 0, 0x0
+BEGIN
+    ICON            1000,-1,8,6,21,20
+    LTEXT           "Some of your private key files are encrypted for security reasons.\nIn order to read them you have to provide the pass phrases.",
+                    -1,29,5,220,19
+    LTEXT           "Enter password:",-1,7,28,75,8
+    EDITTEXT        1002,67,27,174,12,ES_PASSWORD | ES_AUTOHSCROLL
+END
+
+1 VERSIONINFO
+ FILEVERSION 1,1,33,0
+ PRODUCTVERSION 1,1,33,0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904b0"
+        BEGIN
+            VALUE "Comments",  TCN_LICENSE "\0"
+            VALUE "CompanyName", "Apache Software Foundation\0"
+            VALUE "FileDescription", "Tomcat Native Java Library\0"
+            VALUE "FileVersion", TCN_VERSION "\0"
+            VALUE "InternalName", "libtcnative-1\0"
+            VALUE "LegalCopyright", TCN_COPYRIGHT "\0"
+            VALUE "OriginalFilename", "libtcnative-1.dll\0"
+            VALUE "ProductName", "Tomcat Native Java Library\0"
+            VALUE "ProductVersion", TCN_VERSION "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1200
+    END
+END
+
diff --git a/c/os_win32_logmessages.bin b/c/os_win32_logmessages.bin
new file mode 100644
index 0000000..44ce985
--- /dev/null
+++ b/c/os_win32_logmessages.bin
Binary files differ
diff --git a/c/os_win32_logmessages.mc b/c/os_win32_logmessages.mc
new file mode 100644
index 0000000..68f86f6
--- /dev/null
+++ b/c/os_win32_logmessages.mc
@@ -0,0 +1,41 @@
+MessageId=0x1
+Severity=Error
+SymbolicName=LOG_MSG_EMERG
+Language=English
+Emerg: %1
+.
+
+MessageId=0x2
+Severity=Error
+SymbolicName=LOG_MSG_ERROR
+Language=English
+Error: %1
+.
+
+MessageId=0x3
+Severity=Warning
+SymbolicName=LOG_MSG_NOTICE
+Language=English
+Notice: %1
+.
+
+MessageId=0x4
+Severity=Warning
+SymbolicName=LOG_MSG_WARN
+Language=English
+Warn: %1
+.
+
+MessageId=0x5
+Severity=Informational
+SymbolicName=LOG_MSG_INFO
+Language=English
+Info: %1
+.
+
+MessageId=0x6
+Severity=Success
+SymbolicName=LOG_MSG_DEBUG
+Language=English
+Debug: %1
+.
diff --git a/c/os_win32_ntpipe.c b/c/os_win32_ntpipe.c
new file mode 100644
index 0000000..4ef80c7
--- /dev/null
+++ b/c/os_win32_ntpipe.c
@@ -0,0 +1,511 @@
+#ifdef _WINDOWS
+
+/* 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.
+ */
+
+/** NT Pipes network wrapper
+ *
+ * @author Mladen Turk
+ * @version $Id: ntpipe.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+
+
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0500
+#endif
+#define STRICT
+#include <winsock2.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+#include <sddl.h>
+
+#include "tcn.h"
+#include "apr_thread_mutex.h"
+#include "apr_poll.h"
+
+#ifdef TCN_DO_STATISTICS
+#include "apr_atomic.h"
+
+static volatile apr_uint32_t ntp_created  = 0;
+static volatile apr_uint32_t ntp_closed   = 0;
+static volatile apr_uint32_t ntp_cleared  = 0;
+static volatile apr_uint32_t ntp_accepted = 0;
+
+void ntp_network_dump_statistics()
+{
+    fprintf(stderr, "NT Network Statistics ..\n");
+    fprintf(stderr, "Sockets created         : %d\n", ntp_created);
+    fprintf(stderr, "Sockets accepted        : %d\n", ntp_accepted);
+    fprintf(stderr, "Sockets closed          : %d\n", ntp_closed);
+    fprintf(stderr, "Sockets cleared         : %d\n", ntp_cleared);
+}
+
+#endif
+
+#define DEFNAME     "\\\\.\\PIPE\\TOMCATNATIVEPIPE"
+#define DEFNAME_FMT "\\\\.\\PIPE\\TOMCATNATIVEPIPE%08X%08X"
+#define DEFSIZE     8192
+#define DEFTIMEOUT  60000
+
+#define TCN_NTP_UNKNOWN 0
+#define TCN_NTP_CLIENT  1
+#define TCN_NTP_SERVER  2
+
+typedef struct {
+    apr_pool_t     *pool;
+    apr_socket_t   *sock;               /* Dummy socket */
+    OVERLAPPED     rd_o;
+    OVERLAPPED     wr_o;
+    HANDLE         h_pipe;
+    HANDLE         rd_event;
+    HANDLE         wr_event;
+    DWORD          timeout;
+    int            mode;                 /* Client or server mode */
+    int            nmax;
+    DWORD          sndbuf;
+    DWORD          rcvbuf;
+    char           name[MAX_PATH+1];
+    SECURITY_ATTRIBUTES sa;
+} tcn_ntp_conn_t;
+
+static const char *NTSD_STRING = "D:"     /* Discretionary ACL */
+                   "(D;OICI;GA;;;BG)"     /* Deny access to Built-in Guests */
+                   "(D;OICI;GA;;;AN)"     /* Deny access to Anonymous Logon */
+                   "(A;OICI;GRGWGX;;;AU)" /* Allow read/write/execute to Authenticated Users */
+                   "(A;OICI;GA;;;BA)"     /* Allow full control to Administrators */
+                   "(A;OICI;GA;;;LS)"     /* Allow full control to Local service account */
+                   "(A;OICI;GA;;;SY)";    /* Allow full control to Local system */
+
+
+
+static apr_status_t APR_THREAD_FUNC
+ntp_socket_timeout_set(apr_socket_t *sock, apr_interval_time_t t)
+{
+    tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)sock;
+    if (t < 0)
+        con->timeout = INFINITE;
+    else
+        con->timeout = (DWORD)(apr_time_as_msec(t));
+    return APR_SUCCESS;
+}
+
+static apr_status_t APR_THREAD_FUNC
+ntp_socket_timeout_get(apr_socket_t *sock, apr_interval_time_t *t)
+{
+    tcn_ntp_conn_t *con = (tcn_ntp_conn_t*)sock;
+    if (con->timeout == INFINITE)
+        *t = -1;
+    else
+        *t = con->timeout * 1000;
+    return APR_SUCCESS;
+}
+
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+ntp_socket_opt_set(apr_socket_t *sock, apr_int32_t opt, apr_int32_t on)
+{
+    tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)sock;
+    apr_status_t rv = APR_SUCCESS;
+    switch (opt) {
+        case APR_SO_SNDBUF:
+            con->sndbuf = (DWORD)on;
+        break;
+        case APR_SO_RCVBUF:
+            con->rcvbuf = (DWORD)on;
+        break;
+        default:
+            rv = APR_EINVAL;
+        break;
+    }
+    return rv;
+}
+
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+ntp_socket_opt_get(apr_socket_t *sock, apr_int32_t opt, apr_int32_t *on)
+{
+    tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)sock;
+    apr_status_t rv = APR_SUCCESS;
+    switch (opt) {
+        case APR_SO_SNDBUF:
+            *on = con->sndbuf;
+        break;
+        case APR_SO_RCVBUF:
+            *on = con->rcvbuf;
+        break;
+        default:
+            rv = APR_EINVAL;
+        break;
+    }
+    return rv;
+}
+
+static apr_status_t ntp_cleanup(void *data)
+{
+    tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)data;
+
+    if (con) {
+        if (con->h_pipe) {
+            FlushFileBuffers(con->h_pipe);
+            CloseHandle(con->h_pipe);
+            con->h_pipe = NULL;
+        }
+        if (con->rd_event) {
+            CloseHandle(con->rd_event);
+            con->rd_event = NULL;
+        }
+        if (con->wr_event) {
+            CloseHandle(con->wr_event);
+            con->wr_event= NULL;
+        }
+    }
+
+#ifdef TCN_DO_STATISTICS
+    apr_atomic_inc32(&ntp_cleared);
+#endif
+    return APR_SUCCESS;
+}
+
+static apr_status_t APR_THREAD_FUNC
+ntp_socket_shutdown(apr_socket_t *sock, apr_shutdown_how_e how)
+{
+    UNREFERENCED(how);
+    return ntp_cleanup(sock);;
+}
+
+static apr_status_t APR_THREAD_FUNC
+ntp_socket_close(apr_socket_t *sock)
+{
+#ifdef TCN_DO_STATISTICS
+    apr_atomic_inc32(&ntp_closed);
+#endif
+    return ntp_cleanup(sock);;
+}
+
+static apr_status_t APR_THREAD_FUNC
+ntp_socket_recv(apr_socket_t *sock, char *buf, apr_size_t *len)
+{
+    tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)sock;
+    DWORD readed;
+
+    if (!ReadFile(con->h_pipe, buf, (DWORD)*len, &readed, &con->rd_o)) {
+        DWORD err = GetLastError();
+        if (err == ERROR_IO_PENDING) {
+            DWORD r = WaitForSingleObject(con->rd_event, con->timeout);
+            if (r == WAIT_TIMEOUT)
+                return APR_TIMEUP;
+            else if (r != WAIT_OBJECT_0)
+                return APR_EOF;
+        }
+        else if (err == ERROR_BROKEN_PIPE || err == ERROR_NO_DATA) {
+            /* Server closed the pipe */
+            return APR_EOF;
+        }
+        GetOverlappedResult(con->h_pipe, &con->rd_o, &readed, FALSE);
+    }
+    *len = readed;
+    return APR_SUCCESS;
+}
+
+static apr_status_t APR_THREAD_FUNC
+ntp_socket_send(apr_socket_t *sock, const char *buf,
+                apr_size_t *len)
+{
+    tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)sock;
+    DWORD written;
+
+    if (!WriteFile(con->h_pipe, buf, (DWORD)*len, &written, &con->wr_o)) {
+        DWORD err = GetLastError();
+        if (err == ERROR_IO_PENDING) {
+            DWORD r = WaitForSingleObject(con->wr_event, con->timeout);
+            if (r == WAIT_TIMEOUT)
+                return APR_TIMEUP;
+            else if (r != WAIT_OBJECT_0)
+                return APR_EOF;
+        }
+        else if (err == ERROR_BROKEN_PIPE || err == ERROR_NO_DATA) {
+            /* Server closed the pipe */
+            return APR_EOF;
+        }
+        GetOverlappedResult(con->h_pipe, &con->wr_o, &written, FALSE);
+    }
+    *len = written;
+    return APR_SUCCESS;
+}
+
+static apr_status_t APR_THREAD_FUNC
+ntp_socket_sendv(apr_socket_t *sock,
+                 const struct iovec *vec,
+                 apr_int32_t nvec, apr_size_t *len)
+{
+    tcn_ntp_conn_t *con = (tcn_ntp_conn_t *)sock;
+    apr_status_t rv;
+    apr_size_t written = 0;
+    apr_int32_t i;
+
+    for (i = 0; i < nvec; i++) {
+        apr_size_t rd = vec[i].iov_len;
+        if ((rv = ntp_socket_send((apr_socket_t *)con,
+                                  vec[i].iov_base, &rd)) != APR_SUCCESS) {
+            *len = written;
+            return rv;
+        }
+        written += rd;
+    }
+    *len = written;
+    return APR_SUCCESS;
+}
+
+static apr_status_t ntp_socket_cleanup(void *data)
+{
+    tcn_socket_t *s = (tcn_socket_t *)data;
+
+    if (s->net->cleanup) {
+        (*s->net->cleanup)(s->opaque);
+        s->net->cleanup = NULL;
+    }
+#ifdef TCN_DO_STATISTICS
+    apr_atomic_inc32(&ntp_cleared);
+#endif
+    return APR_SUCCESS;
+}
+
+static tcn_nlayer_t ntp_socket_layer = {
+    TCN_SOCKET_NTPIPE,
+    ntp_cleanup,
+    ntp_socket_close,
+    ntp_socket_shutdown,
+    ntp_socket_opt_get,
+    ntp_socket_opt_set,
+    ntp_socket_timeout_get,
+    ntp_socket_timeout_set,
+    ntp_socket_send,
+    ntp_socket_sendv,
+    ntp_socket_recv
+};
+
+static BOOL create_DACL(LPSECURITY_ATTRIBUTES psa)
+{
+
+    return ConvertStringSecurityDescriptorToSecurityDescriptor(
+                NTSD_STRING,
+                SDDL_REVISION_1,
+                &(psa->lpSecurityDescriptor),
+                NULL);
+}
+
+TCN_IMPLEMENT_CALL(jlong, Local, create)(TCN_STDARGS, jstring name,
+                                         jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    tcn_socket_t   *s   = NULL;
+    tcn_ntp_conn_t *con = NULL;
+    TCN_ALLOC_CSTRING(name);
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pool != 0);
+
+#ifdef TCN_DO_STATISTICS
+    ntp_created++;
+#endif
+    con = (tcn_ntp_conn_t *)apr_pcalloc(p, sizeof(tcn_ntp_conn_t));
+    con->pool = p;
+    con->mode = TCN_NTP_UNKNOWN;
+    con->nmax = PIPE_UNLIMITED_INSTANCES;
+    con->timeout = DEFTIMEOUT;
+    con->sndbuf  = DEFSIZE;
+    con->rcvbuf  = DEFSIZE;
+    if (J2S(name)) {
+        strncpy(con->name, J2S(name), MAX_PATH);
+        con->name[MAX_PATH] = '\0';
+        TCN_FREE_CSTRING(name);
+    }
+    else
+        strcpy(con->name, DEFNAME);
+    con->sa.nLength = sizeof(con->sa);
+    con->sa.bInheritHandle = TRUE;
+    if (!create_DACL(&con->sa)) {
+        tcn_ThrowAPRException(e, apr_get_os_error());
+        return 0;
+    }
+
+    s = (tcn_socket_t *)apr_pcalloc(p, sizeof(tcn_socket_t));
+    s->pool   = p;
+    s->net    = &ntp_socket_layer;
+    s->opaque = con;
+    apr_pool_cleanup_register(p, (const void *)s,
+                              ntp_socket_cleanup,
+                              apr_pool_cleanup_null);
+
+    fflush(stderr);
+    return P2J(s);
+
+}
+
+TCN_IMPLEMENT_CALL(jint, Local, bind)(TCN_STDARGS, jlong sock,
+                                      jlong sa)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(sa);
+    TCN_ASSERT(sock != 0);
+    if (s->net->type == TCN_SOCKET_NTPIPE) {
+        tcn_ntp_conn_t *c = (tcn_ntp_conn_t *)s->opaque;
+        c->mode = TCN_NTP_SERVER;
+        return APR_SUCCESS;
+    }
+    else
+        return APR_EINVAL;
+}
+
+TCN_IMPLEMENT_CALL(jint, Local, listen)(TCN_STDARGS, jlong sock,
+                                        jint backlog)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    UNREFERENCED_STDARGS;
+
+    TCN_ASSERT(sock != 0);
+    if (s->net->type == TCN_SOCKET_NTPIPE) {
+        tcn_ntp_conn_t *c = (tcn_ntp_conn_t *)s->opaque;
+        c->mode = TCN_NTP_SERVER;
+        if (backlog > 0)
+            c->nmax = backlog;
+        else
+            c->nmax = PIPE_UNLIMITED_INSTANCES;
+        return APR_SUCCESS;
+    }
+    else
+        return APR_EINVAL;
+}
+
+TCN_IMPLEMENT_CALL(jlong, Local, accept)(TCN_STDARGS, jlong sock)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_pool_t   *p = NULL;
+    tcn_socket_t *a = NULL;
+    tcn_ntp_conn_t *con = NULL;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+
+    TCN_THROW_IF_ERR(apr_pool_create(&p, s->pool), p);
+    if (s->net->type == TCN_SOCKET_NTPIPE) {
+        tcn_ntp_conn_t *c = (tcn_ntp_conn_t *)s->opaque;
+        con = (tcn_ntp_conn_t *)apr_pcalloc(p, sizeof(tcn_ntp_conn_t));
+        con->pool = p;
+        con->mode = TCN_NTP_SERVER;
+        con->nmax = c->nmax;
+        con->timeout = c->timeout;
+        strcpy(con->name, c->name);
+        con->h_pipe = CreateNamedPipe(con->name,
+                                      PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
+                                      PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
+                                      con->nmax,
+                                      con->sndbuf,
+                                      con->rcvbuf,
+                                      con->timeout,
+                                      &c->sa);
+        if (con->h_pipe == INVALID_HANDLE_VALUE) {
+            tcn_ThrowAPRException(e, apr_get_os_error());
+            goto cleanup;
+        }
+        /* Block until a client connects */
+        if (!ConnectNamedPipe(con->h_pipe, NULL)) {
+            DWORD err = GetLastError();
+            if (err != ERROR_PIPE_CONNECTED) {
+                CloseHandle(con->h_pipe);
+                tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(err));
+                goto cleanup;
+            }
+        }
+        /* Create overlapped events */
+        con->rd_event    = CreateEvent(NULL, TRUE, FALSE, NULL);
+        con->rd_o.hEvent = con->rd_event;
+        con->wr_event    = CreateEvent(NULL, TRUE, FALSE, NULL);
+        con->wr_o.hEvent = con->wr_event;
+    }
+    else {
+        tcn_ThrowAPRException(e, APR_ENOTIMPL);
+        goto cleanup;
+    }
+    if (con) {
+#ifdef TCN_DO_STATISTICS
+        apr_atomic_inc32(&ntp_accepted);
+#endif
+        a = (tcn_socket_t *)apr_pcalloc(p, sizeof(tcn_socket_t));
+        a->pool   = p;
+        a->net    = &ntp_socket_layer;
+        a->opaque = con;
+        apr_pool_cleanup_register(p, (const void *)a,
+                                  ntp_socket_cleanup,
+                                  apr_pool_cleanup_null);
+    }
+    return P2J(a);
+cleanup:
+    if (p)
+        apr_pool_destroy(p);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, Local, connect)(TCN_STDARGS, jlong sock,
+                                         jlong sa)
+{
+    tcn_socket_t *s = J2P(sock, tcn_socket_t *);
+    apr_pool_t   *p = NULL;
+    tcn_socket_t *a = NULL;
+    tcn_ntp_conn_t *con = NULL;
+
+    UNREFERENCED(o);
+    UNREFERENCED(sa);
+    TCN_ASSERT(sock != 0);
+    if (s->net->type != TCN_SOCKET_NTPIPE)
+        return APR_ENOTSOCK;
+    con = (tcn_ntp_conn_t *)s->opaque;
+    if (con->mode == TCN_NTP_SERVER)
+        return APR_EINVAL;
+    con->mode = TCN_NTP_CLIENT;
+
+    while (TRUE) {
+        con->h_pipe = CreateFile(con->name,
+                                 GENERIC_WRITE | GENERIC_READ,
+                                 FILE_SHARE_READ | FILE_SHARE_WRITE ,
+                                 NULL,
+                                 OPEN_EXISTING,
+                                 FILE_FLAG_OVERLAPPED,
+                                 NULL);
+        if (con->h_pipe != INVALID_HANDLE_VALUE)
+            break;
+        if (GetLastError() == ERROR_PIPE_BUSY) {
+            /* All pipe instances are busy, so wait for
+             * timeout value specified by the server process in
+             * the CreateNamedPipe function.
+             */
+            if (!WaitNamedPipe(con->name, NMPWAIT_USE_DEFAULT_WAIT))
+                return apr_get_os_error();
+        }
+        else
+            return apr_get_os_error();
+    }
+
+    /* Create overlapped events */
+    con->rd_event    = CreateEvent(NULL, TRUE, FALSE, NULL);
+    con->rd_o.hEvent = con->rd_event;
+    con->wr_event    = CreateEvent(NULL, TRUE, FALSE, NULL);
+    con->wr_o.hEvent = con->wr_event;
+
+    return APR_SUCCESS;
+}
+
+#endif
+
diff --git a/c/os_win32_registry.c b/c/os_win32_registry.c
new file mode 100644
index 0000000..c1b7a26
--- /dev/null
+++ b/c/os_win32_registry.c
@@ -0,0 +1,797 @@
+#ifdef _WINDOWS
+
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: registry.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0501
+#endif
+#include <winsock2.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+#include <shlwapi.h>
+#include <tlhelp32.h>
+
+#include "apr.h"
+#include "apr_pools.h"
+#include "apr_arch_misc.h"   /* for apr_os_level */
+#include "apr_arch_atime.h"  /* for FileTimeToAprTime */
+
+#include "tcn.h"
+
+#define SAFE_CLOSE_KEY(k)                               \
+    if ((k) != NULL && (k) != INVALID_HANDLE_VALUE) {   \
+        RegCloseKey((k));                               \
+        (k) = NULL;                                     \
+    }
+
+typedef struct {
+    apr_pool_t     *pool;
+    HKEY           root;
+    HKEY           key;
+} tcn_nt_registry_t;
+
+
+#define TCN_HKEY_CLASSES_ROOT       1
+#define TCN_HKEY_CURRENT_CONFIG     2
+#define TCN_HKEY_CURRENT_USER       3
+#define TCN_HKEY_LOCAL_MACHINE      4
+#define TCN_HKEY_USERS              5
+
+static const struct {
+    HKEY k;
+} TCN_KEYS[] = {
+    INVALID_HANDLE_VALUE,
+    HKEY_CLASSES_ROOT,
+    HKEY_CURRENT_CONFIG,
+    HKEY_CURRENT_USER,
+    HKEY_LOCAL_MACHINE,
+    HKEY_USERS,
+    INVALID_HANDLE_VALUE
+};
+
+#define TCN_KEY_ALL_ACCESS          0x0001
+#define TCN_KEY_CREATE_LINK         0x0002
+#define TCN_KEY_CREATE_SUB_KEY      0x0004
+#define TCN_KEY_ENUMERATE_SUB_KEYS  0x0008
+#define TCN_KEY_EXECUTE             0x0010
+#define TCN_KEY_NOTIFY              0x0020
+#define TCN_KEY_QUERY_VALUE         0x0040
+#define TCN_KEY_READ                0x0080
+#define TCN_KEY_SET_VALUE           0x0100
+#define TCN_KEY_WOW64_64KEY         0x0200
+#define TCN_KEY_WOW64_32KEY         0x0400
+#define TCN_KEY_WRITE               0x0800
+
+#define TCN_REGSAM(s, x)                    \
+        s = 0;                              \
+        if (x & TCN_KEY_ALL_ACCESS)         \
+            s |= KEY_ALL_ACCESS;            \
+        if (x & TCN_KEY_CREATE_LINK)        \
+            s |= KEY_CREATE_LINK;           \
+        if (x & TCN_KEY_CREATE_SUB_KEY)     \
+            s |= KEY_CREATE_SUB_KEY;        \
+        if (x & TCN_KEY_ENUMERATE_SUB_KEYS) \
+            s |= KEY_ENUMERATE_SUB_KEYS;    \
+        if (x & TCN_KEY_EXECUTE)            \
+            s |= KEY_EXECUTE;               \
+        if (x & TCN_KEY_NOTIFY)             \
+            s |= KEY_NOTIFY;                \
+        if (x & TCN_KEY_READ)               \
+            s |= KEY_READ;                  \
+        if (x & TCN_KEY_SET_VALUE)          \
+            s |= KEY_SET_VALUE;             \
+        if (x & TCN_KEY_WOW64_64KEY)        \
+            s |= KEY_WOW64_64KEY;           \
+        if (x & TCN_KEY_WOW64_32KEY)        \
+            s |= KEY_WOW64_32KEY;           \
+        if (x & TCN_KEY_WRITE)              \
+            s |= KEY_WRITE
+
+#define TCN_REG_BINARY              1
+#define TCN_REG_DWORD               2
+#define TCN_REG_EXPAND_SZ           3
+#define TCN_REG_MULTI_SZ            4
+#define TCN_REG_QWORD               5
+#define TCN_REG_SZ                  6
+
+static const struct {
+    DWORD t;
+} TCN_REGTYPES[] = {
+    REG_NONE,
+    REG_BINARY,
+    REG_DWORD,
+    REG_EXPAND_SZ,
+    REG_MULTI_SZ,
+    REG_QWORD,
+    REG_SZ,
+    REG_NONE
+};
+
+static apr_status_t registry_cleanup(void *data)
+{
+    tcn_nt_registry_t *reg = (tcn_nt_registry_t *)data;
+
+    if (reg) {
+        SAFE_CLOSE_KEY(reg->key);
+    }
+    return APR_SUCCESS;
+}
+
+TCN_IMPLEMENT_CALL(jlong, Registry, create)(TCN_STDARGS, jint root, jstring name,
+                                            jint sam, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    tcn_nt_registry_t *reg = NULL;
+    TCN_ALLOC_WSTRING(name);
+    HKEY key;
+    LONG rc;
+    REGSAM s;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pool != 0);
+
+    if (root < TCN_HKEY_CLASSES_ROOT || root > TCN_HKEY_USERS) {
+        tcn_ThrowException(e, "Invalid Registry Root Key");
+        goto cleanup;
+    }
+    if (sam < TCN_KEY_ALL_ACCESS || root > TCN_KEY_WRITE) {
+        tcn_ThrowException(e, "Invalid Registry Key Security");
+        goto cleanup;
+    }
+    reg = (tcn_nt_registry_t *)apr_palloc(p, sizeof(tcn_nt_registry_t));
+    reg->pool = p;
+    reg->root = TCN_KEYS[root].k;
+    reg->key  = NULL;
+    TCN_INIT_WSTRING(name);
+    TCN_REGSAM(s, sam);
+    rc = RegCreateKeyExW(reg->root, J2W(name), 0, NULL, REG_OPTION_NON_VOLATILE,
+                         s, NULL, &key, NULL);
+    if (rc !=  ERROR_SUCCESS) {
+        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+        goto cleanup;
+    }
+    reg->key = key;
+    apr_pool_cleanup_register(p, (const void *)reg,
+                              registry_cleanup,
+                              apr_pool_cleanup_null);
+
+cleanup:
+    TCN_FREE_WSTRING(name);
+    return P2J(reg);
+}
+
+TCN_IMPLEMENT_CALL(jlong, Registry, open)(TCN_STDARGS, jint root, jstring name,
+                                          jint sam, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    tcn_nt_registry_t *reg = NULL;
+    TCN_ALLOC_WSTRING(name);
+    HKEY key;
+    LONG rc;
+    REGSAM s;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pool != 0);
+
+    if (root < TCN_HKEY_CLASSES_ROOT || root > TCN_HKEY_USERS) {
+        tcn_ThrowException(e, "Invalid Registry Root Key");
+        goto cleanup;
+    }
+    if (sam < TCN_KEY_ALL_ACCESS || root > TCN_KEY_WRITE) {
+        tcn_ThrowException(e, "Invalid Registry Key Security");
+        goto cleanup;
+    }
+    reg = (tcn_nt_registry_t *)apr_palloc(p, sizeof(tcn_nt_registry_t));
+    reg->pool = p;
+    reg->root = TCN_KEYS[root].k;
+    reg->key  = NULL;
+    TCN_INIT_WSTRING(name);
+    TCN_REGSAM(s, sam);
+    rc = RegOpenKeyExW(reg->root, J2W(name), 0, s, &key);
+    if (rc !=  ERROR_SUCCESS) {
+        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+        goto cleanup;
+    }
+    reg->key = key;
+    apr_pool_cleanup_register(p, (const void *)reg,
+                              registry_cleanup,
+                              apr_pool_cleanup_null);
+
+cleanup:
+    TCN_FREE_WSTRING(name);
+    return P2J(reg);
+}
+
+TCN_IMPLEMENT_CALL(jint, Registry, close)(TCN_STDARGS, jlong reg)
+{
+    tcn_nt_registry_t *r = J2P(reg, tcn_nt_registry_t *);
+    UNREFERENCED_STDARGS;
+
+    TCN_ASSERT(reg != 0);
+
+    registry_cleanup(r);
+    apr_pool_cleanup_kill(r->pool, r, registry_cleanup);
+    return APR_SUCCESS;
+}
+
+TCN_IMPLEMENT_CALL(jint, Registry, getType)(TCN_STDARGS, jlong key,
+                                            jstring name)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    TCN_ALLOC_WSTRING(name);
+    LONG rc;
+    DWORD v;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    TCN_INIT_WSTRING(name);
+    rc = RegQueryValueExW(k->key, J2W(name), NULL, &v, NULL, NULL);
+    if (rc != ERROR_SUCCESS)
+        v = -rc;
+    TCN_FREE_WSTRING(name);
+    switch (v) {
+        case REG_BINARY:
+            v = TCN_REG_BINARY;
+            break;
+        case REG_DWORD:
+            v = TCN_REG_DWORD;
+            break;
+        case REG_EXPAND_SZ:
+            v = TCN_REG_EXPAND_SZ;
+            break;
+        case REG_MULTI_SZ:
+            v = TCN_REG_MULTI_SZ;
+            break;
+        case REG_QWORD:
+            v = TCN_REG_QWORD;
+            break;
+        case REG_SZ:
+            v = TCN_REG_SZ;
+            break;
+        case REG_DWORD_BIG_ENDIAN:
+            v = 0;
+            break;
+    }
+    return v;
+}
+
+TCN_IMPLEMENT_CALL(jint, Registry, getSize)(TCN_STDARGS, jlong key,
+                                            jstring name)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    TCN_ALLOC_WSTRING(name);
+    LONG rc;
+    DWORD v;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    TCN_INIT_WSTRING(name);
+    rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, NULL, &v);
+    if (rc != ERROR_SUCCESS)
+        v = -rc;
+    TCN_FREE_WSTRING(name);
+    return v;
+}
+
+TCN_IMPLEMENT_CALL(jint, Registry, getValueI)(TCN_STDARGS, jlong key,
+                                              jstring name)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    TCN_ALLOC_WSTRING(name);
+    LONG rc;
+    DWORD t, l;
+    DWORD v = 0;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    TCN_INIT_WSTRING(name);
+    rc = RegQueryValueExW(k->key, J2W(name), NULL, &t, NULL, &l);
+    if (rc != ERROR_SUCCESS) {
+        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+        goto cleanup;
+    }
+    if (t == REG_DWORD) {
+        l = sizeof(DWORD);
+        rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, (LPBYTE)&v, &l);
+        if (rc != ERROR_SUCCESS) {
+            tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+            goto cleanup;
+        }
+    }
+    else if (t == REG_SZ || t == REG_BINARY ||
+             t == REG_MULTI_SZ || t == REG_EXPAND_SZ)
+        v = l;
+    else {
+        v = 0;
+        tcn_ThrowException(e, "Unable to convert the value to integer");
+    }
+cleanup:
+    TCN_FREE_WSTRING(name);
+    return v;
+}
+
+TCN_IMPLEMENT_CALL(jlong, Registry, getValueJ)(TCN_STDARGS, jlong key,
+                                               jstring name)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    TCN_ALLOC_WSTRING(name);
+    LONG rc;
+    DWORD t, l;
+    UINT64 v = 0;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    TCN_INIT_WSTRING(name);
+    rc = RegQueryValueExW(k->key, J2W(name), NULL, &t, NULL, &l);
+    if (rc != ERROR_SUCCESS) {
+        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+        goto cleanup;
+    }
+    if (t == REG_DWORD) {
+        DWORD tv;
+        l = sizeof(DWORD);
+        rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, (LPBYTE)&tv, &l);
+        if (rc != ERROR_SUCCESS) {
+            tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+            goto cleanup;
+        }
+        v = tv;
+    }
+    else if (t == REG_QWORD) {
+        l = sizeof(UINT64);
+        rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, (LPBYTE)&v, &l);
+        if (rc != ERROR_SUCCESS) {
+            tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+            goto cleanup;
+        }
+    }
+    else if (t == REG_SZ || t == REG_BINARY ||
+             t == REG_MULTI_SZ || t == REG_EXPAND_SZ)
+        v = l;
+    else {
+        v = 0;
+        tcn_ThrowException(e, "Unable to convert the value to long");
+    }
+cleanup:
+    TCN_FREE_WSTRING(name);
+    return v;
+}
+
+TCN_IMPLEMENT_CALL(jstring, Registry, getValueS)(TCN_STDARGS, jlong key,
+                                                 jstring name)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    TCN_ALLOC_WSTRING(name);
+    LONG rc;
+    DWORD t, l;
+    jstring v = NULL;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    TCN_INIT_WSTRING(name);
+    rc = RegQueryValueExW(k->key, J2W(name), NULL, &t, NULL, &l);
+    if (rc != ERROR_SUCCESS) {
+        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+        goto cleanup;
+    }
+    if (t == REG_SZ || t == REG_EXPAND_SZ) {
+        jchar *vw = (jchar *)malloc(l);
+        rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, (LPBYTE)vw, &l);
+        if (rc != ERROR_SUCCESS) {
+            tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+            free(vw);
+            goto cleanup;
+        }
+        v = (*e)->NewString((e), vw, lstrlenW(vw));
+        free(vw);
+    }
+cleanup:
+    TCN_FREE_WSTRING(name);
+    return v;
+}
+
+TCN_IMPLEMENT_CALL(jbyteArray, Registry, getValueB)(TCN_STDARGS, jlong key,
+                                                    jstring name)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    TCN_ALLOC_WSTRING(name);
+    LONG rc;
+    DWORD t, l;
+    jbyteArray v = NULL;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    TCN_INIT_WSTRING(name);
+    rc = RegQueryValueExW(k->key, J2W(name), NULL, &t, NULL, &l);
+    if (rc != ERROR_SUCCESS) {
+        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+        goto cleanup;
+    }
+    if (t == REG_BINARY) {
+        BYTE *b = (BYTE *)malloc(l);
+        rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, b, &l);
+        if (rc != ERROR_SUCCESS) {
+            tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+            free(b);
+            goto cleanup;
+        }
+        v = tcn_new_arrayb(e, b, l);
+        free(b);
+    }
+cleanup:
+    TCN_FREE_WSTRING(name);
+    return v;
+}
+
+static jsize get_multi_sz_count(LPCWSTR str)
+{
+    LPCWSTR p = str;
+    jsize   cnt = 0;
+    for ( ; p && *p; p++) {
+        cnt++;
+        while (*p)
+            p++;
+    }
+    return cnt;
+}
+
+TCN_IMPLEMENT_CALL(jobjectArray, Registry, getValueA)(TCN_STDARGS, jlong key,
+                                                      jstring name)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    TCN_ALLOC_WSTRING(name);
+    LONG rc;
+    DWORD t, l;
+    jobjectArray v = NULL;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    TCN_INIT_WSTRING(name);
+    rc = RegQueryValueExW(k->key, J2W(name), NULL, &t, NULL, &l);
+    if (rc != ERROR_SUCCESS) {
+        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+        goto cleanup;
+    }
+    if (t == REG_MULTI_SZ) {
+        jsize cnt = 0;
+        jchar *p;
+        jchar *vw = (jchar *)malloc(l);
+        rc = RegQueryValueExW(k->key, J2W(name), NULL, NULL, (LPBYTE)vw, &l);
+        if (rc != ERROR_SUCCESS) {
+            tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+            free(vw);
+            goto cleanup;
+        }
+        cnt = get_multi_sz_count(vw);
+        if (cnt) {
+            jsize idx = 0;
+            v = tcn_new_arrays(e, cnt);
+            for (p = vw ; p && *p; p++) {
+                jstring s;
+                jchar *b = p;
+                while (*p)
+                    p++;
+                s = (*e)->NewString((e), b, (jsize)(p - b));
+                (*e)->SetObjectArrayElement((e), v, idx++, s);
+            }
+        }
+        free(vw);
+    }
+cleanup:
+    TCN_FREE_WSTRING(name);
+    return v;
+}
+
+TCN_IMPLEMENT_CALL(jint, Registry, setValueI)(TCN_STDARGS, jlong key,
+                                              jstring name, jint val)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    TCN_ALLOC_WSTRING(name);
+    LONG rc;
+    DWORD v = (DWORD)val;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    TCN_INIT_WSTRING(name);
+    rc = RegSetValueExW(k->key, J2W(name), 0, REG_DWORD, (CONST BYTE *)&v, sizeof(DWORD));
+    TCN_FREE_WSTRING(name);
+    return v;
+}
+
+TCN_IMPLEMENT_CALL(jint, Registry, setValueJ)(TCN_STDARGS, jlong key,
+                                              jstring name, jlong val)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    TCN_ALLOC_WSTRING(name);
+    LONG rc;
+    UINT64 v = (UINT64)val;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    TCN_INIT_WSTRING(name);
+    rc = RegSetValueExW(k->key, J2W(name), 0, REG_QWORD, (CONST BYTE *)&v, sizeof(UINT64));
+    TCN_FREE_WSTRING(name);
+    return rc;
+}
+
+TCN_IMPLEMENT_CALL(jint, Registry, setValueS)(TCN_STDARGS, jlong key,
+                                              jstring name, jstring val)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    TCN_ALLOC_WSTRING(name);
+    TCN_ALLOC_WSTRING(val);
+    LONG rc;
+    DWORD len;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    TCN_INIT_WSTRING(name);
+    TCN_INIT_WSTRING(val);
+    len = lstrlenW(J2W(val));
+    rc = RegSetValueExW(k->key, J2W(name), 0, REG_SZ,
+                        (CONST BYTE *)J2W(val), (len + 1) * 2);
+    TCN_FREE_WSTRING(name);
+    TCN_FREE_WSTRING(val);
+    return rc;
+}
+
+TCN_IMPLEMENT_CALL(jint, Registry, setValueE)(TCN_STDARGS, jlong key,
+                                              jstring name, jstring val)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    TCN_ALLOC_WSTRING(name);
+    TCN_ALLOC_WSTRING(val);
+    LONG rc;
+    DWORD len;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    TCN_INIT_WSTRING(name);
+    TCN_INIT_WSTRING(val);
+    len = lstrlenW(J2W(val));
+    rc = RegSetValueExW(k->key, J2W(name), 0, REG_EXPAND_SZ,
+                        (CONST BYTE *)J2W(val), (len + 1) * 2);
+    TCN_FREE_WSTRING(name);
+    TCN_FREE_WSTRING(val);
+    return rc;
+}
+
+TCN_IMPLEMENT_CALL(jint, Registry, setValueA)(TCN_STDARGS, jlong key,
+                                              jstring name,
+                                              jobjectArray vals)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    TCN_ALLOC_WSTRING(name);
+    LONG rc;
+    jsize i, len;
+    jsize sl = 0;
+    jchar *msz, *p;
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    TCN_INIT_WSTRING(name);
+    len = (*e)->GetArrayLength((e), vals);
+    for (i = 0; i < len; i++) {
+        jstring s = (jstring)(*e)->GetObjectArrayElement((e), vals, i);
+        sl += (*e)->GetStringLength((e), s) + 1;
+    }
+    sl = (sl + 1) * 2;
+    p = msz = (jchar *)calloc(1, sl);
+    for (i = 0; i < len; i++) {
+        jsize   l;
+        jstring s = (jstring)(*e)->GetObjectArrayElement((e), vals, i);
+        l = (*e)->GetStringLength((e), s);
+        wcsncpy(p, (*e)->GetStringChars(e, s, 0), l);
+        p += l + 1;
+    }
+    rc = RegSetValueExW(k->key, J2W(name), 0, REG_MULTI_SZ,
+                        (CONST BYTE *)msz, sl);
+    TCN_FREE_WSTRING(name);
+    free(msz);
+    return rc;
+}
+
+TCN_IMPLEMENT_CALL(jint, Registry, setValueB)(TCN_STDARGS, jlong key,
+                                              jstring name,
+                                              jbyteArray val)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    TCN_ALLOC_WSTRING(name);
+    jsize nbytes = (*e)->GetArrayLength(e, val);
+    jbyte *bytes = (*e)->GetByteArrayElements(e, val, NULL);
+    LONG rc;
+
+    rc = RegSetValueExW(k->key, J2W(name), 0, REG_BINARY,
+                        bytes, (DWORD)nbytes);
+    (*e)->ReleaseByteArrayElements(e, val, bytes, JNI_ABORT);
+    TCN_FREE_WSTRING(name);
+    return rc;
+}
+
+#define MAX_VALUE_NAME 4096
+
+TCN_IMPLEMENT_CALL(jobjectArray, Registry, enumKeys)(TCN_STDARGS, jlong key)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    LONG rc;
+    jobjectArray v = NULL;
+    jsize cnt = 0;
+
+    WCHAR    achKey[MAX_PATH];
+    WCHAR    achClass[MAX_PATH] = L"";
+    DWORD    cchClassName = MAX_PATH;
+    DWORD    cSubKeys;
+    DWORD    cbMaxSubKey;
+    DWORD    cchMaxClass;
+    DWORD    cValues;
+    DWORD    cchMaxValue;
+    DWORD    cbMaxValueData;
+    DWORD    cbSecurityDescriptor;
+    FILETIME ftLastWriteTime;
+
+    DWORD cchValue = MAX_VALUE_NAME;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    rc = RegQueryInfoKeyW(k->key,
+                          achClass,
+                          &cchClassName,
+                          NULL,
+                          &cSubKeys,
+                          &cbMaxSubKey,
+                          &cchMaxClass,
+                          &cValues,
+                          &cchMaxValue,
+                          &cbMaxValueData,
+                          &cbSecurityDescriptor,
+                          &ftLastWriteTime);
+    if (rc != ERROR_SUCCESS) {
+        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+        goto cleanup;
+    }
+    cnt = cSubKeys;
+    if (cnt) {
+        jsize idx = 0;
+        v = tcn_new_arrays(e, cnt);
+        for (idx = 0; idx < cnt; idx++) {
+            jstring s;
+            DWORD achKeyLen = MAX_PATH;
+            rc = RegEnumKeyExW(k->key,
+                               idx,
+                               achKey,
+                               &achKeyLen,
+                               NULL,
+                               NULL,
+                               NULL,
+                               &ftLastWriteTime);
+            if (rc == (DWORD)ERROR_SUCCESS) {
+                s = (*e)->NewString((e), achKey, lstrlenW(achKey));
+                (*e)->SetObjectArrayElement((e), v, idx, s);
+            }
+        }
+    }
+cleanup:
+    return v;
+}
+
+TCN_IMPLEMENT_CALL(jobjectArray, Registry, enumValues)(TCN_STDARGS, jlong key)
+{
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+    LONG rc;
+    jobjectArray v = NULL;
+    jsize cnt = 0;
+
+    WCHAR    achClass[MAX_PATH] = L"";
+    DWORD    cchClassName = MAX_PATH;
+    DWORD    cSubKeys;
+    DWORD    cbMaxSubKey;
+    DWORD    cchMaxClass;
+    DWORD    cValues;
+    DWORD    cchMaxValue;
+    DWORD    cbMaxValueData;
+    DWORD    cbSecurityDescriptor;
+    FILETIME ftLastWriteTime;
+
+    WCHAR  achValue[MAX_VALUE_NAME];
+    DWORD  cchValue = MAX_VALUE_NAME;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(key != 0);
+    /* Get the class name and the value count. */
+    rc = RegQueryInfoKeyW(k->key,
+                          achClass,
+                          &cchClassName,
+                          NULL,
+                          &cSubKeys,
+                          &cbMaxSubKey,
+                          &cchMaxClass,
+                          &cValues,
+                          &cchMaxValue,
+                          &cbMaxValueData,
+                          &cbSecurityDescriptor,
+                          &ftLastWriteTime);
+    if (rc != ERROR_SUCCESS) {
+        tcn_ThrowAPRException(e, APR_FROM_OS_ERROR(rc));
+        goto cleanup;
+    }
+    cnt = cValues;
+    if (cnt) {
+        jsize idx = 0;
+        v = tcn_new_arrays(e, cnt);
+        for (idx = 0; idx < cnt; idx++) {
+            jstring s;
+            cchValue = MAX_VALUE_NAME;
+            achValue[0] = '\0';
+            rc = RegEnumValueW(k->key, idx,
+                               achValue,
+                               &cchValue,
+                               NULL,
+                               NULL,    // &dwType,
+                               NULL,    // &bData,
+                               NULL);   // &bcData
+            if (rc == (DWORD)ERROR_SUCCESS) {
+                s = (*e)->NewString((e), achValue, lstrlenW(achValue));
+                (*e)->SetObjectArrayElement((e), v, idx, s);
+            }
+        }
+    }
+cleanup:
+    return v;
+}
+
+TCN_IMPLEMENT_CALL(jint, Registry, deleteKey)(TCN_STDARGS, jint root, jstring name,
+                                              jboolean only_if_empty)
+{
+    DWORD rv;
+    TCN_ALLOC_WSTRING(name);
+
+    UNREFERENCED(o);
+    if (root < TCN_HKEY_CLASSES_ROOT || root > TCN_HKEY_USERS) {
+        rv = EBADF;
+        goto cleanup;
+    }
+    TCN_INIT_WSTRING(name);
+    if (only_if_empty)
+        rv = SHDeleteEmptyKeyW(TCN_KEYS[root].k, J2W(name));
+    else
+        rv = SHDeleteKeyW(TCN_KEYS[root].k, J2W(name));
+cleanup:
+    TCN_FREE_WSTRING(name);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, Registry, deleteValue)(TCN_STDARGS, jlong key,
+                                                jstring name)
+{
+    LONG rv;
+    TCN_ALLOC_WSTRING(name);
+    tcn_nt_registry_t *k = J2P(key, tcn_nt_registry_t *);
+
+    UNREFERENCED(o);
+    TCN_INIT_WSTRING(name);
+    rv = RegDeleteValueW(k->key, J2W(name));
+    TCN_FREE_WSTRING(name);
+    return (jint)rv;
+}
+
+#endif
+
diff --git a/c/os_win32_system.c b/c/os_win32_system.c
new file mode 100644
index 0000000..7d299f1
--- /dev/null
+++ b/c/os_win32_system.c
@@ -0,0 +1,473 @@
+#ifdef _WINDOWS
+
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: system.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0500
+#endif
+#include <winsock2.h>
+#include <mswsock.h>
+#include <ws2tcpip.h>
+#include <tlhelp32.h>
+
+#include "apr.h"
+#include "apr_pools.h"
+#include "apr_poll.h"
+#include "apr_network_io.h"
+#include "apr_arch_misc.h" /* for apr_os_level */
+#include "apr_arch_atime.h"  /* for FileTimeToAprTime */
+
+#include "tcn.h"
+#ifdef HAVE_OPENSSL
+#include "ssl_private.h"
+#endif
+
+#pragma warning(push)
+#pragma warning(disable : 4201)
+#if (_WIN32_WINNT < 0x0501)
+#include <winternl.h>
+#endif
+#include <psapi.h>
+#pragma warning(pop)
+
+
+static CRITICAL_SECTION dll_critical_section;   /* dll's critical section */
+static HINSTANCE        dll_instance = NULL;
+static SYSTEM_INFO      dll_system_info;
+static HANDLE           h_kernel = NULL;
+static HANDLE           h_ntdll  = NULL;
+static char             dll_file_name[MAX_PATH];
+
+typedef BOOL (WINAPI *pfnGetSystemTimes)(LPFILETIME, LPFILETIME, LPFILETIME);
+static pfnGetSystemTimes fnGetSystemTimes = NULL;
+#if (_WIN32_WINNT < 0x0501)
+typedef NTSTATUS (WINAPI *pfnNtQuerySystemInformation)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);
+static pfnNtQuerySystemInformation fnNtQuerySystemInformation = NULL;
+#endif
+
+BOOL
+WINAPI
+DllMain(
+    HINSTANCE instance,
+    DWORD reason,
+    LPVOID reserved)
+{
+
+    switch (reason) {
+        /** The DLL is loading due to process
+         *  initialization or a call to LoadLibrary.
+         */
+        case DLL_PROCESS_ATTACH:
+            InitializeCriticalSection(&dll_critical_section);
+            dll_instance = instance;
+            GetSystemInfo(&dll_system_info);
+            if ((h_kernel = LoadLibrary("kernel32.dll")) != NULL)
+                fnGetSystemTimes = (pfnGetSystemTimes)GetProcAddress(h_kernel,
+                                                            "GetSystemTimes");
+            if (fnGetSystemTimes == NULL) {
+                FreeLibrary(h_kernel);
+                h_kernel = NULL;
+#if (_WIN32_WINNT < 0x0501)
+                if ((h_ntdll = LoadLibrary("ntdll.dll")) != NULL)
+                    fnNtQuerySystemInformation =
+                        (pfnNtQuerySystemInformation)GetProcAddress(h_ntdll,
+                                                "NtQuerySystemInformation");
+
+                if (fnNtQuerySystemInformation == NULL) {
+                    FreeLibrary(h_ntdll);
+                    h_ntdll = NULL;
+                }
+#endif
+            }
+            GetModuleFileName(instance, dll_file_name, sizeof(dll_file_name));
+            break;
+        /** The attached process creates a new thread.
+         */
+        case DLL_THREAD_ATTACH:
+            break;
+
+        /** The thread of the attached process terminates.
+         */
+        case DLL_THREAD_DETACH:
+            break;
+
+        /** DLL unload due to process termination
+         *  or FreeLibrary.
+         */
+        case DLL_PROCESS_DETACH:
+            if (h_kernel)
+                FreeLibrary(h_kernel);
+            if (h_ntdll)
+                FreeLibrary(h_ntdll);
+            DeleteCriticalSection(&dll_critical_section);
+            break;
+
+        default:
+            break;
+    }
+
+    return TRUE;
+    UNREFERENCED_PARAMETER(reserved);
+}
+
+
+TCN_IMPLEMENT_CALL(jstring, OS, syserror)(TCN_STDARGS, jint err)
+{
+    jstring str;
+    void *buf;
+
+    UNREFERENCED(o);
+    if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+                       FORMAT_MESSAGE_FROM_SYSTEM |
+                       FORMAT_MESSAGE_IGNORE_INSERTS,
+                       NULL,
+                       err,
+                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+                       (LPTSTR)&buf,
+                       0,
+                       NULL)) {
+        str = AJP_TO_JSTRING("Unknown Error");
+    }
+    else {
+        str = AJP_TO_JSTRING((const char *)buf);
+        LocalFree(buf);
+    }
+    return str;
+}
+
+TCN_IMPLEMENT_CALL(jstring, OS, expand)(TCN_STDARGS, jstring val)
+{
+    jstring str;
+    jchar buf[TCN_BUFFER_SZ] = L"";
+    DWORD len;
+    TCN_ALLOC_WSTRING(val);
+
+    UNREFERENCED(o);
+    TCN_INIT_WSTRING(val);
+
+    len = ExpandEnvironmentStringsW(J2W(val), buf, TCN_BUFFER_SZ - 1);
+    if (len > (TCN_BUFFER_SZ - 1)) {
+        jchar *dbuf = malloc((len + 1) * 2);
+        ExpandEnvironmentStringsW(J2W(val), dbuf, len);
+        str = (*e)->NewString(e, dbuf, lstrlenW(dbuf));
+        free(dbuf);
+    }
+    else
+        str = (*e)->NewString(e, buf, lstrlenW(buf));
+
+    TCN_FREE_WSTRING(val);
+    return str;
+}
+
+#define LOG_MSG_EMERG                    0xC0000001L
+#define LOG_MSG_ERROR                    0xC0000002L
+#define LOG_MSG_NOTICE                   0x80000003L
+#define LOG_MSG_WARN                     0x80000004L
+#define LOG_MSG_INFO                     0x40000005L
+#define LOG_MSG_DEBUG                    0x00000006L
+#define LOG_MSG_DOMAIN                   "Native"
+
+static char log_domain[MAX_PATH] = "Native";
+
+static void init_log_source(const char *domain)
+{
+    HKEY  key;
+    DWORD ts;
+    char event_key[MAX_PATH];
+
+    strcpy(event_key, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\");
+    strcat(event_key, domain);
+    if (!RegCreateKey(HKEY_LOCAL_MACHINE, event_key, &key)) {
+        RegSetValueEx(key, "EventMessageFile", 0, REG_SZ, (LPBYTE)&dll_file_name[0],
+                      lstrlenA(dll_file_name) + 1);
+        ts = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
+
+        RegSetValueEx(key, "TypesSupported", 0, REG_DWORD, (LPBYTE) &ts, sizeof(DWORD));
+        RegCloseKey(key);
+    }
+    strcpy(log_domain, domain);
+}
+
+TCN_IMPLEMENT_CALL(void, OS, sysloginit)(TCN_STDARGS, jstring domain)
+{
+    const char *d;
+    TCN_ALLOC_CSTRING(domain);
+
+    UNREFERENCED(o);
+
+    if ((d = J2S(domain)) == NULL)
+        d = LOG_MSG_DOMAIN;
+    init_log_source(d);
+
+    TCN_FREE_CSTRING(domain);
+}
+
+TCN_IMPLEMENT_CALL(void, OS, syslog)(TCN_STDARGS, jint level,
+                                     jstring msg)
+{
+    TCN_ALLOC_CSTRING(msg);
+    DWORD id = LOG_MSG_DEBUG;
+    WORD  il = EVENTLOG_SUCCESS;
+    HANDLE  source;
+    const char *messages[1];
+    UNREFERENCED(o);
+
+    switch (level) {
+        case TCN_LOG_EMERG:
+            id = LOG_MSG_EMERG;
+            il = EVENTLOG_ERROR_TYPE;
+        break;
+        case TCN_LOG_ERROR:
+            id = LOG_MSG_ERROR;
+            il = EVENTLOG_ERROR_TYPE;
+        break;
+        case TCN_LOG_NOTICE:
+            id = LOG_MSG_NOTICE;
+            il = EVENTLOG_WARNING_TYPE;
+        break;
+        case TCN_LOG_WARN:
+            id = LOG_MSG_WARN;
+            il = EVENTLOG_WARNING_TYPE;
+        break;
+        case TCN_LOG_INFO:
+            id = LOG_MSG_INFO;
+            il = EVENTLOG_INFORMATION_TYPE;
+        break;
+    }
+
+    messages[0] = J2S(msg);
+    source = RegisterEventSource(NULL, log_domain);
+
+    if (source != NULL) {
+        ReportEvent(source, il,
+                    0,
+                    id,
+                    NULL,
+                    1, 0,
+                    messages, NULL);
+        DeregisterEventSource(source);
+    }
+
+    TCN_FREE_CSTRING(msg);
+}
+
+TCN_IMPLEMENT_CALL(jboolean, OS, is)(TCN_STDARGS, jint type)
+{
+    UNREFERENCED_STDARGS;
+#ifdef _WIN64
+    if (type == 4)
+        return JNI_TRUE;
+    else
+#endif
+    if (type == 3)
+        return JNI_TRUE;
+    else
+        return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jint, OS, info)(TCN_STDARGS,
+                                   jlongArray inf)
+{
+    MEMORYSTATUSEX ms;
+    ULONGLONG st[4];
+    FILETIME ft[4];
+    PROCESS_MEMORY_COUNTERS pmc;
+    jint rv;
+    int i;
+    jsize ilen = (*e)->GetArrayLength(e, inf);
+    jlong *pvals = (*e)->GetLongArrayElements(e, inf, NULL);
+
+    if (ilen < 16) {
+        return APR_EINVAL;
+    }
+    for (i = 0; i < 16; i++)
+        pvals[i] = 0;
+
+    ms.dwLength = sizeof(MEMORYSTATUSEX);
+
+    UNREFERENCED(o);
+    if (GlobalMemoryStatusEx(&ms)) {
+        pvals[0] = (jlong)ms.ullTotalPhys;
+        pvals[1] = (jlong)ms.ullAvailPhys;
+        pvals[2] = (jlong)ms.ullTotalPageFile;
+        pvals[3] = (jlong)ms.ullAvailPageFile;
+        /* Slots 4 and 5 are for shared memory */
+        pvals[6] = (jlong)ms.dwMemoryLoad;
+    }
+    else
+        goto cleanup;
+
+    memset(st, 0, sizeof(st));
+
+    if (fnGetSystemTimes) {
+        if ((*fnGetSystemTimes)(&ft[0], &ft[1], &ft[2])) {
+            st[0] = (((ULONGLONG)ft[0].dwHighDateTime << 32) | ft[0].dwLowDateTime) / 10;
+            st[1] = (((ULONGLONG)ft[1].dwHighDateTime << 32) | ft[1].dwLowDateTime) / 10;
+            st[2] = (((ULONGLONG)ft[2].dwHighDateTime << 32) | ft[2].dwLowDateTime) / 10;
+        }
+        else
+            goto cleanup;
+    }
+#if (_WIN32_WINNT < 0x0501)
+    else if (fnNtQuerySystemInformation) {
+        BYTE buf[2048]; /* This should ne enough for 32 processors */
+        NTSTATUS rs = (*fnNtQuerySystemInformation)(SystemProcessorPerformanceInformation,
+                                           (LPVOID)buf, 2048, NULL);
+        if (rs == 0) {
+            PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION pspi = (PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)&buf[0];
+            DWORD i;
+            /* Calculate all processors */
+            for (i = 0; i < dll_system_info.dwNumberOfProcessors; i++) {
+                st[0] += pspi[i].IdleTime.QuadPart / 10;
+                st[1] += pspi[i].KernelTime.QuadPart / 10;
+                st[2] += pspi[i].UserTime.QuadPart / 10;
+            }
+        }
+        else
+            goto cleanup;
+    }
+#endif
+    pvals[7] = st[0];
+    pvals[8] = st[1];
+    pvals[9] = st[2];
+
+    memset(st, 0, sizeof(st));
+    if (GetProcessTimes(GetCurrentProcess(), &ft[0], &ft[1], &ft[2], &ft[3])) {
+        FileTimeToAprTime((apr_time_t *)&st[0], &ft[0]);
+        st[1] = (((ULONGLONG)ft[2].dwHighDateTime << 32) | ft[2].dwLowDateTime) / 10;
+        st[2] = (((ULONGLONG)ft[3].dwHighDateTime << 32) | ft[3].dwLowDateTime) / 10;
+    }
+    pvals[10] = st[0];
+    pvals[11] = st[1];
+    pvals[12] = st[2];
+
+    if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
+        pvals[13] = pmc.WorkingSetSize;
+        pvals[14] = pmc.PeakWorkingSetSize;
+        pvals[15] = pmc.PageFaultCount;
+    }
+
+    (*e)->ReleaseLongArrayElements(e, inf, pvals, 0);
+    return APR_SUCCESS;
+cleanup:
+    rv = apr_get_os_error();
+    (*e)->ReleaseLongArrayElements(e, inf, pvals, 0);
+    return rv;
+}
+
+#ifdef HAVE_OPENSSL
+
+static DWORD WINAPI password_thread(void *data)
+{
+    tcn_pass_cb_t *cb = (tcn_pass_cb_t *)data;
+    MSG     msg;
+    HWINSTA hwss;
+    HWINSTA hwsu;
+    HDESK   hwds;
+    HDESK   hwdu;
+    HWND    hwnd;
+
+    /* Ensure connection to service window station and desktop, and
+     * save their handles.
+     */
+    GetDesktopWindow();
+    hwss = GetProcessWindowStation();
+    hwds = GetThreadDesktop(GetCurrentThreadId());
+
+    /* Impersonate the client and connect to the User's
+     * window station and desktop.
+     */
+    hwsu = OpenWindowStation("WinSta0", FALSE, MAXIMUM_ALLOWED);
+    if (hwsu == NULL) {
+        ExitThread(1);
+        return 1;
+    }
+    SetProcessWindowStation(hwsu);
+    hwdu = OpenDesktop("Default", 0, FALSE, MAXIMUM_ALLOWED);
+    if (hwdu == NULL) {
+        SetProcessWindowStation(hwss);
+        CloseWindowStation(hwsu);
+        ExitThread(1);
+        return 1;
+    }
+    SetThreadDesktop(hwdu);
+
+    hwnd = CreateDialog(dll_instance, MAKEINTRESOURCE(1001), NULL, NULL);
+    if (hwnd != NULL)
+        ShowWindow(hwnd, SW_SHOW);
+    else  {
+        ExitThread(1);
+        return 1;
+    }
+    while (1) {
+        if (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE)) {
+            if (msg.message == WM_KEYUP) {
+                int nVirtKey = (int)msg.wParam;
+                if (nVirtKey == VK_ESCAPE) {
+                    DestroyWindow(hwnd);
+                    break;
+                }
+                else if (nVirtKey == VK_RETURN) {
+                    HWND he = GetDlgItem(hwnd, 1002);
+                    if (he) {
+                        int n = GetWindowText(he, cb->password, SSL_MAX_PASSWORD_LEN - 1);
+                        cb->password[n] = '\0';
+                    }
+                    DestroyWindow(hwnd);
+                    break;
+                }
+            }
+            TranslateMessage(&msg);
+            DispatchMessage(&msg);
+        }
+        else
+            Sleep(100);
+    }
+    /* Restore window station and desktop.
+     */
+    SetThreadDesktop(hwds);
+    SetProcessWindowStation(hwss);
+    CloseDesktop(hwdu);
+    CloseWindowStation(hwsu);
+
+    ExitThread(0);
+    return 0;
+}
+
+int WIN32_SSL_password_prompt(tcn_pass_cb_t *data)
+{
+    DWORD id;
+    HANDLE thread;
+    /* TODO: See how to display this from service mode */
+    thread = CreateThread(NULL, 0,
+                password_thread, data,
+                0, &id);
+    WaitForSingleObject(thread, INFINITE);
+    CloseHandle(thread);
+    return (int)strlen(data->password);
+}
+
+#endif
+
+#endif
+
diff --git a/c/poll.c b/c/poll.c
new file mode 100644
index 0000000..1df8b63
--- /dev/null
+++ b/c/poll.c
@@ -0,0 +1,489 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: poll.c 1667243 2015-03-17 10:28:05Z markt $
+ */
+
+#include "tcn.h"
+
+#ifdef TCN_DO_STATISTICS
+static int sp_created       = 0;
+static int sp_destroyed     = 0;
+static int sp_cleared       = 0;
+#endif
+
+/* Internal poll structure for queryset
+ */
+typedef struct tcn_pollset {
+    apr_pool_t    *pool;
+    apr_int32_t   nelts;
+    apr_int32_t   nalloc;
+    apr_pollset_t *pollset;
+    jlong         *set;
+    apr_interval_time_t default_timeout;
+    /* A ring containing all of the pollfd_t that are active
+     */
+    APR_RING_HEAD(pfd_poll_ring_t, tcn_pfde_t) poll_ring;
+    /* A ring of pollfd_t that have been used, and then _remove()'d
+     */
+    APR_RING_HEAD(pfd_free_ring_t, tcn_pfde_t) free_ring;
+    /* A ring of pollfd_t where rings that have been _remove()`ed but
+     * might still be inside a _poll()
+     */
+    APR_RING_HEAD(pfd_dead_ring_t, tcn_pfde_t) dead_ring;
+#ifdef TCN_DO_STATISTICS
+    int sp_added;
+    int sp_max_count;
+    int sp_poll;
+    int sp_polled;
+    int sp_max_polled;
+    int sp_remove;
+    int sp_removed;
+    int sp_maintained;
+    int sp_max_maintained;
+    int sp_err_poll;
+    int sp_poll_timeout;
+    int sp_overflow;
+    int sp_equals;
+    int sp_eintr;
+#endif
+} tcn_pollset_t;
+
+#ifdef TCN_DO_STATISTICS
+static void sp_poll_statistics(tcn_pollset_t *p)
+{
+    fprintf(stderr, "Pollset Statistics ......\n");
+    fprintf(stderr, "Number of added sockets : %d\n", p->sp_added);
+    fprintf(stderr, "Max. number of sockets  : %d\n", p->sp_max_count);
+    fprintf(stderr, "Poll calls              : %d\n", p->sp_poll);
+    fprintf(stderr, "Poll timeouts           : %d\n", p->sp_poll_timeout);
+    fprintf(stderr, "Poll errors             : %d\n", p->sp_err_poll);
+    fprintf(stderr, "Poll overflows          : %d\n", p->sp_overflow);
+    fprintf(stderr, "Polled sockets          : %d\n", p->sp_polled);
+    fprintf(stderr, "Max. Polled sockets     : %d\n", p->sp_max_polled);
+    fprintf(stderr, "Poll remove             : %d\n", p->sp_remove);
+    fprintf(stderr, "Total removed           : %d\n", p->sp_removed);
+    fprintf(stderr, "Maintained              : %d\n", p->sp_maintained);
+    fprintf(stderr, "Max. maintained         : %d\n", p->sp_max_maintained);
+    fprintf(stderr, "Number of duplicates    : %d\n", p->sp_equals);
+    fprintf(stderr, "Number of interrupts    : %d\n", p->sp_eintr);
+
+}
+
+static apr_status_t sp_poll_cleanup(void *data)
+{
+    sp_cleared++;
+    sp_poll_statistics(data);
+    return APR_SUCCESS;
+}
+
+void sp_poll_dump_statistics()
+{
+    fprintf(stderr, "Poll Statistics .........\n");
+    fprintf(stderr, "Polls created           : %d\n", sp_created);
+    fprintf(stderr, "Polls destroyed         : %d\n", sp_destroyed);
+    fprintf(stderr, "Polls cleared           : %d\n", sp_cleared);
+}
+#endif
+
+TCN_IMPLEMENT_CALL(jlong, Poll, create)(TCN_STDARGS, jint size,
+                                        jlong pool, jint flags,
+                                        jlong default_timeout)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_pollset_t *pollset = NULL;
+    tcn_pollset_t *tps = NULL;
+    apr_uint32_t f = (apr_uint32_t)flags | APR_POLLSET_NOCOPY;
+    UNREFERENCED(o);
+    TCN_ASSERT(pool != 0);
+
+    if (f & APR_POLLSET_THREADSAFE) {
+        apr_status_t rv = apr_pollset_create(&pollset, (apr_uint32_t)size, p, f);
+        if (rv == APR_ENOTIMPL)
+            f &= ~APR_POLLSET_THREADSAFE;
+        else if (rv != APR_SUCCESS) {
+            tcn_ThrowAPRException(e, rv);
+            goto cleanup;
+        }
+    }
+    if (pollset == NULL) {
+        TCN_THROW_IF_ERR(apr_pollset_create(&pollset,
+                         (apr_uint32_t)size, p, f), pollset);
+    }
+    tps = apr_pcalloc(p, sizeof(tcn_pollset_t));
+    TCN_CHECK_ALLOCATED(tps);
+    tps->pollset = pollset;
+    tps->set     = apr_pcalloc(p, size * sizeof(jlong) * 2);
+    TCN_CHECK_ALLOCATED(tps->set);
+    APR_RING_INIT(&tps->poll_ring, tcn_pfde_t, link);
+    APR_RING_INIT(&tps->free_ring, tcn_pfde_t, link);
+    APR_RING_INIT(&tps->dead_ring, tcn_pfde_t, link);
+
+    tps->nelts  = 0;
+    tps->nalloc = size;
+    tps->pool   = p;
+    tps->default_timeout = J2T(default_timeout);
+#ifdef TCN_DO_STATISTICS
+    sp_created++;
+    apr_pool_cleanup_register(p, (const void *)tps,
+                              sp_poll_cleanup,
+                              apr_pool_cleanup_null);
+#endif
+cleanup:
+    return P2J(tps);
+}
+
+TCN_IMPLEMENT_CALL(jint, Poll, destroy)(TCN_STDARGS, jlong pollset)
+{
+    tcn_pollset_t *p = J2P(pollset,  tcn_pollset_t *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(pollset != 0);
+#ifdef TCN_DO_STATISTICS
+    sp_destroyed++;
+    apr_pool_cleanup_kill(p->pool, p, sp_poll_cleanup);
+    sp_poll_statistics(p);
+#endif
+    return (jint)apr_pollset_destroy(p->pollset);
+}
+
+static apr_status_t do_add(tcn_pollset_t *p, tcn_socket_t *s,
+                           apr_int16_t reqevents,
+                           apr_interval_time_t socket_timeout)
+{
+
+    apr_status_t rv;
+    apr_interval_time_t timeout = socket_timeout;
+    tcn_pfde_t *elem = NULL;
+
+    if (p->nelts == p->nalloc) {
+#ifdef TCN_DO_STATISTICS
+        p->sp_overflow++;
+#endif
+        return APR_ENOMEM;
+    }
+    if (s->pe != NULL) {
+        /* Socket is already added to the pollset.
+         */
+#ifdef TCN_DO_STATISTICS
+        p->sp_equals++;
+#endif
+        return APR_EEXIST;
+    }
+    if (timeout == TCN_NO_SOCKET_TIMEOUT) {
+        timeout = p->default_timeout;
+    }
+    if (timeout > 0)
+        s->last_active = apr_time_now();
+    else
+        s->last_active = 0;
+    s->timeout = socket_timeout;
+    if (!APR_RING_EMPTY(&p->free_ring, tcn_pfde_t, link)) {
+        elem = APR_RING_FIRST(&p->free_ring);
+        APR_RING_REMOVE(elem, link);
+    }
+    else {
+        elem = (tcn_pfde_t *)apr_palloc(p->pool, sizeof(tcn_pfde_t));
+        APR_RING_ELEM_INIT(elem, link);
+    }
+    elem->fd.reqevents   = reqevents;
+    elem->fd.desc_type   = APR_POLL_SOCKET;
+    elem->fd.desc.s      = s->sock;
+    elem->fd.client_data = s;
+#ifdef TCN_DO_STATISTICS
+    p->sp_added++;
+    p->sp_max_count = TCN_MAX(p->sp_max_count, p->sp_added);
+#endif
+    rv = apr_pollset_add(p->pollset, &elem->fd);
+    if (rv != APR_SUCCESS) {
+        APR_RING_INSERT_TAIL(&p->free_ring, elem, tcn_pfde_t, link);
+    }
+    else {
+        APR_RING_INSERT_TAIL(&p->poll_ring, elem, tcn_pfde_t, link);
+        s->pe = elem;
+    }
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, Poll, add)(TCN_STDARGS, jlong pollset,
+                                    jlong socket, jint reqevents)
+{
+    tcn_pollset_t *p = J2P(pollset,  tcn_pollset_t *);
+    tcn_socket_t *s  = J2P(socket, tcn_socket_t *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(socket != 0);
+
+    return (jint) do_add(p, s, (apr_int16_t)reqevents, TCN_NO_SOCKET_TIMEOUT);
+}
+
+TCN_IMPLEMENT_CALL(jint, Poll, addWithTimeout)(TCN_STDARGS, jlong pollset,
+                                               jlong socket, jint reqevents,
+                                               jlong socket_timeout)
+{
+    tcn_pollset_t *p = J2P(pollset,  tcn_pollset_t *);
+    tcn_socket_t *s  = J2P(socket, tcn_socket_t *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(socket != 0);
+
+    return (jint) do_add(p, s, (apr_int16_t)reqevents, J2T(socket_timeout));
+}
+
+TCN_IMPLEMENT_CALL(jint, Poll, remove)(TCN_STDARGS, jlong pollset,
+                                       jlong socket)
+{
+    apr_pollfd_t fd;
+    apr_status_t rv;
+    tcn_pollset_t *p = J2P(pollset,  tcn_pollset_t *);
+    tcn_socket_t  *s = J2P(socket, tcn_socket_t *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(socket != 0);
+
+    if (s->pe == NULL) {
+        /* Already removed */
+        return APR_NOTFOUND;
+    }
+    fd.desc_type   = APR_POLL_SOCKET;
+    fd.desc.s      = s->sock;
+    fd.client_data = s;
+    fd.reqevents   = APR_POLLIN | APR_POLLOUT;
+#ifdef TCN_DO_STATISTICS
+    p->sp_remove++;
+#endif
+
+    rv = apr_pollset_remove(p->pollset, &fd);
+    APR_RING_REMOVE(s->pe, link);
+    APR_RING_INSERT_TAIL(&p->dead_ring, s->pe, tcn_pfde_t, link);
+    s->pe = NULL;
+    p->nelts--;
+#ifdef TCN_DO_STATISTICS
+    p->sp_removed++;
+#endif
+    return rv;
+}
+
+
+TCN_IMPLEMENT_CALL(jint, Poll, poll)(TCN_STDARGS, jlong pollset,
+                                     jlong timeout, jlongArray set,
+                                     jboolean remove)
+{
+    const apr_pollfd_t *fd = NULL;
+    tcn_pollset_t *p = J2P(pollset,  tcn_pollset_t *);
+    apr_int32_t  i, num = 0;
+    apr_status_t rv = APR_SUCCESS;
+    apr_time_t now = 0;
+    apr_interval_time_t ptime = J2T(timeout);
+    UNREFERENCED(o);
+    TCN_ASSERT(pollset != 0);
+
+#ifdef TCN_DO_STATISTICS
+     p->sp_poll++;
+#endif
+
+    if (ptime > 0) {
+        tcn_pfde_t  *ep;
+
+        now = apr_time_now();
+        /* Find the minimum timeout */
+        APR_RING_FOREACH(ep, &p->poll_ring, tcn_pfde_t, link)
+        {
+            apr_interval_time_t socket_timeout = 0;
+            tcn_socket_t *s = (tcn_socket_t *)ep->fd.client_data;
+            if (s->timeout == TCN_NO_SOCKET_TIMEOUT) {
+                socket_timeout = p->default_timeout;
+            }
+            else {
+                socket_timeout = s->timeout;
+            }
+            if (socket_timeout >= 0) {
+                apr_interval_time_t t = now - s->last_active;
+                if (t >= socket_timeout) {
+                    ptime = 0;
+                    break;
+                }
+                else {
+                    ptime = TCN_MIN(socket_timeout - t, ptime);
+                }
+            }
+        }
+    }
+    else if (ptime < 0)
+        ptime = 0;
+    for (;;) {
+        rv = apr_pollset_poll(p->pollset, ptime, &num, &fd);
+        if (rv != APR_SUCCESS) {
+            if (APR_STATUS_IS_EINTR(rv)) {
+#ifdef TCN_DO_STATISTICS
+                p->sp_eintr++;
+#endif
+                continue;
+            }
+            TCN_ERROR_WRAP(rv);
+#ifdef TCN_DO_STATISTICS
+            if (rv == TCN_TIMEUP)
+                p->sp_poll_timeout++;
+            else
+                p->sp_err_poll++;
+#endif
+            num = (apr_int32_t)(-rv);
+        }
+        break;
+    }
+    /* Shift all PFDs in the Dead Ring to the Free Ring */
+    APR_RING_CONCAT(&p->free_ring, &p->dead_ring, tcn_pfde_t, link);
+    if (num > 0) {
+#ifdef TCN_DO_STATISTICS
+         p->sp_polled += num;
+         p->sp_max_polled = TCN_MAX(p->sp_max_polled, num);
+#endif
+        if (!remove)
+            now = apr_time_now();
+        for (i = 0; i < num; i++) {
+            tcn_socket_t *s = (tcn_socket_t *)fd->client_data;
+            p->set[i*2+0] = (jlong)(fd->rtnevents);
+            p->set[i*2+1] = P2J(s);
+            /* If a socket is registered for multiple events and the poller has
+               multiple events to return it may do as a single pair in this
+               array or as multiple pairs depending on implementation. On OSX at
+               least, multiple pairs have been observed. In this case do not try
+               and remove socket from the pollset for a second time else a crash
+               will result. */ 
+            if (remove) {
+                if (s->pe) {
+                    apr_pollset_remove(p->pollset, fd);
+                    APR_RING_REMOVE(s->pe, link);
+                    APR_RING_INSERT_TAIL(&p->dead_ring, s->pe, tcn_pfde_t, link);
+                    s->pe = NULL;
+                    p->nelts--;
+#ifdef TCN_DO_STATISTICS
+                    p->sp_removed++;
+#endif
+                }
+            }
+            else {
+                /* Update last active with the current time
+                 * after the poll call.
+                 */
+                s->last_active = now;
+            }
+            fd ++;
+        }
+        (*e)->SetLongArrayRegion(e, set, 0, num * 2, p->set);
+    }
+
+    return (jint)num;
+}
+
+TCN_IMPLEMENT_CALL(jint, Poll, maintain)(TCN_STDARGS, jlong pollset,
+                                         jlongArray set, jboolean remove)
+{
+    tcn_pollset_t *p = J2P(pollset,  tcn_pollset_t *);
+    apr_int32_t  i = 0, num = 0;
+    apr_time_t now = apr_time_now();
+    tcn_pfde_t   *ep, *ip;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pollset != 0);
+
+    /* Check for timeout sockets */
+    APR_RING_FOREACH_SAFE(ep, ip, &p->poll_ring, tcn_pfde_t, link)
+    {
+        apr_interval_time_t timeout = 0;
+        tcn_socket_t *s = (tcn_socket_t *)ep->fd.client_data;
+        if (s->timeout == TCN_NO_SOCKET_TIMEOUT) {
+            timeout = p->default_timeout;
+        }
+        else {
+            timeout = s->timeout;
+        }
+        if (timeout == -1) {
+            continue;
+        }
+        if ((now - s->last_active) >= timeout) {
+            p->set[num++] = P2J(s);
+            if (remove) {
+                APR_RING_REMOVE(ep, link);
+                APR_RING_INSERT_TAIL(&p->dead_ring, ep, tcn_pfde_t, link);
+                s->pe = NULL;
+                p->nelts--;
+#ifdef TCN_DO_STATISTICS
+                p->sp_removed++;
+#endif
+            }
+        }
+    }
+    if (num) {
+#ifdef TCN_DO_STATISTICS
+        p->sp_maintained += num;
+        p->sp_max_maintained = TCN_MAX(p->sp_max_maintained, num);
+#endif
+        if (remove) {
+            for (i = 0; i < num; i++) {
+                apr_pollfd_t fd;
+                tcn_socket_t *s = J2P(p->set[i], tcn_socket_t *);
+                fd.desc_type    = APR_POLL_SOCKET;
+                fd.desc.s       = s->sock;
+                fd.client_data  = s;
+                fd.reqevents    = APR_POLLIN | APR_POLLOUT;
+                apr_pollset_remove(p->pollset, &fd);
+            }
+        }
+        (*e)->SetLongArrayRegion(e, set, 0, num, p->set);
+    }
+    return (jint)num;
+}
+
+TCN_IMPLEMENT_CALL(void, Poll, setTtl)(TCN_STDARGS, jlong pollset,
+                                       jlong default_timeout)
+{
+    tcn_pollset_t *p = J2P(pollset,  tcn_pollset_t *);
+    UNREFERENCED_STDARGS;
+    p->default_timeout = J2T(default_timeout);
+}
+
+TCN_IMPLEMENT_CALL(jlong, Poll, getTtl)(TCN_STDARGS, jlong pollset)
+{
+    tcn_pollset_t *p = J2P(pollset,  tcn_pollset_t *);
+    UNREFERENCED_STDARGS;
+    return (jlong)p->default_timeout;
+}
+
+TCN_IMPLEMENT_CALL(jint, Poll, pollset)(TCN_STDARGS, jlong pollset,
+                                        jlongArray set)
+{
+    tcn_pollset_t *p = J2P(pollset,  tcn_pollset_t *);
+    apr_int32_t n = 0;
+    tcn_pfde_t *ep;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pollset != 0);
+
+    APR_RING_FOREACH(ep, &p->poll_ring, tcn_pfde_t, link)
+    {
+        apr_pollfd_t *fd = &ep->fd;
+        fd->rtnevents = APR_POLLHUP | APR_POLLIN;
+        p->set[n++]   = (jlong)(fd->rtnevents);
+        p->set[n++]   = P2J(fd->client_data);
+    }
+    if (n > 0)
+        (*e)->SetLongArrayRegion(e, set, 0, n, p->set);
+    return n / 2;
+}
diff --git a/c/pool.c b/c/pool.c
new file mode 100644
index 0000000..f6c31fb
--- /dev/null
+++ b/c/pool.c
@@ -0,0 +1,250 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: pool.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+
+#include "tcn.h"
+
+extern apr_pool_t *tcn_global_pool;
+
+static apr_status_t generic_pool_cleanup(void *data)
+{
+    apr_status_t rv = APR_SUCCESS;
+    tcn_callback_t *cb = (tcn_callback_t *)data;
+
+    if (data) {
+        JNIEnv *env;
+        tcn_get_java_env(&env);
+        if (!TCN_IS_NULL(env, cb->obj)) {
+            rv = (*(env))->CallIntMethod(env, cb->obj, cb->mid[0],
+                                         NULL);
+            TCN_UNLOAD_CLASS(env, cb->obj);
+        }
+        free(cb);
+    }
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, Pool, create)(TCN_STDARGS, jlong parent)
+{
+    apr_pool_t *p = J2P(parent, apr_pool_t *);
+    apr_pool_t *n;
+
+    UNREFERENCED(o);
+    /* Make sure our global pool is accessor for all pools */
+    if (p == NULL)
+        p = tcn_global_pool;
+    TCN_THROW_IF_ERR(apr_pool_create(&n, p), n);
+cleanup:
+    return P2J(n);
+}
+
+TCN_IMPLEMENT_CALL(void, Pool, clear)(TCN_STDARGS, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(pool != 0);
+    apr_pool_clear(p);
+}
+
+TCN_IMPLEMENT_CALL(void, Pool, destroy)(TCN_STDARGS, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(pool != 0);
+    apr_pool_destroy(p);
+}
+
+TCN_IMPLEMENT_CALL(jlong, Pool, parentGet)(TCN_STDARGS, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(pool != 0);
+    return P2J(apr_pool_parent_get(p));
+}
+
+TCN_IMPLEMENT_CALL(jboolean, Pool, isAncestor)(TCN_STDARGS, jlong a, jlong b)
+{
+    apr_pool_t *pa = J2P(a, apr_pool_t *);
+    apr_pool_t *pb = J2P(b, apr_pool_t *);
+    UNREFERENCED_STDARGS;
+    return apr_pool_is_ancestor(pa, pb) ? JNI_TRUE : JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jlong, Pool, palloc)(TCN_STDARGS, jlong pool, jint size)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    UNREFERENCED_STDARGS;
+    return P2J(apr_palloc(p, (apr_size_t)size));
+}
+
+TCN_IMPLEMENT_CALL(jlong, Pool, pcalloc)(TCN_STDARGS, jlong pool, jint size)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    UNREFERENCED_STDARGS;
+    return P2J(apr_pcalloc(p, (apr_size_t)size));
+}
+
+TCN_IMPLEMENT_CALL(jlong, Pool, cleanupRegister)(TCN_STDARGS, jlong pool,
+                                                 jobject obj)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    tcn_callback_t *cb = (tcn_callback_t *)malloc(sizeof(tcn_callback_t));
+    jclass cls;
+
+    UNREFERENCED(o);
+
+    if (cb == NULL) {
+       TCN_THROW_OS_ERROR(e);
+       return 0;
+    }
+    cls = (*e)->GetObjectClass(e, obj);
+    cb->obj    = (*e)->NewGlobalRef(e, obj);
+    cb->mid[0] = (*e)->GetMethodID(e, cls, "callback", "()I");
+
+    apr_pool_cleanup_register(p, (const void *)cb,
+                              generic_pool_cleanup,
+                              apr_pool_cleanup_null);
+
+    return P2J(cb);
+}
+
+TCN_IMPLEMENT_CALL(void, Pool, cleanupKill)(TCN_STDARGS, jlong pool,
+                                            jlong data)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    tcn_callback_t *cb = J2P(data, tcn_callback_t *);
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pool != 0);
+    apr_pool_cleanup_kill(p, cb, generic_pool_cleanup);
+    (*e)->DeleteGlobalRef(e, cb->obj);
+    free(cb);
+}
+
+TCN_IMPLEMENT_CALL(jobject, Pool, alloc)(TCN_STDARGS, jlong pool,
+                                         jint size)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_size_t sz = (apr_size_t)size;
+    void *mem;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pool != 0);
+
+    if ((mem = apr_palloc(p, sz)) != NULL)
+        return (*e)->NewDirectByteBuffer(e, mem, (jlong)sz);
+    else
+        return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jobject, Pool, calloc)(TCN_STDARGS, jlong pool,
+                                          jint size)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_size_t sz = (apr_size_t)size;
+    void *mem;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pool != 0);
+
+    if ((mem = apr_pcalloc(p, sz)) != NULL)
+        return (*e)->NewDirectByteBuffer(e, mem, (jlong)sz);
+    else
+        return NULL;
+}
+
+static apr_status_t generic_pool_data_cleanup(void *data)
+{
+    apr_status_t rv = APR_SUCCESS;
+    tcn_callback_t *cb = (tcn_callback_t *)data;
+
+    if (data) {
+        JNIEnv *env;
+        tcn_get_java_env(&env);
+
+        if (!TCN_IS_NULL(env, cb->obj)) {
+            TCN_UNLOAD_CLASS(env, cb->obj);
+        }
+        free(cb);
+    }
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, Pool, dataSet)(TCN_STDARGS, jlong pool,
+                                        jstring key, jobject data)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_status_t rv = APR_SUCCESS;
+    void *old = NULL;
+    TCN_ALLOC_CSTRING(key);
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pool != 0);
+
+    if (apr_pool_userdata_get(&old, J2S(key), p) == APR_SUCCESS) {
+        if (old)
+            apr_pool_cleanup_run(p, old, generic_pool_data_cleanup);
+    }
+    if (data) {
+        JNIEnv *e;
+        tcn_callback_t *cb = (tcn_callback_t *)malloc(sizeof(tcn_callback_t));
+        tcn_get_java_env(&e);
+        cb->obj = (*e)->NewGlobalRef(e, data);
+        if ((rv = apr_pool_userdata_set(cb, J2S(key), generic_pool_data_cleanup,
+                                        p)) != APR_SUCCESS) {
+            (*e)->DeleteGlobalRef(e, cb->obj);
+            free(cb);
+        }
+    }
+    else {
+        /* Clear the exiting user data */
+        rv = apr_pool_userdata_set(NULL, J2S(key), NULL, p);
+    }
+    TCN_FREE_CSTRING(key);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jobject, Pool, dataGet)(TCN_STDARGS, jlong pool,
+                                           jstring key)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    void *old = NULL;
+    TCN_ALLOC_CSTRING(key);
+    jobject rv = NULL;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(pool != 0);
+
+    if (apr_pool_userdata_get(&old, J2S(key), p) == APR_SUCCESS) {
+        if (old) {
+            tcn_callback_t *cb = (tcn_callback_t *)old;
+            rv = cb->obj;
+        }
+    }
+    TCN_FREE_CSTRING(key);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(void, Pool, cleanupForExec)(TCN_STDARGS)
+{
+    UNREFERENCED_STDARGS;
+    apr_pool_cleanup_for_exec();
+}
diff --git a/c/proc.c b/c/proc.c
new file mode 100644
index 0000000..18902dc
--- /dev/null
+++ b/c/proc.c
@@ -0,0 +1,453 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: proc.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+ 
+#include "tcn.h"
+#include "apr_thread_proc.h"
+#include "apr_version.h"
+
+#define ERRFN_USERDATA_KEY    "TCNATIVECHILDERRFN"
+
+static void generic_child_errfn(apr_pool_t *pool, apr_status_t err,
+                                const char *description)
+{
+    void *data;
+    tcn_callback_t *cb;
+
+    apr_pool_userdata_get(&data, ERRFN_USERDATA_KEY, pool);
+    cb = (tcn_callback_t *)data;
+    if (cb) {
+        JNIEnv *env;
+        tcn_get_java_env(&env);
+        if (!TCN_IS_NULL(env, cb->obj)) {
+            (*(env))->CallVoidMethod(env, cb->obj, cb->mid[0],
+                                P2J(pool), (jint)err,
+                                (*(env))->NewStringUTF(env, description),
+                                NULL);
+        }
+    }
+}
+
+static apr_status_t child_errfn_pool_cleanup(void *data)
+{
+    tcn_callback_t *cb = (tcn_callback_t *)data;
+
+    if (data) {
+        JNIEnv *env;
+        tcn_get_java_env(&env);
+        if (!TCN_IS_NULL(env, cb->obj)) {
+            TCN_UNLOAD_CLASS(env, cb->obj);
+        }
+        free(cb);
+    }
+    return APR_SUCCESS;
+}
+
+TCN_IMPLEMENT_CALL(jlong, Procattr, create)(TCN_STDARGS,
+                                            jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_procattr_t *attr;
+
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_procattr_create(&attr, p), attr);
+
+cleanup:
+    return P2J(attr);
+}
+
+TCN_IMPLEMENT_CALL(jint, Procattr, ioSet)(TCN_STDARGS,
+                                          jlong attr, jint in,
+                                          jint out, jint err)
+{
+    apr_procattr_t *a = J2P(attr, apr_procattr_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_procattr_io_set(a, (apr_int32_t)in,
+                     (apr_int32_t)out, (apr_int32_t)err);
+}
+
+TCN_IMPLEMENT_CALL(jint, Procattr, childInSet)(TCN_STDARGS,
+                                          jlong attr, jlong in,
+                                          jlong parent)
+{
+    apr_procattr_t *a = J2P(attr, apr_procattr_t *);
+    apr_file_t *f = J2P(in, apr_file_t *);
+    apr_file_t *p = J2P(parent, apr_file_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_procattr_child_in_set(a, f, p);
+}
+
+TCN_IMPLEMENT_CALL(jint, Procattr, childOutSet)(TCN_STDARGS,
+                                          jlong attr, jlong out,
+                                          jlong parent)
+{
+    apr_procattr_t *a = J2P(attr, apr_procattr_t *);
+    apr_file_t *f = J2P(out, apr_file_t *);
+    apr_file_t *p = J2P(parent, apr_file_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_procattr_child_out_set(a, f, p);
+}
+
+TCN_IMPLEMENT_CALL(jint, Procattr, childErrSet)(TCN_STDARGS,
+                                          jlong attr, jlong err,
+                                          jlong parent)
+{
+    apr_procattr_t *a = J2P(attr, apr_procattr_t *);
+    apr_file_t *f = J2P(err, apr_file_t *);
+    apr_file_t *p = J2P(parent, apr_file_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_procattr_child_in_set(a, f, p);
+}
+
+TCN_IMPLEMENT_CALL(jint, Procattr, dirSet)(TCN_STDARGS,
+                                           jlong attr,
+                                           jstring dir)
+{
+    apr_status_t rv;
+    apr_procattr_t *a = J2P(attr, apr_procattr_t *);
+    TCN_ALLOC_CSTRING(dir);
+
+    UNREFERENCED(o);
+
+    rv = apr_procattr_dir_set(a, J2S(dir));
+    TCN_FREE_CSTRING(dir);
+    return (jint) rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, Procattr, cmdtypeSet)(TCN_STDARGS,
+                                          jlong attr, jint cmd)
+{
+    apr_procattr_t *a = J2P(attr, apr_procattr_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_procattr_cmdtype_set(a, (apr_int32_t)cmd);
+}
+
+TCN_IMPLEMENT_CALL(jint, Procattr, detachSet)(TCN_STDARGS,
+                                          jlong attr, jint detach)
+{
+    apr_procattr_t *a = J2P(attr, apr_procattr_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_procattr_detach_set(a, (apr_int32_t)detach);
+}
+
+TCN_IMPLEMENT_CALL(jint, Procattr, errorCheckSet)(TCN_STDARGS,
+                                          jlong attr, jint chk)
+{
+    apr_procattr_t *a = J2P(attr, apr_procattr_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_procattr_error_check_set(a, (apr_int32_t)chk);
+}
+
+TCN_IMPLEMENT_CALL(jint, Procattr, addrspaceSet)(TCN_STDARGS,
+                                          jlong attr, jint addr)
+{
+    apr_procattr_t *a = J2P(attr, apr_procattr_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_procattr_addrspace_set(a, (apr_int32_t)addr);
+}
+
+TCN_IMPLEMENT_CALL(jlong, Proc, alloc)(TCN_STDARGS,
+                                       jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_proc_t *proc;
+
+    UNREFERENCED_STDARGS;
+    proc = (apr_proc_t *)apr_pcalloc(p, sizeof(apr_proc_t));
+
+    return P2J(proc);
+}
+
+#define MAX_ARGS_SIZE 1024
+#define MAX_ENV_SIZE  1024
+
+TCN_IMPLEMENT_CALL(jint, Proc, create)(TCN_STDARGS, jlong proc,
+                                       jstring progname,
+                                       jobjectArray args,
+                                       jobjectArray env,
+                                       jlong attr, jlong pool)
+{
+    apr_status_t rv;
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_procattr_t *a = J2P(attr, apr_procattr_t *);
+    apr_proc_t *np = J2P(proc, apr_proc_t *);
+    TCN_ALLOC_CSTRING(progname);
+    char *s_args[MAX_ARGS_SIZE];
+    char *s_env[MAX_ENV_SIZE];
+    const char * const *pargs = NULL;
+    const char * const *penv  = NULL;
+    jsize as = 0;
+    jsize es = 0;
+    jsize i;
+
+    UNREFERENCED(o);
+    if (args)
+        as = (*e)->GetArrayLength(e, args);
+    if (env)
+        es = (*e)->GetArrayLength(e, env);
+    if (as > (MAX_ARGS_SIZE - 1) || es > (MAX_ENV_SIZE - 2)) {
+        TCN_FREE_CSTRING(progname);
+        return APR_EINVAL;
+    }
+    if (as) {
+        for (i = 0; i < as; i++) {
+            jstring str = (*e)->GetObjectArrayElement(e, args, i);
+            s_args[i] = tcn_get_string(e, str);
+            (*e)->DeleteLocalRef(e, str);
+        }
+        s_args[i] = NULL;
+        pargs = (const char * const *)&s_args[0];
+    }
+    if (es) {
+        for (i = 0; i < es; i++) {
+            jstring str = (*e)->GetObjectArrayElement(e, env, i);
+            s_env[i] = tcn_get_string(e, str);
+            (*e)->DeleteLocalRef(e, str);
+        }
+#ifdef WIN32
+        s_env[i++] = apr_psprintf(p, TCN_PARENT_IDE "=%d", getpid());
+#endif
+        s_env[i] = NULL;
+        penv = (const char * const *)&s_env[0];
+    }
+#ifdef WIN32
+    else {
+        char pps[32];
+        itoa(getpid(), pps, 10);
+        SetEnvironmentVariable(TCN_PARENT_IDE, pps);
+    }
+#endif
+    rv = apr_proc_create(np, J2S(progname), pargs,
+                         penv, a, p);
+#ifdef WIN32
+    if (!es)
+        SetEnvironmentVariable(TCN_PARENT_IDE, NULL);
+#endif
+
+    /* Free local resources */
+    TCN_FREE_CSTRING(progname);
+    for (i = 0; i < as; i++) {
+        if (s_args[i])
+            free(s_args[i]);
+    }
+    for (i = 0; i < es; i++) {
+        if (s_env[i])
+            free(s_env[i]);
+    }
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, Proc, wait)(TCN_STDARGS, jlong proc,
+                                     jintArray rvals, jint waithow)
+{
+    apr_status_t rv;
+    apr_proc_t *p = J2P(proc, apr_proc_t *);
+    int exitcode;
+    apr_exit_why_e exitwhy;
+
+    UNREFERENCED(o);
+
+    rv = apr_proc_wait(p, &exitcode, &exitwhy, (apr_wait_how_e)waithow);
+    if (rv == APR_SUCCESS && rvals) {
+        jsize n = (*e)->GetArrayLength(e, rvals);
+        if (n > 1) {
+            jint *ints = (*e)->GetIntArrayElements(e, rvals, NULL);
+            ints[0] = exitcode;
+            ints[1] = exitwhy;
+            (*e)->ReleaseIntArrayElements(e, rvals, ints, 0);
+        }
+    }
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, Proc, waitAllProcs)(TCN_STDARGS,
+                                             jlong proc, jintArray rvals,
+                                             jint waithow, jlong pool)
+{
+    apr_status_t rv;
+    apr_proc_t *p = J2P(proc, apr_proc_t *);
+    apr_pool_t *c = J2P(pool, apr_pool_t *);
+    int exitcode;
+    apr_exit_why_e exitwhy;
+
+    UNREFERENCED(o);
+
+    rv = apr_proc_wait_all_procs(p, &exitcode, &exitwhy,
+                                 (apr_wait_how_e)waithow, c);
+    if (rv == APR_SUCCESS && rvals) {
+        jsize n = (*e)->GetArrayLength(e, rvals);
+        if (n > 1) {
+            jint *ints = (*e)->GetIntArrayElements(e, rvals, NULL);
+            ints[0] = exitcode;
+            ints[1] = exitwhy;
+            (*e)->ReleaseIntArrayElements(e, rvals, ints, 0);
+        }
+    }
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, Proc, detach)(TCN_STDARGS, jint daemonize)
+{
+
+    UNREFERENCED_STDARGS;
+#if defined(WIN32) || defined (NETWARE)
+    UNREFERENCED(daemonize);
+    return APR_ENOTIMPL;
+#else
+    return (jint)apr_proc_detach(daemonize);
+#endif
+}
+
+TCN_IMPLEMENT_CALL(jint, Proc, kill)(TCN_STDARGS, jlong proc, jint sig)
+{
+    apr_proc_t *p = J2P(proc, apr_proc_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_proc_kill(p, (int)sig);
+}
+
+TCN_IMPLEMENT_CALL(void, Pool, noteSubprocess)(TCN_STDARGS, jlong pool,
+                                               jlong proc, jint how)
+{
+    apr_proc_t *p = J2P(proc, apr_proc_t *);
+    apr_pool_t *a = J2P(pool, apr_pool_t *);
+
+    UNREFERENCED_STDARGS;
+    apr_pool_note_subprocess(a, p, (apr_kill_conditions_e)how);
+}
+
+TCN_IMPLEMENT_CALL(jint, Proc, fork)(TCN_STDARGS,
+                                     jlongArray proc,
+                                     jlong pool)
+{
+    apr_status_t rv = APR_EINVAL;
+
+#if APR_HAS_FORK
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_proc_t *f = apr_pcalloc(p, sizeof(apr_proc_t));
+
+    UNREFERENCED(o);
+
+    rv = apr_proc_fork(f, p);
+    if (rv == APR_SUCCESS && proc) {
+        jsize n = (*e)->GetArrayLength(e, proc);
+        if (n > 0) {
+            jlong *rp = (*e)->GetLongArrayElements(e, proc, NULL);
+            rp[0] = P2J(f);
+            (*e)->ReleaseLongArrayElements(e, proc, rp, 0);
+        }
+    }
+#else
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(proc);
+    UNREFERENCED(pool);
+
+#endif
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(void, Procattr, errfnSet)(TCN_STDARGS, jlong attr,
+                                             jlong pool, jobject obj)
+{
+    apr_procattr_t *a = J2P(attr, apr_procattr_t *);
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    tcn_callback_t *cb = (tcn_callback_t *)malloc(sizeof(tcn_callback_t));
+    jclass cls;
+
+    UNREFERENCED(o);
+
+    if (cb == NULL) {
+       return;
+    }
+    cls = (*e)->GetObjectClass(e, obj);
+    cb->obj    = (*e)->NewGlobalRef(e, obj);
+    cb->mid[0] = (*e)->GetMethodID(e, cls, "callback", "(JILjava/lang/String;)V");
+
+    apr_pool_userdata_setn(cb, ERRFN_USERDATA_KEY, child_errfn_pool_cleanup, p);
+    apr_procattr_child_errfn_set(a, generic_child_errfn);
+
+}
+
+TCN_IMPLEMENT_CALL(jint, Procattr, userSet)(TCN_STDARGS,
+                                            jlong attr,
+                                            jstring username,
+                                            jstring password)
+{
+
+#if ((APR_MAJOR_VERSION >= 1) && (APR_MINOR_VERSION >= 1))
+    apr_status_t rv;
+    apr_procattr_t *a = J2P(attr, apr_procattr_t *);
+    TCN_ALLOC_CSTRING(username);
+#if APR_PROCATTR_USER_SET_REQUIRES_PASSWORD
+    TCN_ALLOC_CSTRING(password);
+#else
+    const char *cpassword = NULL;
+#endif
+    UNREFERENCED(o);
+
+    rv = apr_procattr_user_set(a, J2S(username), J2S(password));
+    TCN_FREE_CSTRING(username);
+#if APR_PROCATTR_USER_SET_REQUIRES_PASSWORD
+    TCN_FREE_CSTRING(password);
+#endif
+    return (jint) rv;
+#else
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(attr);
+    UNREFERENCED(username);
+    UNREFERENCED(password);
+
+    return APR_ENOTIMPL;
+#endif
+}
+
+TCN_IMPLEMENT_CALL(jint, Procattr, groupSet)(TCN_STDARGS,
+                                             jlong attr,
+                                             jstring group)
+{
+
+#if ((APR_MAJOR_VERSION >= 1) && (APR_MINOR_VERSION >= 1))
+    apr_status_t rv;
+    apr_procattr_t *a = J2P(attr, apr_procattr_t *);
+    TCN_ALLOC_CSTRING(group);
+
+    UNREFERENCED(o);
+
+    rv = apr_procattr_group_set(a, J2S(group));
+    TCN_FREE_CSTRING(group);
+    return (jint) rv;
+#else
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(attr);
+    UNREFERENCED(group);
+
+    return APR_ENOTIMPL;
+#endif
+}
diff --git a/c/shm.c b/c/shm.c
new file mode 100644
index 0000000..a415585
--- /dev/null
+++ b/c/shm.c
@@ -0,0 +1,127 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: shm.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+ 
+#include "tcn.h"
+#include "apr_shm.h"
+
+TCN_IMPLEMENT_CALL(jlong, Shm, create)(TCN_STDARGS, jlong reqsize,
+                                       jstring filename,
+                                       jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    const char *fname = NULL;
+    apr_shm_t *shm;
+
+
+    UNREFERENCED(o);
+    if (filename)
+        fname = (const char *)((*e)->GetStringUTFChars(e, filename, 0));
+    TCN_THROW_IF_ERR(apr_shm_create(&shm, (apr_size_t)reqsize,
+                                    fname, p), shm);
+
+cleanup:
+    if (fname)
+        (*e)->ReleaseStringUTFChars(e, filename, fname);
+    return P2J(shm);
+}
+
+TCN_IMPLEMENT_CALL(jint, Shm, remove)(TCN_STDARGS,
+                                      jstring filename,
+                                      jlong pool)
+{
+    apr_status_t rv;
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    TCN_ALLOC_CSTRING(filename);
+
+
+    UNREFERENCED(o);
+    rv = apr_shm_remove(J2S(filename), p);
+    TCN_FREE_CSTRING(filename);
+
+    return (jint)rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, Shm, destroy)(TCN_STDARGS, jlong shm)
+{
+    apr_shm_t *s = J2P(shm, apr_shm_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_shm_destroy(s);
+}
+
+TCN_IMPLEMENT_CALL(jlong, Shm, attach)(TCN_STDARGS,
+                                       jstring filename,
+                                       jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    const char *fname = NULL;
+    apr_shm_t *shm;
+
+
+    UNREFERENCED(o);
+    if (filename)
+        fname = (const char *)((*e)->GetStringUTFChars(e, filename, 0));
+    TCN_THROW_IF_ERR(apr_shm_attach(&shm, fname, p), shm);
+
+cleanup:
+    if (fname)
+        (*e)->ReleaseStringUTFChars(e, filename, fname);
+    return P2J(shm);
+}
+
+TCN_IMPLEMENT_CALL(jint, Shm, detach)(TCN_STDARGS, jlong shm)
+{
+    apr_shm_t *s = J2P(shm, apr_shm_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jint)apr_shm_detach(s);
+}
+
+TCN_IMPLEMENT_CALL(jlong, Shm, baseaddr)(TCN_STDARGS, jlong shm)
+{
+    apr_shm_t *s = J2P(shm, apr_shm_t *);
+
+    UNREFERENCED_STDARGS;
+    return P2J(apr_shm_baseaddr_get(s));
+}
+
+TCN_IMPLEMENT_CALL(jlong, Shm, size)(TCN_STDARGS, jlong shm)
+{
+    apr_shm_t *s = J2P(shm, apr_shm_t *);
+
+    UNREFERENCED_STDARGS;
+    return (jlong)apr_shm_size_get(s);
+}
+
+TCN_IMPLEMENT_CALL(jobject, Shm, buffer)(TCN_STDARGS, jlong shm)
+{
+    apr_shm_t *s = J2P(shm, apr_shm_t *);
+    jlong sz = (jlong)apr_shm_size_get(s);
+    void *a;
+
+    UNREFERENCED(o);
+
+    if ((a = apr_shm_baseaddr_get(s)) != NULL)
+        return (*e)->NewDirectByteBuffer(e, a, sz);
+    else
+        return NULL;
+}
diff --git a/c/ssl.c b/c/ssl.c
new file mode 100644
index 0000000..89e6cad
--- /dev/null
+++ b/c/ssl.c
@@ -0,0 +1,2457 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: ssl.c 1649733 2015-01-06 04:42:24Z billbarker $
+ */
+
+#include "tcn.h"
+#include "apr_file_io.h"
+#include "apr_thread_mutex.h"
+#include "apr_atomic.h"
+#include "apr_poll.h"
+#ifdef HAVE_OPENSSL
+#include "ssl_private.h"
+
+static int ssl_initialized = 0;
+static char *ssl_global_rand_file = NULL;
+extern apr_pool_t *tcn_global_pool;
+
+ENGINE *tcn_ssl_engine = NULL;
+void *SSL_temp_keys[SSL_TMP_KEY_MAX];
+tcn_pass_cb_t tcn_password_callback;
+
+/* Global reference to the pool used by the dynamic mutexes */
+static apr_pool_t *dynlockpool = NULL;
+
+static jclass byteArrayClass;
+static jclass stringClass;
+
+/* Dynamic lock structure */
+struct CRYPTO_dynlock_value {
+    apr_pool_t *pool;
+    const char* file;
+    int line;
+    apr_thread_mutex_t *mutex;
+};
+
+
+/*
+ * Handle the Temporary RSA Keys and DH Params
+ */
+
+#define SSL_TMP_KEY_FREE(type, idx)                     \
+    if (SSL_temp_keys[idx]) {                           \
+        type##_free((type *)SSL_temp_keys[idx]);        \
+        SSL_temp_keys[idx] = NULL;                      \
+    } else (void)(0)
+
+#define SSL_TMP_KEYS_FREE(type) \
+    SSL_TMP_KEY_FREE(type, SSL_TMP_KEY_##type##_512);   \
+    SSL_TMP_KEY_FREE(type, SSL_TMP_KEY_##type##_1024);  \
+    SSL_TMP_KEY_FREE(type, SSL_TMP_KEY_##type##_2048);  \
+    SSL_TMP_KEY_FREE(type, SSL_TMP_KEY_##type##_4096)
+
+#define SSL_TMP_KEY_INIT_RSA(bits) \
+    ssl_tmp_key_init_rsa(bits, SSL_TMP_KEY_RSA_##bits)
+
+#define SSL_TMP_KEY_INIT_DH(bits)  \
+    ssl_tmp_key_init_dh(bits, SSL_TMP_KEY_DH_##bits)
+
+#define SSL_TMP_KEYS_INIT(R)                    \
+    SSL_temp_keys[SSL_TMP_KEY_RSA_2048] = NULL; \
+    SSL_temp_keys[SSL_TMP_KEY_RSA_4096] = NULL; \
+    R |= SSL_TMP_KEY_INIT_RSA(512);             \
+    R |= SSL_TMP_KEY_INIT_RSA(1024);            \
+    R |= SSL_TMP_KEY_INIT_DH(512);              \
+    R |= SSL_TMP_KEY_INIT_DH(1024);             \
+    R |= SSL_TMP_KEY_INIT_DH(2048);             \
+    R |= SSL_TMP_KEY_INIT_DH(4096)
+
+/*
+ * supported_ssl_opts is a bitmask that contains all supported SSL_OP_*
+ * options at compile-time. This is used in hasOp to determine which
+ * SSL_OP_* options are available at runtime.
+ *
+ * Note that at least up through OpenSSL 0.9.8o, checking SSL_OP_ALL will
+ * return JNI_FALSE because SSL_OP_ALL is a mask that covers all bug
+ * workarounds for OpenSSL including future workarounds that are defined
+ * to be in the least-significant 3 nibbles of the SSL_OP_* bit space.
+ *
+ * This implementation has chosen NOT to simply set all those lower bits
+ * so that the return value for SSL_OP_FUTURE_WORKAROUND will only be
+ * reported by versions that actually support that specific workaround.
+ */
+static const jint supported_ssl_opts = 0
+/*
+  Specifically skip SSL_OP_ALL
+#ifdef SSL_OP_ALL
+     | SSL_OP_ALL
+#endif
+*/
+#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
+     | SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
+#endif
+
+#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
+     | SSL_OP_CIPHER_SERVER_PREFERENCE
+#endif
+
+#ifdef SSL_OP_CRYPTOPRO_TLSEXT_BUG
+     | SSL_OP_CRYPTOPRO_TLSEXT_BUG
+#endif
+
+#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+     | SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+#endif
+
+#ifdef SSL_OP_EPHEMERAL_RSA
+     | SSL_OP_EPHEMERAL_RSA
+#endif
+
+#ifdef SSL_OP_LEGACY_SERVER_CONNECT
+     | SSL_OP_LEGACY_SERVER_CONNECT
+#endif
+
+#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
+     | SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
+#endif
+
+#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
+     | SSL_OP_MICROSOFT_SESS_ID_BUG
+#endif
+
+#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
+     | SSL_OP_MSIE_SSLV2_RSA_PADDING
+#endif
+
+#ifdef SSL_OP_NETSCAPE_CA_DN_BUG
+     | SSL_OP_NETSCAPE_CA_DN_BUG
+#endif
+
+#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
+     | SSL_OP_NETSCAPE_CHALLENGE_BUG
+#endif
+
+#ifdef SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG
+     | SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG
+#endif
+
+#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
+     | SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
+#endif
+
+#ifdef SSL_OP_NO_COMPRESSION
+     | SSL_OP_NO_COMPRESSION
+#endif
+
+#ifdef SSL_OP_NO_QUERY_MTU
+     | SSL_OP_NO_QUERY_MTU
+#endif
+
+#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
+     | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
+#endif
+
+#ifdef SSL_OP_NO_SSLv2
+     | SSL_OP_NO_SSLv2
+#endif
+
+#ifdef SSL_OP_NO_SSLv3
+     | SSL_OP_NO_SSLv3
+#endif
+
+#ifdef SSL_OP_NO_TICKET
+     | SSL_OP_NO_TICKET
+#endif
+
+#ifdef SSL_OP_NO_TLSv1
+     | SSL_OP_NO_TLSv1
+#endif
+
+#ifdef SSL_OP_PKCS1_CHECK_1
+     | SSL_OP_PKCS1_CHECK_1
+#endif
+
+#ifdef SSL_OP_PKCS1_CHECK_2
+     | SSL_OP_PKCS1_CHECK_2
+#endif
+
+#ifdef SSL_OP_NO_TLSv1_1
+     | SSL_OP_NO_TLSv1_1
+#endif
+
+#ifdef SSL_OP_NO_TLSv1_2
+     | SSL_OP_NO_TLSv1_2
+#endif
+
+#ifdef SSL_OP_SINGLE_DH_USE
+     | SSL_OP_SINGLE_DH_USE
+#endif
+
+#ifdef SSL_OP_SINGLE_ECDH_USE
+     | SSL_OP_SINGLE_ECDH_USE
+#endif
+
+#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
+     | SSL_OP_SSLEAY_080_CLIENT_DH_BUG
+#endif
+
+#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
+     | SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
+#endif
+
+#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
+     | SSL_OP_TLS_BLOCK_PADDING_BUG
+#endif
+
+#ifdef SSL_OP_TLS_D5_BUG
+     | SSL_OP_TLS_D5_BUG
+#endif
+
+#ifdef SSL_OP_TLS_ROLLBACK_BUG
+     | SSL_OP_TLS_ROLLBACK_BUG
+#endif
+     | 0;
+
+static int ssl_tmp_key_init_rsa(int bits, int idx)
+{
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(OPENSSL_USE_DEPRECATED)
+    if (!(SSL_temp_keys[idx] =
+          RSA_generate_key(bits, RSA_F4, NULL, NULL))) {
+#ifdef OPENSSL_FIPS
+        /**
+         * With FIPS mode short RSA keys cannot be
+         * generated.
+         */
+        if (bits < 1024)
+            return 0;
+        else
+#endif
+        return 1;
+    }
+    else {
+        return 0;
+    }
+#else
+    return 0;
+#endif
+}
+
+static int ssl_tmp_key_init_dh(int bits, int idx)
+{
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(OPENSSL_USE_DEPRECATED)
+    if (!(SSL_temp_keys[idx] =
+          SSL_dh_get_tmp_param(bits)))
+        return 1;
+    else
+        return 0;
+#else
+    return 0;
+#endif
+}
+
+
+TCN_IMPLEMENT_CALL(jint, SSL, version)(TCN_STDARGS)
+{
+    UNREFERENCED_STDARGS;
+    return (jint) SSLeay();
+}
+
+TCN_IMPLEMENT_CALL(jstring, SSL, versionString)(TCN_STDARGS)
+{
+    UNREFERENCED(o);
+    return AJP_TO_JSTRING(SSLeay_version(SSLEAY_VERSION));
+}
+
+/*
+ *  the various processing hooks
+ */
+static apr_status_t ssl_init_cleanup(void *data)
+{
+    UNREFERENCED(data);
+
+    if (!ssl_initialized)
+        return APR_SUCCESS;
+    ssl_initialized = 0;
+
+    if (tcn_password_callback.cb.obj) {
+        JNIEnv *env;
+        tcn_get_java_env(&env);
+        TCN_UNLOAD_CLASS(env,
+                         tcn_password_callback.cb.obj);
+    }
+
+    SSL_TMP_KEYS_FREE(RSA);
+    SSL_TMP_KEYS_FREE(DH);
+    /*
+     * Try to kill the internals of the SSL library.
+     */
+#if OPENSSL_VERSION_NUMBER >= 0x00907001 && !defined(OPENSSL_IS_BORINGSSL)
+    /* Corresponds to OPENSSL_load_builtin_modules():
+     * XXX: borrowed from apps.h, but why not CONF_modules_free()
+     * which also invokes CONF_modules_finish()?
+     */
+    CONF_modules_unload(1);
+#endif
+    /* Corresponds to SSL_library_init: */
+    EVP_cleanup();
+#if HAVE_ENGINE_LOAD_BUILTIN_ENGINES
+    ENGINE_cleanup();
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x00907001
+    CRYPTO_cleanup_all_ex_data();
+#endif
+    ERR_remove_state(0);
+
+    /* Don't call ERR_free_strings here; ERR_load_*_strings only
+     * actually load the error strings once per process due to static
+     * variable abuse in OpenSSL. */
+
+    /*
+     * TODO: determine somewhere we can safely shove out diagnostics
+     *       (when enabled) at this late stage in the game:
+     * CRYPTO_mem_leaks_fp(stderr);
+     */
+    return APR_SUCCESS;
+}
+
+#ifndef OPENSSL_NO_ENGINE
+/* Try to load an engine in a shareable library */
+static ENGINE *ssl_try_load_engine(const char *engine)
+{
+    ENGINE *e = ENGINE_by_id("dynamic");
+    if (e) {
+        if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", engine, 0)
+            || !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) {
+            ENGINE_free(e);
+            e = NULL;
+        }
+    }
+    return e;
+}
+#endif
+
+/*
+ * To ensure thread-safetyness in OpenSSL
+ */
+
+static apr_thread_mutex_t **ssl_lock_cs;
+static int                  ssl_lock_num_locks;
+
+static void ssl_thread_lock(int mode, int type,
+                            const char *file, int line)
+{
+    UNREFERENCED(file);
+    UNREFERENCED(line);
+    if (type < ssl_lock_num_locks) {
+        if (mode & CRYPTO_LOCK) {
+            apr_thread_mutex_lock(ssl_lock_cs[type]);
+        }
+        else {
+            apr_thread_mutex_unlock(ssl_lock_cs[type]);
+        }
+    }
+}
+
+static unsigned long ssl_thread_id(void)
+{
+    /* OpenSSL needs this to return an unsigned long.  On OS/390, the pthread
+     * id is a structure twice that big.  Use the TCB pointer instead as a
+     * unique unsigned long.
+     */
+#ifdef __MVS__
+    struct PSA {
+        char unmapped[540];
+        unsigned long PSATOLD;
+    } *psaptr = 0;
+
+    return psaptr->PSATOLD;
+#elif defined(WIN32)
+    return (unsigned long)GetCurrentThreadId();
+#else
+    return (unsigned long)(apr_os_thread_current());
+#endif
+}
+
+static apr_status_t ssl_thread_cleanup(void *data)
+{
+    UNREFERENCED(data);
+    CRYPTO_set_locking_callback(NULL);
+    CRYPTO_set_id_callback(NULL);
+    CRYPTO_set_dynlock_create_callback(NULL);
+    CRYPTO_set_dynlock_lock_callback(NULL);
+    CRYPTO_set_dynlock_destroy_callback(NULL);
+
+    dynlockpool = NULL;
+
+    /* Let the registered mutex cleanups do their own thing
+     */
+    return APR_SUCCESS;
+}
+
+/*
+ * Dynamic lock creation callback
+ */
+static struct CRYPTO_dynlock_value *ssl_dyn_create_function(const char *file,
+                                                     int line)
+{
+    struct CRYPTO_dynlock_value *value;
+    apr_pool_t *p;
+    apr_status_t rv;
+
+    /*
+     * We need a pool to allocate our mutex.  Since we can't clear
+     * allocated memory from a pool, create a subpool that we can blow
+     * away in the destruction callback.
+     */
+    rv = apr_pool_create(&p, dynlockpool);
+    if (rv != APR_SUCCESS) {
+        /* TODO log that fprintf(stderr, "Failed to create subpool for dynamic lock"); */
+        return NULL;
+    }
+
+    value = (struct CRYPTO_dynlock_value *)apr_palloc(p,
+                                                      sizeof(struct CRYPTO_dynlock_value));
+    if (!value) {
+        /* TODO log that fprintf(stderr, "Failed to allocate dynamic lock structure"); */
+        return NULL;
+    }
+
+    value->pool = p;
+    /* Keep our own copy of the place from which we were created,
+       using our own pool. */
+    value->file = apr_pstrdup(p, file);
+    value->line = line;
+    rv = apr_thread_mutex_create(&(value->mutex), APR_THREAD_MUTEX_DEFAULT,
+                                p);
+    if (rv != APR_SUCCESS) {
+        /* TODO log that fprintf(stderr, "Failed to create thread mutex for dynamic lock"); */
+        apr_pool_destroy(p);
+        return NULL;
+    }
+    return value;
+}
+
+/*
+ * Dynamic locking and unlocking function
+ */
+static void ssl_dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l,
+                           const char *file, int line)
+{
+
+
+    if (mode & CRYPTO_LOCK) {
+        apr_thread_mutex_lock(l->mutex);
+    }
+    else {
+        apr_thread_mutex_unlock(l->mutex);
+    }
+}
+
+/*
+ * Dynamic lock destruction callback
+ */
+static void ssl_dyn_destroy_function(struct CRYPTO_dynlock_value *l,
+                          const char *file, int line)
+{
+    apr_status_t rv;
+    rv = apr_thread_mutex_destroy(l->mutex);
+    if (rv != APR_SUCCESS) {
+        /* TODO log that fprintf(stderr, "Failed to destroy mutex for dynamic lock %s:%d", l->file, l->line); */
+    }
+
+    /* Trust that whomever owned the CRYPTO_dynlock_value we were
+     * passed has no future use for it...
+     */
+    apr_pool_destroy(l->pool);
+}
+static void ssl_thread_setup(apr_pool_t *p)
+{
+    int i;
+
+    ssl_lock_num_locks = CRYPTO_num_locks();
+    ssl_lock_cs = apr_palloc(p, ssl_lock_num_locks * sizeof(*ssl_lock_cs));
+
+    for (i = 0; i < ssl_lock_num_locks; i++) {
+        apr_thread_mutex_create(&(ssl_lock_cs[i]),
+                                APR_THREAD_MUTEX_DEFAULT, p);
+    }
+
+    CRYPTO_set_id_callback(ssl_thread_id);
+    CRYPTO_set_locking_callback(ssl_thread_lock);
+
+    /* Set up dynamic locking scaffolding for OpenSSL to use at its
+     * convenience.
+     */
+    dynlockpool = p;
+    CRYPTO_set_dynlock_create_callback(ssl_dyn_create_function);
+    CRYPTO_set_dynlock_lock_callback(ssl_dyn_lock_function);
+    CRYPTO_set_dynlock_destroy_callback(ssl_dyn_destroy_function);
+
+    apr_pool_cleanup_register(p, NULL, ssl_thread_cleanup,
+                              apr_pool_cleanup_null);
+}
+
+static int ssl_rand_choosenum(int l, int h)
+{
+    int i;
+    char buf[50];
+
+    apr_snprintf(buf, sizeof(buf), "%.0f",
+                 (((double)(rand()%RAND_MAX)/RAND_MAX)*(h-l)));
+    i = atoi(buf)+1;
+    if (i < l) i = l;
+    if (i > h) i = h;
+    return i;
+}
+
+static int ssl_rand_load_file(const char *file)
+{
+    int n;
+
+    if (file == NULL)
+        file = ssl_global_rand_file;
+    if (file && (strcmp(file, "builtin") == 0))
+        return -1;
+// BoringSsl doesn't support RAND_file_name, but RAND_status() returns 1 anyways
+#ifndef OPENSSL_IS_BORINGSSL
+    if (file == NULL) {
+        char buffer[APR_PATH_MAX];
+        file = RAND_file_name(buffer, sizeof(buffer));
+    }
+#endif
+    if (file) {
+#ifdef HAVE_SSL_RAND_EGD
+        if (strncmp(file, "egd:", 4) == 0) {
+            if ((n = RAND_egd(file + 4)) > 0)
+                return n;
+            else
+                return -1;
+        }
+#endif
+        if ((n = RAND_load_file(file, -1)) > 0)
+            return n;
+    }
+    return -1;
+}
+
+/*
+ * writes a number of random bytes (currently 1024) to
+ * file which can be used to initialize the PRNG by calling
+ * RAND_load_file() in a later session
+ */
+static int ssl_rand_save_file(const char *file)
+{
+#ifndef OPENSSL_IS_BORINGSSL
+    char buffer[APR_PATH_MAX];
+    int n;
+    if (file == NULL) {
+        file = RAND_file_name(buffer, sizeof(buffer));
+#ifdef HAVE_SSL_RAND_EGD
+    } else if ((n = RAND_egd(file)) > 0) {
+        return 0;
+#endif
+    }
+    if (file == NULL || !RAND_write_file(file))
+        return 0;
+    else
+        return 1;
+#else
+    // BoringSsl doesn't have RAND_file_name/RAND_write_file and RAND_egd always return 255
+    return 0;
+#endif
+}
+
+int SSL_rand_seed(const char *file)
+{
+    unsigned char stackdata[256];
+    static volatile apr_uint32_t counter = 0;
+
+    if (ssl_rand_load_file(file) < 0) {
+        int n;
+        struct {
+            apr_time_t    t;
+            pid_t         p;
+            unsigned long i;
+            apr_uint32_t  u;
+        } _ssl_seed;
+        if (counter == 0) {
+            apr_generate_random_bytes(stackdata, 256);
+            RAND_seed(stackdata, 128);
+        }
+        _ssl_seed.t = apr_time_now();
+        _ssl_seed.p = getpid();
+        _ssl_seed.i = ssl_thread_id();
+        apr_atomic_inc32(&counter);
+        _ssl_seed.u = counter;
+        RAND_seed((unsigned char *)&_ssl_seed, sizeof(_ssl_seed));
+        /*
+         * seed in some current state of the run-time stack (128 bytes)
+         */
+        n = ssl_rand_choosenum(0, sizeof(stackdata)-128-1);
+        RAND_seed(stackdata + n, 128);
+    }
+    return RAND_status();
+}
+
+static int ssl_rand_make(const char *file, int len, int base64)
+{
+    int r;
+    int num = len;
+    BIO *out = NULL;
+
+    out = BIO_new(BIO_s_file());
+    if (out == NULL)
+        return 0;
+    if ((r = BIO_write_filename(out, (char *)file)) < 0) {
+        BIO_free_all(out);
+        return 0;
+    }
+    if (base64) {
+        BIO *b64 = BIO_new(BIO_f_base64());
+        if (b64 == NULL) {
+            BIO_free_all(out);
+            return 0;
+        }
+        out = BIO_push(b64, out);
+    }
+    while (num > 0) {
+        unsigned char buf[4096];
+        int len = num;
+        if (len > sizeof(buf))
+            len = sizeof(buf);
+        r = RAND_bytes(buf, len);
+        if (r <= 0) {
+            BIO_free_all(out);
+            return 0;
+        }
+        BIO_write(out, buf, len);
+        num -= len;
+    }
+    r = BIO_flush(out);
+    BIO_free_all(out);
+    return r > 0 ? 1 : 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, initialize)(TCN_STDARGS, jstring engine)
+{
+    int r = 0;
+    jclass clazz;
+    jclass sClazz;
+
+    TCN_ALLOC_CSTRING(engine);
+
+    UNREFERENCED(o);
+    if (!tcn_global_pool) {
+        TCN_FREE_CSTRING(engine);
+        tcn_ThrowAPRException(e, APR_EINVAL);
+        return (jint)APR_EINVAL;
+    }
+    /* Check if already initialized */
+    if (ssl_initialized++) {
+        TCN_FREE_CSTRING(engine);
+        return (jint)APR_SUCCESS;
+    }
+
+    if (SSLeay() < 0x0090700L) {
+        TCN_FREE_CSTRING(engine);
+        tcn_ThrowAPRException(e, APR_EINVAL);
+        ssl_initialized = 0;
+        return (jint)APR_EINVAL;
+    }
+
+#ifndef OPENSSL_IS_BORINGSSL
+    /* We must register the library in full, to ensure our configuration
+     * code can successfully test the SSL environment.
+     */
+    CRYPTO_malloc_init();
+#endif
+
+    ERR_load_crypto_strings();
+    SSL_load_error_strings();
+    SSL_library_init();
+    OpenSSL_add_all_algorithms();
+#if HAVE_ENGINE_LOAD_BUILTIN_ENGINES
+    ENGINE_load_builtin_engines();
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x00907001 && !defined(OPENSSL_IS_BORINGSSL)
+    OPENSSL_load_builtin_modules();
+#endif
+
+    /* Initialize thread support */
+    ssl_thread_setup(tcn_global_pool);
+
+#ifndef OPENSSL_NO_ENGINE
+    if (J2S(engine)) {
+        ENGINE *ee = NULL;
+        apr_status_t err = APR_SUCCESS;
+        if(strcmp(J2S(engine), "auto") == 0) {
+            ENGINE_register_all_complete();
+        }
+        else {
+            if ((ee = ENGINE_by_id(J2S(engine))) == NULL
+                && (ee = ssl_try_load_engine(J2S(engine))) == NULL)
+                err = APR_ENOTIMPL;
+            else {
+#ifdef ENGINE_CTRL_CHIL_SET_FORKCHECK
+                if (strcmp(J2S(engine), "chil") == 0)
+                    ENGINE_ctrl(ee, ENGINE_CTRL_CHIL_SET_FORKCHECK, 1, 0, 0);
+#endif
+                if (!ENGINE_set_default(ee, ENGINE_METHOD_ALL))
+                    err = APR_ENOTIMPL;
+            }
+            /* Free our "structural" reference. */
+            if (ee)
+                ENGINE_free(ee);
+        }
+        if (err != APR_SUCCESS) {
+            TCN_FREE_CSTRING(engine);
+            ssl_init_cleanup(NULL);
+            tcn_ThrowAPRException(e, err);
+            return (jint)err;
+        }
+        tcn_ssl_engine = ee;
+    }
+#endif
+
+    memset(&tcn_password_callback, 0, sizeof(tcn_pass_cb_t));
+    /* Initialize PRNG
+     * This will in most cases call the builtin
+     * low entropy seed.
+     */
+    SSL_rand_seed(NULL);
+    /* For SSL_get_app_data2() and SSL_get_app_data3() at request time */
+    SSL_init_app_data2_3_idx();
+
+    SSL_TMP_KEYS_INIT(r);
+    if (r) {
+        TCN_FREE_CSTRING(engine);
+        ssl_init_cleanup(NULL);
+        tcn_ThrowAPRException(e, APR_ENOTIMPL);
+        return APR_ENOTIMPL;
+    }
+    /*
+     * Let us cleanup the ssl library when the library is unloaded
+     */
+    apr_pool_cleanup_register(tcn_global_pool, NULL,
+                              ssl_init_cleanup,
+                              apr_pool_cleanup_null);
+    TCN_FREE_CSTRING(engine);
+
+    // Cache the byte[].class for performance reasons
+    clazz = (*e)->FindClass(e, "[B");
+    byteArrayClass = (jclass) (*e)->NewGlobalRef(e, clazz);
+
+    // Cache the String.class for performance reasons
+    sClazz = (*e)->FindClass(e, "java/lang/String");
+    stringClass = (jclass) (*e)->NewGlobalRef(e, sClazz);
+
+    return (jint)APR_SUCCESS;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, randLoad)(TCN_STDARGS, jstring file)
+{
+    TCN_ALLOC_CSTRING(file);
+    int r;
+    UNREFERENCED(o);
+    r = SSL_rand_seed(J2S(file));
+    TCN_FREE_CSTRING(file);
+    return r ? JNI_TRUE : JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, randSave)(TCN_STDARGS, jstring file)
+{
+    TCN_ALLOC_CSTRING(file);
+    int r;
+    UNREFERENCED(o);
+    r = ssl_rand_save_file(J2S(file));
+    TCN_FREE_CSTRING(file);
+    return r ? JNI_TRUE : JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, randMake)(TCN_STDARGS, jstring file,
+                                            jint length, jboolean base64)
+{
+    TCN_ALLOC_CSTRING(file);
+    int r;
+    UNREFERENCED(o);
+    r = ssl_rand_make(J2S(file), length, base64);
+    TCN_FREE_CSTRING(file);
+    return r ? JNI_TRUE : JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, randSet)(TCN_STDARGS, jstring file)
+{
+    TCN_ALLOC_CSTRING(file);
+    UNREFERENCED(o);
+    if (J2S(file)) {
+        ssl_global_rand_file = apr_pstrdup(tcn_global_pool, J2S(file));
+    }
+    TCN_FREE_CSTRING(file);
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, fipsModeGet)(TCN_STDARGS)
+{
+    UNREFERENCED(o);
+#ifdef OPENSSL_FIPS
+    return FIPS_mode();
+#else
+    /* FIPS is unavailable */
+    tcn_ThrowException(e, "FIPS was not available to tcnative at build time. You will need to re-build tcnative against an OpenSSL with FIPS.");
+
+    return 0;
+#endif
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, fipsModeSet)(TCN_STDARGS, jint mode)
+{
+    int r = 0;
+    UNREFERENCED(o);
+
+#ifdef OPENSSL_FIPS
+    if(1 != (r = (jint)FIPS_mode_set((int)mode))) {
+      /* arrange to get a human-readable error message */
+      unsigned long err = ERR_get_error();
+      char msg[256];
+
+      /* ERR_load_crypto_strings() already called in initialize() */
+
+      ERR_error_string_n(err, msg, 256);
+
+      tcn_ThrowException(e, msg);
+    }
+#else
+    /* FIPS is unavailable */
+    tcn_ThrowException(e, "FIPS was not available to tcnative at build time. You will need to re-build tcnative against an OpenSSL with FIPS.");
+#endif
+
+    return r;
+}
+
+/* OpenSSL Java Stream BIO */
+
+typedef struct  {
+    int            refcount;
+    apr_pool_t     *pool;
+    tcn_callback_t cb;
+} BIO_JAVA;
+
+
+static apr_status_t generic_bio_cleanup(void *data)
+{
+    BIO *b = (BIO *)data;
+
+    if (b) {
+        BIO_free(b);
+    }
+    return APR_SUCCESS;
+}
+
+void SSL_BIO_close(BIO *bi)
+{
+    if (bi == NULL)
+        return;
+    if (bi->ptr != NULL && (bi->flags & SSL_BIO_FLAG_CALLBACK)) {
+        BIO_JAVA *j = (BIO_JAVA *)bi->ptr;
+        j->refcount--;
+        if (j->refcount == 0) {
+            if (j->pool)
+                apr_pool_cleanup_run(j->pool, bi, generic_bio_cleanup);
+            else
+                BIO_free(bi);
+        }
+    }
+    else
+        BIO_free(bi);
+}
+
+void SSL_BIO_doref(BIO *bi)
+{
+    if (bi == NULL)
+        return;
+    if (bi->ptr != NULL && (bi->flags & SSL_BIO_FLAG_CALLBACK)) {
+        BIO_JAVA *j = (BIO_JAVA *)bi->ptr;
+        j->refcount++;
+    }
+}
+
+
+static int jbs_new(BIO *bi)
+{
+    BIO_JAVA *j;
+
+    if ((j = OPENSSL_malloc(sizeof(BIO_JAVA))) == NULL)
+        return 0;
+    j->pool      = NULL;
+    j->refcount  = 1;
+    bi->shutdown = 1;
+    bi->init     = 0;
+    bi->num      = -1;
+    bi->ptr      = (char *)j;
+
+    return 1;
+}
+
+static int jbs_free(BIO *bi)
+{
+    JNIEnv *e = NULL;
+    BIO_JAVA *j;
+
+    if (bi == NULL)
+        return 0;
+    if (bi->ptr != NULL) {
+        j = (BIO_JAVA *)bi->ptr;
+        if (bi->init) {
+            bi->init = 0;
+            tcn_get_java_env(&e);
+            TCN_UNLOAD_CLASS(e, j->cb.obj);
+        }
+        OPENSSL_free(bi->ptr);
+    }
+    bi->ptr = NULL;
+    return 1;
+}
+
+static int jbs_write(BIO *b, const char *in, int inl)
+{
+    jint ret = -1;
+
+    if (b->init && in != NULL) {
+        BIO_JAVA *j = (BIO_JAVA *)b->ptr;
+        JNIEnv   *e = NULL;
+        jbyteArray jb;
+        tcn_get_java_env(&e);
+        jb = (*e)->NewByteArray(e, inl);
+        if (!(*e)->ExceptionOccurred(e)) {
+            BIO_clear_retry_flags(b);
+            (*e)->SetByteArrayRegion(e, jb, 0, inl, (jbyte *)in);
+            ret = (*e)->CallIntMethod(e, j->cb.obj,
+                                      j->cb.mid[0], jb);
+            (*e)->DeleteLocalRef(e, jb);
+        }
+    }
+    if (ret == 0) {
+        BIO_set_retry_write(b);
+        ret = -1;
+    }
+    return ret;
+}
+
+static int jbs_read(BIO *b, char *out, int outl)
+{
+    jint ret = 0;
+    jbyte *jout;
+
+    if (b->init && out != NULL) {
+        BIO_JAVA *j = (BIO_JAVA *)b->ptr;
+        JNIEnv   *e = NULL;
+        jbyteArray jb;
+        tcn_get_java_env(&e);
+        jb = (*e)->NewByteArray(e, outl);
+        if (!(*e)->ExceptionOccurred(e)) {
+            BIO_clear_retry_flags(b);
+            ret = (*e)->CallIntMethod(e, j->cb.obj,
+                                      j->cb.mid[1], jb);
+            if (ret > 0) {
+                jout = (*e)->GetPrimitiveArrayCritical(e, jb, NULL);
+                memcpy(out, jout, ret);
+                (*e)->ReleasePrimitiveArrayCritical(e, jb, jout, 0);
+            } else if (outl != 0) {
+                ret = -1;
+                BIO_set_retry_read(b);
+            }
+            (*e)->DeleteLocalRef(e, jb);
+        }
+    }
+    return ret;
+}
+
+static int jbs_puts(BIO *b, const char *in)
+{
+    int ret = 0;
+    JNIEnv *e = NULL;
+    BIO_JAVA *j;
+
+    if (b->init && in != NULL) {
+        j = (BIO_JAVA *)b->ptr;
+        tcn_get_java_env(&e);
+        ret = (*e)->CallIntMethod(e, j->cb.obj,
+                                  j->cb.mid[2],
+                                  tcn_new_string(e, in));
+    }
+    return ret;
+}
+
+static int jbs_gets(BIO *b, char *out, int outl)
+{
+    int ret = 0;
+    JNIEnv *e = NULL;
+    BIO_JAVA *j;
+    jobject o;
+    int l;
+
+    if (b->init && out != NULL) {
+        j = (BIO_JAVA *)b->ptr;
+        tcn_get_java_env(&e);
+        if ((o = (*e)->CallObjectMethod(e, j->cb.obj,
+                            j->cb.mid[3], (jint)(outl - 1)))) {
+            TCN_ALLOC_CSTRING(o);
+            if (J2S(o)) {
+                l = (int)strlen(J2S(o));
+                if (l < outl) {
+                    strcpy(out, J2S(o));
+                    ret = outl;
+                }
+            }
+            TCN_FREE_CSTRING(o);
+        }
+    }
+    return ret;
+}
+
+static long jbs_ctrl(BIO *b, int cmd, long num, void *ptr)
+{
+    int ret = 0;
+    switch (cmd) {
+        case BIO_CTRL_FLUSH:
+            ret = 1;
+            break;
+        default:
+            ret = 0;
+            break;
+    }
+    return ret;
+}
+
+static BIO_METHOD jbs_methods = {
+    BIO_TYPE_FILE,
+    "Java Callback",
+    jbs_write,
+    jbs_read,
+    jbs_puts,
+    jbs_gets,
+    jbs_ctrl,
+    jbs_new,
+    jbs_free,
+    NULL
+};
+
+static BIO_METHOD *BIO_jbs()
+{
+    return(&jbs_methods);
+}
+
+
+TCN_IMPLEMENT_CALL(jlong, SSL, newMemBIO)(TCN_STDARGS)
+{
+    BIO *bio = NULL;
+
+    UNREFERENCED(o);
+
+    // TODO: Use BIO_s_secmem() once included in stable release
+    if ((bio = BIO_new(BIO_s_mem())) == NULL) {
+        tcn_ThrowException(e, "Create BIO failed");
+        return (jlong) NULL;
+    }
+    return P2J(bio);
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSL, newBIO)(TCN_STDARGS, jlong pool,
+                                       jobject callback)
+{
+    BIO *bio = NULL;
+    BIO_JAVA *j;
+    jclass cls;
+
+    UNREFERENCED(o);
+
+    if ((bio = BIO_new(BIO_jbs())) == NULL) {
+        tcn_ThrowException(e, "Create BIO failed");
+        goto init_failed;
+    }
+    j = (BIO_JAVA *)bio->ptr;
+    if (j == NULL) {
+        tcn_ThrowException(e, "Create BIO failed");
+        goto init_failed;
+    }
+    j->pool = J2P(pool, apr_pool_t *);
+    if (j->pool) {
+        apr_pool_cleanup_register(j->pool, (const void *)bio,
+                                  generic_bio_cleanup,
+                                  apr_pool_cleanup_null);
+    }
+
+    cls = (*e)->GetObjectClass(e, callback);
+    j->cb.mid[0] = (*e)->GetMethodID(e, cls, "write", "([B)I");
+    j->cb.mid[1] = (*e)->GetMethodID(e, cls, "read",  "([B)I");
+    j->cb.mid[2] = (*e)->GetMethodID(e, cls, "puts",  "(Ljava/lang/String;)I");
+    j->cb.mid[3] = (*e)->GetMethodID(e, cls, "gets",  "(I)Ljava/lang/String;");
+    /* TODO: Check if method id's are valid */
+    j->cb.obj    = (*e)->NewGlobalRef(e, callback);
+
+    bio->init  = 1;
+    bio->flags = SSL_BIO_FLAG_CALLBACK;
+    return P2J(bio);
+init_failed:
+    return 0;
+}
+
+
+TCN_IMPLEMENT_CALL(jint, SSL, closeBIO)(TCN_STDARGS, jlong bio)
+{
+    BIO *b = J2P(bio, BIO *);
+
+    UNREFERENCED_STDARGS;
+
+    if (b != NULL) {
+        SSL_BIO_close(b);
+    }
+
+    return APR_SUCCESS;
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setPasswordCallback)(TCN_STDARGS,
+                                                   jobject callback)
+{
+    jclass cls;
+
+    UNREFERENCED(o);
+    if (tcn_password_callback.cb.obj) {
+        TCN_UNLOAD_CLASS(e,
+                         tcn_password_callback.cb.obj);
+    }
+    cls = (*e)->GetObjectClass(e, callback);
+    tcn_password_callback.cb.mid[0] = (*e)->GetMethodID(e, cls, "callback",
+                           "(Ljava/lang/String;)Ljava/lang/String;");
+    /* TODO: Check if method id is valid */
+    tcn_password_callback.cb.obj    = (*e)->NewGlobalRef(e, callback);
+
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setPassword)(TCN_STDARGS, jstring password)
+{
+    TCN_ALLOC_CSTRING(password);
+    UNREFERENCED(o);
+    if (J2S(password)) {
+        strncpy(tcn_password_callback.password, J2S(password), SSL_MAX_PASSWORD_LEN);
+        tcn_password_callback.password[SSL_MAX_PASSWORD_LEN-1] = '\0';
+    }
+    TCN_FREE_CSTRING(password);
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, generateRSATempKey)(TCN_STDARGS, jint idx)
+{
+    int r = 1;
+    UNREFERENCED_STDARGS;
+    SSL_TMP_KEY_FREE(RSA, idx);
+    switch (idx) {
+        case SSL_TMP_KEY_RSA_512:
+            r = SSL_TMP_KEY_INIT_RSA(512);
+        break;
+        case SSL_TMP_KEY_RSA_1024:
+            r = SSL_TMP_KEY_INIT_RSA(1024);
+        break;
+        case SSL_TMP_KEY_RSA_2048:
+            r = SSL_TMP_KEY_INIT_RSA(2048);
+        break;
+        case SSL_TMP_KEY_RSA_4096:
+            r = SSL_TMP_KEY_INIT_RSA(4096);
+        break;
+    }
+    return r ? JNI_FALSE : JNI_TRUE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, loadDSATempKey)(TCN_STDARGS, jint idx,
+                                                  jstring file)
+{
+    jboolean r = JNI_FALSE;
+    TCN_ALLOC_CSTRING(file);
+    DH *dh;
+    UNREFERENCED(o);
+
+    if (!J2S(file))
+        return JNI_FALSE;
+    SSL_TMP_KEY_FREE(DSA, idx);
+    if ((dh = SSL_dh_get_param_from_file(J2S(file)))) {
+        SSL_temp_keys[idx] = dh;
+        r = JNI_TRUE;
+    }
+    TCN_FREE_CSTRING(file);
+    return r;
+}
+
+TCN_IMPLEMENT_CALL(jstring, SSL, getLastError)(TCN_STDARGS)
+{
+    char buf[256];
+    UNREFERENCED(o);
+    ERR_error_string(ERR_get_error(), buf);
+    return tcn_new_string(e, buf);
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, hasOp)(TCN_STDARGS, jint op)
+{
+    return op == (op & supported_ssl_opts) ? JNI_TRUE : JNI_FALSE;
+}
+
+/*** Begin Twitter 1:1 API addition ***/
+TCN_IMPLEMENT_CALL(jint, SSL, getLastErrorNumber)(TCN_STDARGS) {
+    UNREFERENCED_STDARGS;
+    return ERR_get_error();
+}
+
+static void ssl_info_callback(const SSL *ssl, int where, int ret) {
+    int *handshakeCount = NULL;
+    if (0 != (where & SSL_CB_HANDSHAKE_START)) {
+        handshakeCount = (int*) SSL_get_app_data3((SSL*) ssl);
+        if (handshakeCount != NULL) {
+            ++(*handshakeCount);
+        }
+    }
+}
+
+TCN_IMPLEMENT_CALL(jlong /* SSL * */, SSL, newSSL)(TCN_STDARGS,
+                                                   jlong ctx /* tcn_ssl_ctxt_t * */,
+                                                   jboolean server) {
+    SSL *ssl = NULL;
+    int *handshakeCount = NULL;
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+
+    if (c == NULL) {
+        tcn_ThrowException(e, "ssl ctx is null");
+        return 0;
+    }
+    if (c->ctx == NULL) {
+        tcn_ThrowException(e, "ctx is null");
+        return 0;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    handshakeCount = malloc(sizeof(int));
+    ssl = SSL_new(c->ctx);
+    if (ssl == NULL) {
+        tcn_ThrowException(e, "cannot create new ssl");
+        return 0;
+    }
+
+    // Store the handshakeCount in the SSL instance.
+    *handshakeCount = 0;
+    SSL_set_app_data3(ssl, handshakeCount);
+
+    // Add callback to keep track of handshakes.
+    SSL_CTX_set_info_callback(c->ctx, ssl_info_callback);
+
+    if (server) {
+        SSL_set_accept_state(ssl);
+    } else {
+        SSL_set_connect_state(ssl);
+    }
+
+    // Setup verify and seed
+    SSL_set_verify_result(ssl, X509_V_OK);
+    SSL_rand_seed(c->rand_file);
+
+    // Store for later usage in SSL_callback_SSL_verify
+    SSL_set_app_data2(ssl, c);
+    return P2J(ssl);
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setBIO)(TCN_STDARGS,
+                                      jlong ssl /* SSL * */,
+                                      jlong rbio /* BIO * */,
+                                      jlong wbio /* BIO * */) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+    BIO *r = J2P(rbio, BIO *);
+    BIO *w = J2P(wbio, BIO *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    SSL_set_bio(ssl_, r, w);
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, getError)(TCN_STDARGS,
+                                       jlong ssl /* SSL * */,
+                                       jint ret) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    return SSL_get_error(ssl_, ret);
+}
+
+// How much did SSL write into this BIO?
+TCN_IMPLEMENT_CALL(jint /* nbytes */, SSL, pendingWrittenBytesInBIO)(TCN_STDARGS,
+                                                                     jlong bio /* BIO * */) {
+    BIO *b = J2P(bio, BIO *);
+
+    if (b == NULL) {
+        tcn_ThrowException(e, "bio is null");
+        return 0;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    return BIO_ctrl_pending(b);
+}
+
+// How much is available for reading in the given SSL struct?
+TCN_IMPLEMENT_CALL(jint, SSL, pendingReadableBytesInSSL)(TCN_STDARGS, jlong ssl /* SSL * */) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    return SSL_pending(ssl_);
+}
+
+// Write wlen bytes from wbuf into bio
+TCN_IMPLEMENT_CALL(jint /* status */, SSL, writeToBIO)(TCN_STDARGS,
+                                                       jlong bio /* BIO * */,
+                                                       jlong wbuf /* char* */,
+                                                       jint wlen /* sizeof(wbuf) */) {
+    BIO *b = J2P(bio, BIO *);
+    void *w = J2P(wbuf, void *);
+
+    if (b == NULL) {
+        tcn_ThrowException(e, "bio is null");
+        return 0;
+    }
+    if (w == NULL) {
+        tcn_ThrowException(e, "wbuf is null");
+        return 0;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    return BIO_write(b, w, wlen);
+
+}
+
+// Read up to rlen bytes from bio into rbuf
+TCN_IMPLEMENT_CALL(jint /* status */, SSL, readFromBIO)(TCN_STDARGS,
+                                                        jlong bio /* BIO * */,
+                                                        jlong rbuf /* char * */,
+                                                        jint rlen /* sizeof(rbuf) - 1 */) {
+    BIO *b = J2P(bio, BIO *);
+    void *r = J2P(rbuf, void *);
+
+    if (b == NULL) {
+        tcn_ThrowException(e, "bio is null");
+        return 0;
+    }
+    if (r == NULL) {
+        tcn_ThrowException(e, "rbuf is null");
+        return 0;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    return BIO_read(b, r, rlen);
+}
+
+// Write up to wlen bytes of application data to the ssl BIO (encrypt)
+TCN_IMPLEMENT_CALL(jint /* status */, SSL, writeToSSL)(TCN_STDARGS,
+                                                       jlong ssl /* SSL * */,
+                                                       jlong wbuf /* char * */,
+                                                       jint wlen /* sizeof(wbuf) */) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+    void *w = J2P(wbuf, void *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+    if (w == NULL) {
+        tcn_ThrowException(e, "wbuf is null");
+        return 0;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    return SSL_write(ssl_, w, wlen);
+}
+
+// Read up to rlen bytes of application data from the given SSL BIO (decrypt)
+TCN_IMPLEMENT_CALL(jint /* status */, SSL, readFromSSL)(TCN_STDARGS,
+                                                        jlong ssl /* SSL * */,
+                                                        jlong rbuf /* char * */,
+                                                        jint rlen /* sizeof(rbuf) - 1 */) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+    void *r = J2P(rbuf, void *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+    if (r == NULL) {
+        tcn_ThrowException(e, "rbuf is null");
+        return 0;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    return SSL_read(ssl_, r, rlen);
+}
+
+// Get the shutdown status of the engine
+TCN_IMPLEMENT_CALL(jint /* status */, SSL, getShutdown)(TCN_STDARGS,
+                                                        jlong ssl /* SSL * */) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    return SSL_get_shutdown(ssl_);
+}
+
+// Called when the peer closes the connection
+TCN_IMPLEMENT_CALL(void, SSL, setShutdown)(TCN_STDARGS,
+                                           jlong ssl /* SSL * */,
+                                           jint mode) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    SSL_set_shutdown(ssl_, mode);
+}
+
+// Free the SSL * and its associated internal BIO
+TCN_IMPLEMENT_CALL(void, SSL, freeSSL)(TCN_STDARGS,
+                                       jlong ssl /* SSL * */) {
+    int *handshakeCount = NULL;
+    SSL *ssl_ = J2P(ssl, SSL *);
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return;
+    }
+    handshakeCount = SSL_get_app_data3(ssl_);
+
+    UNREFERENCED_STDARGS;
+
+    if (handshakeCount != NULL) {
+        free(handshakeCount);
+    }
+    SSL_free(ssl_);
+}
+
+// Make a BIO pair (network and internal) for the provided SSL * and return the network BIO
+TCN_IMPLEMENT_CALL(jlong, SSL, makeNetworkBIO)(TCN_STDARGS,
+                                               jlong ssl /* SSL * */) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+    BIO *internal_bio;
+    BIO *network_bio;
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+
+    if (BIO_new_bio_pair(&internal_bio, 0, &network_bio, 0) != 1) {
+        tcn_ThrowException(e, "BIO_new_bio_pair failed");
+        return 0;
+    }
+
+    UNREFERENCED(o);
+
+    SSL_set_bio(ssl_, internal_bio, internal_bio);
+
+    return P2J(network_bio);
+}
+
+// Free a BIO * (typically, the network BIO)
+TCN_IMPLEMENT_CALL(void, SSL, freeBIO)(TCN_STDARGS,
+                                       jlong bio /* BIO * */) {
+    BIO *bio_ = J2P(bio, BIO *);
+
+    UNREFERENCED_STDARGS;
+
+    if (bio_ != NULL) {
+        BIO_free(bio_);
+    }
+}
+
+// Send CLOSE_NOTIFY to peer
+TCN_IMPLEMENT_CALL(jint /* status */, SSL, shutdownSSL)(TCN_STDARGS,
+                                                        jlong ssl /* SSL * */) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    return SSL_shutdown(ssl_);
+}
+
+// Read which cipher was negotiated for the given SSL *.
+TCN_IMPLEMENT_CALL(jstring, SSL, getCipherForSSL)(TCN_STDARGS,
+                                                  jlong ssl /* SSL * */)
+{
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return NULL;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    return AJP_TO_JSTRING(SSL_get_cipher(ssl_));
+}
+
+// Read which protocol was negotiated for the given SSL *.
+TCN_IMPLEMENT_CALL(jstring, SSL, getVersion)(TCN_STDARGS,
+                                                  jlong ssl /* SSL * */)
+{
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return NULL;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    return AJP_TO_JSTRING(SSL_get_version(ssl_));
+}
+
+// Is the handshake over yet?
+TCN_IMPLEMENT_CALL(jint, SSL, isInInit)(TCN_STDARGS,
+                                        jlong ssl /* SSL * */) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+
+    UNREFERENCED(o);
+
+    return SSL_in_init(ssl_);
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, doHandshake)(TCN_STDARGS,
+                                           jlong ssl /* SSL * */) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+
+    UNREFERENCED(o);
+
+    return SSL_do_handshake(ssl_);
+}
+
+// Read which protocol was negotiated for the given SSL *.
+TCN_IMPLEMENT_CALL(jstring, SSL, getNextProtoNegotiated)(TCN_STDARGS,
+                                                         jlong ssl /* SSL * */) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+    const unsigned char *proto;
+    unsigned int proto_len;
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return NULL;
+    }
+
+    UNREFERENCED(o);
+
+    SSL_get0_next_proto_negotiated(ssl_, &proto, &proto_len);
+    return tcn_new_stringn(e, (char*) proto, proto_len);
+}
+
+/*** End Twitter API Additions ***/
+
+/*** Apple API Additions ***/
+
+TCN_IMPLEMENT_CALL(jstring, SSL, getAlpnSelected)(TCN_STDARGS,
+                                                         jlong ssl /* SSL * */) {
+    // Use weak linking with GCC as this will alow us to run the same packaged version with multiple
+    // version of openssl.
+    #if defined(__GNUC__) || defined(__GNUG__)
+        if (!SSL_get0_alpn_selected) {
+            UNREFERENCED(o);
+            UNREFERENCED(ssl);
+            return NULL;
+        }
+    #endif
+
+    // We can only support it when either use openssl version >= 1.0.2 or GCC as this way we can use weak linking
+    #if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined(__GNUC__) || defined(__GNUG__)
+        SSL *ssl_ = J2P(ssl, SSL *);
+        const unsigned char *proto;
+        unsigned int proto_len;
+
+        if (ssl_ == NULL) {
+            tcn_ThrowException(e, "ssl is null");
+            return NULL;
+        }
+
+        UNREFERENCED(o);
+
+        SSL_get0_alpn_selected(ssl_, &proto, &proto_len);
+        return tcn_new_stringn(e, (char*) proto, proto_len);
+    #else
+        UNREFERENCED(o);
+        UNREFERENCED(ssl);
+        return NULL;
+    #endif
+}
+
+TCN_IMPLEMENT_CALL(jobjectArray, SSL, getPeerCertChain)(TCN_STDARGS,
+                                                  jlong ssl /* SSL * */)
+{
+    STACK_OF(X509) *sk;
+    int len;
+    int i;
+    X509 *cert;
+    int length;
+    unsigned char *buf;
+    jobjectArray array;
+    jbyteArray bArray;
+
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return NULL;
+    }
+
+    UNREFERENCED(o);
+
+    // Get a stack of all certs in the chain.
+    sk = SSL_get_peer_cert_chain(ssl_);
+
+    len = sk_num((_STACK*) sk);
+    if (len <= 0) {
+        // No peer certificate chain as no auth took place yet, or the auth was not successful.
+        return NULL;
+    }
+    // Create the byte[][] array that holds all the certs
+    array = (*e)->NewObjectArray(e, len, byteArrayClass, NULL);
+
+    for(i = 0; i < len; i++) {
+        cert = (X509*) sk_value((_STACK*) sk, i);
+
+        buf = NULL;
+        length = i2d_X509(cert, &buf);
+        if (length < 0) {
+            OPENSSL_free(buf);
+            // In case of error just return an empty byte[][]
+            return (*e)->NewObjectArray(e, 0, byteArrayClass, NULL);
+        }
+        bArray = (*e)->NewByteArray(e, length);
+        (*e)->SetByteArrayRegion(e, bArray, 0, length, (jbyte*) buf);
+        (*e)->SetObjectArrayElement(e, array, i, bArray);
+
+        // Delete the local reference as we not know how long the chain is and local references are otherwise
+        // only freed once jni method returns.
+        (*e)->DeleteLocalRef(e, bArray);
+
+        OPENSSL_free(buf);
+    }
+    return array;
+}
+
+TCN_IMPLEMENT_CALL(jbyteArray, SSL, getPeerCertificate)(TCN_STDARGS,
+                                                  jlong ssl /* SSL * */)
+{
+    X509 *cert;
+    int length;
+    unsigned char *buf = NULL;
+    jbyteArray bArray;
+
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return NULL;
+    }
+
+    UNREFERENCED(o);
+
+    // Get a stack of all certs in the chain
+    cert = SSL_get_peer_certificate(ssl_);
+    if (cert == NULL) {
+        return NULL;
+    }
+
+    length = i2d_X509(cert, &buf);
+
+    bArray = (*e)->NewByteArray(e, length);
+    (*e)->SetByteArrayRegion(e, bArray, 0, length, (jbyte*) buf);
+
+    // We need to free the cert as the reference count is incremented by one and it is not destroyed when the
+    // session is freed.
+    // See https://www.openssl.org/docs/ssl/SSL_get_peer_certificate.html
+    X509_free(cert);
+
+    OPENSSL_free(buf);
+
+    return bArray;
+}
+
+TCN_IMPLEMENT_CALL(jstring, SSL, getErrorString)(TCN_STDARGS, jlong number)
+{
+    char buf[256];
+    UNREFERENCED(o);
+    ERR_error_string(number, buf);
+    return tcn_new_string(e, buf);
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSL, getTime)(TCN_STDARGS, jlong ssl)
+{
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+
+    if (ssl_->session == NULL) {
+        // BoringSSL does not protect against a NULL session. OpenSSL
+        // returns 0 if the session is NULL, so do that here.
+        return 0;
+    }
+
+    UNREFERENCED(o);
+
+    return SSL_get_time(ssl_->session);
+}
+
+
+TCN_IMPLEMENT_CALL(jlong, SSL, getTimeout)(TCN_STDARGS, jlong ssl)
+{
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+
+    if (ssl_->session == NULL) {
+        // BoringSSL does not protect against a NULL session. OpenSSL
+        // returns 0 if the session is NULL, so do that here.
+        return 0;
+    }
+
+    UNREFERENCED(o);
+
+    return SSL_get_timeout(ssl_->session);
+}
+
+
+TCN_IMPLEMENT_CALL(jlong, SSL, setTimeout)(TCN_STDARGS, jlong ssl, jlong seconds)
+{
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+    if (ssl_->session == NULL) {
+        // BoringSSL does not protect against a NULL session. OpenSSL
+        // returns 0 if the session is NULL, so do that here.
+        return 0;
+    }
+
+    UNREFERENCED(o);
+
+    return SSL_set_timeout(ssl_->session, seconds);
+}
+
+
+TCN_IMPLEMENT_CALL(void, SSL, setVerify)(TCN_STDARGS, jlong ssl,
+                                                jint level, jint depth)
+{
+    tcn_ssl_ctxt_t *c;
+    int verify;
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return;
+    }
+
+    c = SSL_get_app_data2(ssl_);
+
+    verify = SSL_VERIFY_NONE;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(c->ctx != 0);
+    c->verify_mode = level;
+
+    if (c->verify_mode == SSL_CVERIFY_UNSET)
+        c->verify_mode = SSL_CVERIFY_NONE;
+    if (depth > 0)
+        c->verify_depth = depth;
+    /*
+     *  Configure callbacks for SSL context
+     */
+    if (c->verify_mode == SSL_CVERIFY_REQUIRE)
+        verify |= SSL_VERIFY_PEER_STRICT;
+    if ((c->verify_mode == SSL_CVERIFY_OPTIONAL) ||
+        (c->verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
+        verify |= SSL_VERIFY_PEER;
+    if (!c->store) {
+        if (SSL_CTX_set_default_verify_paths(c->ctx)) {
+            c->store = SSL_CTX_get_cert_store(c->ctx);
+            X509_STORE_set_flags(c->store, 0);
+        }
+        else {
+            /* XXX: See if this is fatal */
+        }
+    }
+
+    SSL_set_verify(ssl_, verify, SSL_callback_SSL_verify);
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setOptions)(TCN_STDARGS, jlong ssl,
+                                                 jint opt)
+{
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return;
+    }
+
+    UNREFERENCED_STDARGS;
+
+#ifndef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
+    /* Clear the flag if not supported */
+    if (opt & 0x00040000) {
+        opt &= ~0x00040000;
+    }
+#endif
+    SSL_set_options(ssl_, opt);
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, getOptions)(TCN_STDARGS, jlong ssl)
+{
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    return SSL_get_options(ssl_);
+}
+
+TCN_IMPLEMENT_CALL(jobjectArray, SSL, getCiphers)(TCN_STDARGS, jlong ssl)
+{
+    STACK_OF(SSL_CIPHER) *sk;
+    int len;
+    jobjectArray array;
+    SSL_CIPHER *cipher;
+    const char *name;
+    int i;
+    jstring c_name;
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return NULL;
+    }
+
+    UNREFERENCED_STDARGS;
+
+    sk = SSL_get_ciphers(ssl_);
+    len = sk_num((_STACK*) sk);
+
+    if (len <= 0) {
+        // No peer certificate chain as no auth took place yet, or the auth was not successful.
+        return NULL;
+    }
+
+    // Create the byte[][] array that holds all the certs
+    array = (*e)->NewObjectArray(e, len, stringClass, NULL);
+
+    for (i = 0; i < len; i++) {
+        cipher = (SSL_CIPHER*) sk_value((_STACK*) sk, i);
+        name = SSL_CIPHER_get_name(cipher);
+
+        c_name = (*e)->NewStringUTF(e, name);
+        (*e)->SetObjectArrayElement(e, array, i, c_name);
+    }
+    return array;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, setCipherSuites)(TCN_STDARGS, jlong ssl,
+                                                         jstring ciphers)
+{
+    jboolean rv = JNI_TRUE;
+    TCN_ALLOC_CSTRING(ciphers);
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return JNI_FALSE;
+    }
+
+    UNREFERENCED(o);
+
+    if (!J2S(ciphers)) {
+        return JNI_FALSE;
+    }
+
+    if (!SSL_set_cipher_list(ssl_, J2S(ciphers))) {
+        char err[256];
+        ERR_error_string(ERR_get_error(), err);
+        tcn_Throw(e, "Unable to configure permitted SSL ciphers (%s)", err);
+        rv = JNI_FALSE;
+    }
+
+    TCN_FREE_CSTRING(ciphers);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jbyteArray, SSL, getSessionId)(TCN_STDARGS, jlong ssl)
+{
+
+    unsigned int len;
+    const unsigned char *session_id;
+    SSL_SESSION *session;
+    jbyteArray bArray;
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return NULL;
+    }
+
+    UNREFERENCED(o);
+
+    session = SSL_get_session(ssl_);
+    if (session == NULL) {
+        return NULL;
+    }
+
+    session_id = SSL_SESSION_get_id(session, &len);
+    if (len == 0 || session_id == NULL) {
+        return NULL;
+    }
+
+    bArray = (*e)->NewByteArray(e, len);
+    (*e)->SetByteArrayRegion(e, bArray, 0, len, (jbyte*) session_id);
+    return bArray;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, getHandshakeCount)(TCN_STDARGS, jlong ssl)
+{
+    int *handshakeCount = NULL;
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return -1;
+    }
+
+    UNREFERENCED(o);
+
+    handshakeCount = SSL_get_app_data3(ssl_);
+    if (handshakeCount != NULL) {
+        return *handshakeCount;
+    }
+    return 0;
+}
+
+
+TCN_IMPLEMENT_CALL(void, SSL, clearError)(TCN_STDARGS)
+{
+    UNREFERENCED(o);
+    ERR_clear_error();
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, renegotiate)(TCN_STDARGS,
+                                           jlong ssl /* SSL * */) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return 0;
+    }
+
+    UNREFERENCED(o);
+
+    return SSL_renegotiate(ssl_);
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setState)(TCN_STDARGS,
+                                           jlong ssl, /* SSL * */
+                                           jint state) {
+    SSL *ssl_ = J2P(ssl, SSL *);
+
+    if (ssl_ == NULL) {
+        tcn_ThrowException(e, "ssl is null");
+        return;
+    }
+
+    UNREFERENCED(o);
+
+    SSL_set_state(ssl_, state);
+}
+
+
+/*** End Apple API Additions ***/
+
+#else
+#error OpenSSL is required!
+
+/* OpenSSL is not supported.
+ * Create empty stubs.
+ */
+
+TCN_IMPLEMENT_CALL(jint, SSL, version)(TCN_STDARGS)
+{
+    UNREFERENCED_STDARGS;
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jstring, SSL, versionString)(TCN_STDARGS)
+{
+    UNREFERENCED_STDARGS;
+    return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, initialize)(TCN_STDARGS, jstring engine)
+{
+    UNREFERENCED(o);
+    UNREFERENCED(engine);
+    tcn_ThrowAPRException(e, APR_ENOTIMPL);
+    return (jint)APR_ENOTIMPL;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, randLoad)(TCN_STDARGS, jstring file)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(file);
+    return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, randSave)(TCN_STDARGS, jstring file)
+{
+    UNREFERENCED_STDARGS;
+    return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, randMake)(TCN_STDARGS, jstring file,
+                                            jint length, jboolean base64)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(file);
+    UNREFERENCED(length);
+    UNREFERENCED(base64);
+    return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, randSet)(TCN_STDARGS, jstring file)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(file);
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, fipsModeGet)(TCN_STDARGS)
+{
+    UNREFERENCED(o);
+    tcn_ThrowException(e, "FIPS was not available to tcnative at build time. You will need to re-build tcnative against an OpenSSL with FIPS.");
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, fipsModeSet)(TCN_STDARGS, jint mode)
+{
+    UNREFERENCED(o);
+    UNREFERENCED(mode);
+    tcn_ThrowException(e, "FIPS was not available to tcnative at build time. You will need to re-build tcnative against an OpenSSL with FIPS.");
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSL, newBIO)(TCN_STDARGS, jlong pool,
+                                       jobject callback)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(pool);
+    UNREFERENCED(callback);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSL, newMemBIO)(TCN_STDARGS)
+{
+    UNREFERENCED_STDARGS;
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, closeBIO)(TCN_STDARGS, jlong bio)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(bio);
+    return (jint)APR_ENOTIMPL;
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setPasswordCallback)(TCN_STDARGS,
+                                                   jobject callback)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(callback);
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setPassword)(TCN_STDARGS, jstring password)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(password);
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, generateRSATempKey)(TCN_STDARGS, jint idx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(idx);
+    return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, loadDSATempKey)(TCN_STDARGS, jint idx,
+                                                  jstring file)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(idx);
+    UNREFERENCED(file);
+    return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jstring, SSL, getLastError)(TCN_STDARGS)
+{
+    UNREFERENCED_STDARGS;
+    return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSL, hasOp)(TCN_STDARGS, jint op)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(op);
+    return JNI_FALSE;
+}
+
+/*** Begin Twitter 1:1 API addition ***/
+TCN_IMPLEMENT_CALL(jint, SSL, getLastErrorNumber)(TCN_STDARGS) {
+  UNREFERENCED(o);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSL, newSSL)(TCN_STDARGS, jlong ssl_ctx) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl_ctx);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setBIO)(TCN_STDARGS, jlong ssl, jlong rbio, jlong wbio) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  UNREFERENCED(rbio);
+  UNREFERENCED(wbio);
+  tcn_ThrowException(e, "Not implemented");
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, pendingWrittenBytesInBIO)(TCN_STDARGS, jlong bio) {
+  UNREFERENCED(o);
+  UNREFERENCED(bio);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, pendingReadableBytesInSSL)(TCN_STDARGS, jlong ssl) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, writeToBIO)(TCN_STDARGS, jlong bio, jlong wbuf, jint wlen) {
+  UNREFERENCED(o);
+  UNREFERENCED(bio);
+  UNREFERENCED(wbuf);
+  UNREFERENCED(wlen);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, readFromBIO)(TCN_STDARGS, jlong bio, jlong rbuf, jint rlen) {
+  UNREFERENCED(o);
+  UNREFERENCED(bio);
+  UNREFERENCED(rbuf);
+  UNREFERENCED(rlen);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, writeToSSL)(TCN_STDARGS, jlong ssl, jlong wbuf, jint wlen) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  UNREFERENCED(wbuf);
+  UNREFERENCED(wlen);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, readFromSSL)(TCN_STDARGS, jlong ssl, jlong rbuf, jint rlen) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  UNREFERENCED(rbuf);
+  UNREFERENCED(rlen);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, getShutdown)(TCN_STDARGS, jlong ssl) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setShutdown)(TCN_STDARGS, jlong ssl, jint mode) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  UNREFERENCED(mode);
+  tcn_ThrowException(e, "Not implemented");
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, freeSSL)(TCN_STDARGS, jlong ssl) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSL, makeNetworkBIO)(TCN_STDARGS, jlong ssl) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, freeBIO)(TCN_STDARGS, jlong bio) {
+  UNREFERENCED(o);
+  UNREFERENCED(bio);
+  tcn_ThrowException(e, "Not implemented");
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, shutdownSSL)(TCN_STDARGS, jlong ssl) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(jstring, SSL, getCipherForSSL)(TCN_STDARGS, jlong ssl) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+  return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, isInInit)(TCN_STDARGS, jlong ssl) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, doHandshake)(TCN_STDARGS, jlong ssl) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+}
+
+TCN_IMPLEMENT_CALL(jstring, SSL, getNextProtoNegotiated)(TCN_STDARGS, jlong ssl) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+  return NULL;
+}
+
+/*** End Twitter 1:1 API addition ***/
+
+/*** Begin Apple 1:1 API addition ***/
+
+TCN_IMPLEMENT_CALL(jstring, SSL, getAlpnSelected)(TCN_STDARGS, jlong ssl) {
+    UNREFERENCED(o);
+    UNREFERENCED(ssl);
+    tcn_ThrowException(e, "Not implemented");
+    return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jobjectArray, SSL, getPeerCertChain)(TCN_STDARGS, jlong ssl)
+{
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+  return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jbyteArray, SSL, getPeerCertificate)(TCN_STDARGS, jlong ssl)
+{
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+  return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jstring, SSL, getErrorString)(TCN_STDARGS, jlong number)
+{
+  UNREFERENCED(o);
+  tcn_ThrowException(e, "Not implemented");
+  return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jstring, SSL, getVersion)(TCN_STDARGS, jlong ssl)
+{
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+  return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSL, getTime)(TCN_STDARGS, jlong ssl)
+{
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSL, getTimeout)(TCN_STDARGS, jlong ssl)
+{
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSL, setTimeout)(TCN_STDARGS, jlong ssl, jlong seconds)
+{
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  UNREFERENCED(seconds);
+  tcn_ThrowException(e, "Not implemented");
+  return 0;
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setVerify)(TCN_STDARGS, jlong ssl,
+                                                jint level, jint depth)
+{
+    UNREFERENCED(o);
+    UNREFERENCED(ssl);
+    tcn_ThrowException(e, "Not implemented");
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setOptions)(TCN_STDARGS, jlong ssl,
+                                                 jint opt)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ssl);
+    UNREFERENCED(opt);
+    tcn_ThrowException(e, "Not implemented");
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, getOptions)(TCN_STDARGS, jlong ssl)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ssl);
+    tcn_ThrowException(e, "Not implemented");
+    return 0;
+}
+TCN_IMPLEMENT_CALL(jobjectArray, SSL, getCiphers)(TCN_STDARGS, jlong ssl)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ssl);
+    tcn_ThrowException(e, "Not implemented");
+    return 0;
+}
+TCN_IMPLEMENT_CALL(jboolean, SSL, setCipherSuites)(TCN_STDARGS, jlong ssl,
+                                                         jstring ciphers)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ssl);
+    UNREFERENCED(ciphers);
+    tcn_ThrowException(e, "Not implemented");
+    return JNI_FALSE;
+}
+TCN_IMPLEMENT_CALL(jbyteArray, SSL, getSessionId)(TCN_STDARGS, jlong ssl)
+{
+    UNREFERENCED(o);
+    UNREFERENCED(ssl);
+    tcn_ThrowException(e, "Not implemented");
+}
+TCN_IMPLEMENT_CALL(jint, SSL, getHandshakeCount)(TCN_STDARGS, jlong ssl)
+{
+    UNREFERENCED(o);
+    UNREFERENCED(ssl);
+    tcn_ThrowException(e, "Not implemented");
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, clearError)(TCN_STDARGS)
+{
+    UNREFERENCED(o);
+    tcn_ThrowException(e, "Not implemented");
+}
+
+TCN_IMPLEMENT_CALL(jint, SSL, renegotiate)(TCN_STDARGS, jlong ssl) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  tcn_ThrowException(e, "Not implemented");
+}
+
+TCN_IMPLEMENT_CALL(void, SSL, setState)(TCN_STDARGS, jlong ssl, jint state) {
+  UNREFERENCED(o);
+  UNREFERENCED(ssl);
+  UNREFERENCED(state);
+  tcn_ThrowException(e, "Not implemented");
+}
+/*** End Apple API Additions ***/
+#endif
diff --git a/c/ssl_private.h b/c/ssl_private.h
new file mode 100644
index 0000000..c8de472
--- /dev/null
+++ b/c/ssl_private.h
@@ -0,0 +1,372 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: ssl_private.h 1658728 2015-02-10 14:45:19Z kkolinko $
+ */
+
+#ifndef SSL_PRIVATE_H
+#define SSL_PRIVATE_H
+
+/* Exclude unused OpenSSL features
+ * even if the OpenSSL supports them
+ */
+#ifndef OPENSSL_NO_IDEA
+#define OPENSSL_NO_IDEA
+#endif
+#ifndef OPENSSL_NO_KRB5
+#define OPENSSL_NO_KRB5
+#endif
+#ifndef OPENSSL_NO_MDC2
+#define OPENSSL_NO_MDC2
+#endif
+#ifndef OPENSSL_NO_RC5
+#define OPENSSL_NO_RC5
+#endif
+
+#include "apr_thread_rwlock.h"
+
+/* OpenSSL headers */
+#include <openssl/opensslv.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h> 
+#include <openssl/crypto.h>
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+#include <openssl/x509v3.h>
+/* Avoid tripping over an engine build installed globally and detected
+ * when the user points at an explicit non-engine flavor of OpenSSL
+ */
+#ifndef OPENSSL_NO_ENGINE
+#include <openssl/engine.h>
+#endif
+
+#ifndef RAND_MAX
+#include <limits.h>
+#define RAND_MAX INT_MAX
+#endif
+
+#define SSL_ALGO_UNKNOWN (0)
+#define SSL_ALGO_RSA     (1<<0)
+#define SSL_ALGO_DSA     (1<<1)
+#define SSL_ALGO_ALL     (SSL_ALGO_RSA|SSL_ALGO_DSA)
+
+#define SSL_AIDX_RSA     (0)
+#define SSL_AIDX_DSA     (1)
+#define SSL_AIDX_MAX     (2)
+
+/*
+ * Define IDs for the temporary RSA keys and DH params
+ */
+
+#define SSL_TMP_KEY_RSA_512     (0)
+#define SSL_TMP_KEY_RSA_1024    (1)
+#define SSL_TMP_KEY_RSA_2048    (2)
+#define SSL_TMP_KEY_RSA_4096    (3)
+#define SSL_TMP_KEY_DH_512      (4)
+#define SSL_TMP_KEY_DH_1024     (5)
+#define SSL_TMP_KEY_DH_2048     (6)
+#define SSL_TMP_KEY_DH_4096     (7)
+#define SSL_TMP_KEY_MAX         (8)
+
+#define SSL_CRT_FORMAT_UNDEF    (0)
+#define SSL_CRT_FORMAT_ASN1     (1)
+#define SSL_CRT_FORMAT_TEXT     (2)
+#define SSL_CRT_FORMAT_PEM      (3)
+#define SSL_CRT_FORMAT_NETSCAPE (4)
+#define SSL_CRT_FORMAT_PKCS12   (5)
+#define SSL_CRT_FORMAT_SMIME    (6)
+#define SSL_CRT_FORMAT_ENGINE   (7)
+/* XXX this stupid macro helps us to avoid
+ * adding yet another param to load_*key()
+ */
+#define SSL_KEY_FORMAT_IISSGC   (8)
+
+/*
+ * Define the SSL options
+ */
+#define SSL_OPT_NONE            (0)
+#define SSL_OPT_RELSET          (1<<0)
+#define SSL_OPT_STDENVVARS      (1<<1)
+#define SSL_OPT_EXPORTCERTDATA  (1<<3)
+#define SSL_OPT_FAKEBASICAUTH   (1<<4)
+#define SSL_OPT_STRICTREQUIRE   (1<<5)
+#define SSL_OPT_OPTRENEGOTIATE  (1<<6)
+#define SSL_OPT_ALL             (SSL_OPT_STDENVVARS|SSL_OPT_EXPORTCERTDATA|SSL_OPT_FAKEBASICAUTH|SSL_OPT_STRICTREQUIRE|SSL_OPT_OPTRENEGOTIATE)
+
+/*
+ * Define the SSL Protocol options
+ */
+#define SSL_PROTOCOL_NONE       (0)
+#define SSL_PROTOCOL_SSLV2      (1<<0)
+#define SSL_PROTOCOL_SSLV3      (1<<1)
+#define SSL_PROTOCOL_TLSV1      (1<<2)
+#define SSL_PROTOCOL_TLSV1_1    (1<<3)
+#define SSL_PROTOCOL_TLSV1_2    (1<<4)
+#define SSL_PROTOCOL_ALL        (SSL_PROTOCOL_SSLV2|SSL_PROTOCOL_SSLV3|SSL_PROTOCOL_TLSV1|SSL_PROTOCOL_TLSV1_1|SSL_PROTOCOL_TLSV1_2)
+
+#define SSL_MODE_CLIENT         (0)
+#define SSL_MODE_SERVER         (1)
+#define SSL_MODE_COMBINED       (2)
+
+#define SSL_BIO_FLAG_RDONLY     (1<<0)
+#define SSL_BIO_FLAG_CALLBACK   (1<<1)
+#define SSL_DEFAULT_CACHE_SIZE  (256)
+#define SSL_DEFAULT_VHOST_NAME  ("_default_:443")
+#define SSL_MAX_STR_LEN         (2048)
+#define SSL_MAX_PASSWORD_LEN    (256)
+
+#define SSL_CVERIFY_UNSET           (-1)
+#define SSL_CVERIFY_NONE            (0)
+#define SSL_CVERIFY_OPTIONAL        (1)
+#define SSL_CVERIFY_REQUIRE         (2)
+#define SSL_CVERIFY_OPTIONAL_NO_CA  (3)
+#define SSL_VERIFY_PEER_STRICT      (SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
+
+#define SSL_SHUTDOWN_TYPE_UNSET     (0)
+#define SSL_SHUTDOWN_TYPE_STANDARD  (1)
+#define SSL_SHUTDOWN_TYPE_UNCLEAN   (2)
+#define SSL_SHUTDOWN_TYPE_ACCURATE  (3)
+
+#define SSL_TO_APR_ERROR(X)         (APR_OS_START_USERERR + 1000 + X)
+
+#define SSL_INFO_SESSION_ID                 (0x0001)
+#define SSL_INFO_CIPHER                     (0x0002)
+#define SSL_INFO_CIPHER_USEKEYSIZE          (0x0003)
+#define SSL_INFO_CIPHER_ALGKEYSIZE          (0x0004)
+#define SSL_INFO_CIPHER_VERSION             (0x0005)
+#define SSL_INFO_CIPHER_DESCRIPTION         (0x0006)
+#define SSL_INFO_PROTOCOL                   (0x0007)
+
+#define SSL_INFO_CLIENT_S_DN                (0x0010)
+#define SSL_INFO_CLIENT_I_DN                (0x0020)
+#define SSL_INFO_SERVER_S_DN                (0x0040)
+#define SSL_INFO_SERVER_I_DN                (0x0080)
+
+#define SSL_INFO_DN_COUNTRYNAME             (0x0001)
+#define SSL_INFO_DN_STATEORPROVINCENAME     (0x0002)
+#define SSL_INFO_DN_LOCALITYNAME            (0x0003)
+#define SSL_INFO_DN_ORGANIZATIONNAME        (0x0004)
+#define SSL_INFO_DN_ORGANIZATIONALUNITNAME  (0x0005)
+#define SSL_INFO_DN_COMMONNAME              (0x0006)
+#define SSL_INFO_DN_TITLE                   (0x0007)
+#define SSL_INFO_DN_INITIALS                (0x0008)
+#define SSL_INFO_DN_GIVENNAME               (0x0009)
+#define SSL_INFO_DN_SURNAME                 (0x000A)
+#define SSL_INFO_DN_DESCRIPTION             (0x000B)
+#define SSL_INFO_DN_UNIQUEIDENTIFIER        (0x000C)
+#define SSL_INFO_DN_EMAILADDRESS            (0x000D)
+
+#define SSL_INFO_CLIENT_MASK                (0x0100)
+
+#define SSL_INFO_CLIENT_M_VERSION           (0x0101)
+#define SSL_INFO_CLIENT_M_SERIAL            (0x0102)
+#define SSL_INFO_CLIENT_V_START             (0x0103)
+#define SSL_INFO_CLIENT_V_END               (0x0104)
+#define SSL_INFO_CLIENT_A_SIG               (0x0105)
+#define SSL_INFO_CLIENT_A_KEY               (0x0106)
+#define SSL_INFO_CLIENT_CERT                (0x0107)
+#define SSL_INFO_CLIENT_V_REMAIN            (0x0108)
+
+#define SSL_INFO_SERVER_MASK                (0x0200)
+
+#define SSL_INFO_SERVER_M_VERSION           (0x0201)
+#define SSL_INFO_SERVER_M_SERIAL            (0x0202)
+#define SSL_INFO_SERVER_V_START             (0x0203)
+#define SSL_INFO_SERVER_V_END               (0x0204)
+#define SSL_INFO_SERVER_A_SIG               (0x0205)
+#define SSL_INFO_SERVER_A_KEY               (0x0206)
+#define SSL_INFO_SERVER_CERT                (0x0207)
+#define SSL_INFO_CLIENT_CERT_CHAIN          (0x0400)
+
+#define SSL_VERIFY_ERROR_IS_OPTIONAL(errnum) \
+   ((errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) \
+    || (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) \
+    || (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) \
+    || (errnum == X509_V_ERR_CERT_UNTRUSTED) \
+    || (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE))
+
+#define SSL_DEFAULT_PASS_PROMPT "Some of your private key files are encrypted for security reasons.\n"  \
+                                "In order to read them you have to provide the pass phrases.\n"         \
+                                "Enter password :"
+
+#define OCSP_STATUS_OK        0
+#define OCSP_STATUS_REVOKED   1
+#define OCSP_STATUS_UNKNOWN   2
+
+#define MAX_ALPN_NPN_PROTO_SIZE 65535
+
+/* ECC: make sure we have at least 1.0.0 */
+#if !defined(OPENSSL_NO_EC) && defined(TLSEXT_ECPOINTFORMAT_uncompressed)
+#define HAVE_ECC              1
+#endif
+
+
+#define SSL_SELECTOR_FAILURE_NO_ADVERTISE                       0
+#define SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL            1
+
+#define SSL_SESSION_TICKET_KEY_NAME_LEN 16
+#define SSL_SESSION_TICKET_AES_KEY_LEN  16
+#define SSL_SESSION_TICKET_HMAC_KEY_LEN 16
+#define SSL_SESSION_TICKET_KEY_SIZE     48
+
+extern void *SSL_temp_keys[SSL_TMP_KEY_MAX];
+
+typedef struct {
+    /* client can have any number of cert/key pairs */
+    const char  *cert_file;
+    const char  *cert_path;
+    STACK_OF(X509_INFO) *certs;
+} ssl_pkc_t;
+
+typedef struct tcn_ssl_ctxt_t tcn_ssl_ctxt_t;
+
+typedef struct {
+    char            password[SSL_MAX_PASSWORD_LEN];
+    const char     *prompt;
+    tcn_callback_t cb;
+} tcn_pass_cb_t;
+
+extern tcn_pass_cb_t tcn_password_callback;
+
+typedef struct {
+    unsigned char   key_name[SSL_SESSION_TICKET_KEY_NAME_LEN];
+    unsigned char   hmac_key[SSL_SESSION_TICKET_HMAC_KEY_LEN];
+    unsigned char   aes_key[SSL_SESSION_TICKET_AES_KEY_LEN];
+} tcn_ssl_ticket_key_t;
+
+struct tcn_ssl_ctxt_t {
+    apr_pool_t              *pool;
+    SSL_CTX                 *ctx;
+    BIO                     *bio_os;
+    BIO                     *bio_is;
+
+    unsigned char           context_id[SHA_DIGEST_LENGTH];
+
+    int                     protocol;
+    /* we are one or the other */
+    int                     mode;
+
+    /* certificate revocation list */
+    X509_STORE              *crl;
+    /* pointer to the context verify store */
+    X509_STORE              *store;
+    const char              *cert_files[SSL_AIDX_MAX];
+    const char              *key_files[SSL_AIDX_MAX];
+    X509                    *certs[SSL_AIDX_MAX];
+    EVP_PKEY                *keys[SSL_AIDX_MAX];
+
+    int                     ca_certs;
+    int                     shutdown_type;
+    char                    *rand_file;
+
+    const char              *cipher_suite;
+    /* for client or downstream server authentication */
+    int                     verify_depth;
+    int                     verify_mode;
+    tcn_pass_cb_t           *cb_data;
+
+    /* certificate verifier callback */
+    jobject verifier;
+    jmethodID verifier_method;
+
+    unsigned char           *next_proto_data;
+    unsigned int            next_proto_len;
+    int                     next_selector_failure_behavior;
+
+    /* Holds the alpn protocols, each of them prefixed with the len of the protocol */
+    unsigned char           *alpn_proto_data;
+    unsigned int            alpn_proto_len;
+    int                     alpn_selector_failure_behavior;
+
+    apr_thread_rwlock_t     *mutex;
+    tcn_ssl_ticket_key_t    *ticket_keys;
+    unsigned int            ticket_keys_len;
+};
+
+  
+typedef struct {
+    apr_pool_t     *pool;
+    tcn_ssl_ctxt_t *ctx;
+    SSL            *ssl;
+    X509           *peer;
+    int             shutdown_type;
+    /* Track the handshake/renegotiation state for the connection so
+     * that all client-initiated renegotiations can be rejected, as a
+     * partial fix for CVE-2009-3555.
+     */
+    enum { 
+        RENEG_INIT = 0, /* Before initial handshake */
+        RENEG_REJECT,   /* After initial handshake; any client-initiated
+                         * renegotiation should be rejected
+                         */
+        RENEG_ALLOW,    /* A server-initated renegotiation is taking
+                         * place (as dictated by configuration)
+                         */
+        RENEG_ABORT     /* Renegotiation initiated by client, abort the
+                         * connection
+                         */
+    } reneg_state;
+    apr_socket_t   *sock;
+    apr_pollset_t  *pollset;
+} tcn_ssl_conn_t;
+
+
+/*
+ *  Additional Functions
+ */
+void        SSL_init_app_data2_3_idx(void);
+// The app_data2 is used to store the tcn_ssl_ctxt_t pointer for the SSL instance.
+void       *SSL_get_app_data2(SSL *);
+void        SSL_set_app_data2(SSL *, void *);
+// The app_data3 is used to store the handshakeCount pointer for the SSL instance.
+void       *SSL_get_app_data3(SSL *);
+void        SSL_set_app_data3(SSL *, void *);
+int         SSL_password_prompt(tcn_pass_cb_t *);
+int         SSL_password_callback(char *, int, int, void *);
+void        SSL_BIO_close(BIO *);
+void        SSL_BIO_doref(BIO *);
+DH         *SSL_dh_get_tmp_param(int);
+DH         *SSL_dh_get_param_from_file(const char *);
+RSA        *SSL_callback_tmp_RSA(SSL *, int, int);
+DH         *SSL_callback_tmp_DH(SSL *, int, int);
+void        SSL_callback_handshake(const SSL *, int, int);
+int         SSL_CTX_use_certificate_chain(SSL_CTX *, const char *, int);
+int         SSL_CTX_use_certificate_chain_bio(SSL_CTX *, BIO *, int);
+int         SSL_callback_SSL_verify(int, X509_STORE_CTX *);
+int         SSL_rand_seed(const char *file);
+int         SSL_callback_next_protos(SSL *, const unsigned char **, unsigned int *, void *);
+int         SSL_callback_select_next_proto(SSL *, unsigned char **, unsigned char *, const unsigned char *, unsigned int,void *);
+int         SSL_callback_alpn_select_proto(SSL *, const unsigned char **, unsigned char *, const unsigned char *, unsigned int, void *);
+
+
+#if defined(__GNUC__) || defined(__GNUG__)
+    // only supported with GCC, this will be used to support different openssl versions at the same time.
+    extern int SSL_CTX_set_alpn_protos(SSL_CTX *ctx, const unsigned char *protos,
+           unsigned protos_len) __attribute__((weak));
+    extern void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, int (*cb) (SSL *ssl, const unsigned char **out,
+           unsigned char *outlen, const unsigned char *in, unsigned int inlen,
+           void *arg), void *arg) __attribute__((weak));
+    extern void SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data,
+           unsigned *len) __attribute__((weak));
+#endif
+
+#endif /* SSL_PRIVATE_H */
diff --git a/c/sslcontext.c b/c/sslcontext.c
new file mode 100644
index 0000000..925ca2a
--- /dev/null
+++ b/c/sslcontext.c
@@ -0,0 +1,1925 @@
+/* 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.
+ */
+
+/** SSL Context wrapper
+ *
+ * @author Mladen Turk
+ * @version $Id: sslcontext.c 1649733 2015-01-06 04:42:24Z billbarker $
+ */
+
+#include "tcn.h"
+
+#include "apr_file_io.h"
+#include "apr_thread_mutex.h"
+#include "apr_thread_rwlock.h"
+#include "apr_poll.h"
+
+#ifdef HAVE_OPENSSL
+#include "ssl_private.h"
+
+static jclass byteArrayClass;
+
+static apr_status_t ssl_context_cleanup(void *data)
+{
+    tcn_ssl_ctxt_t *c = (tcn_ssl_ctxt_t *)data;
+    JNIEnv *e;
+
+    if (c) {
+        int i;
+        if (c->crl)
+            X509_STORE_free(c->crl);
+        c->crl = NULL;
+        if (c->ctx)
+            SSL_CTX_free(c->ctx);
+        c->ctx = NULL;
+        for (i = 0; i < SSL_AIDX_MAX; i++) {
+            if (c->certs[i]) {
+                X509_free(c->certs[i]);
+                c->certs[i] = NULL;
+            }
+            if (c->keys[i]) {
+                EVP_PKEY_free(c->keys[i]);
+                c->keys[i] = NULL;
+            }
+        }
+        if (c->bio_is) {
+            SSL_BIO_close(c->bio_is);
+            c->bio_is = NULL;
+        }
+        if (c->bio_os) {
+            SSL_BIO_close(c->bio_os);
+            c->bio_os = NULL;
+        }
+
+        if (c->verifier) {
+            tcn_get_java_env(&e);
+            (*e)->DeleteGlobalRef(e, c->verifier);
+            c->verifier = NULL;
+        }
+        c->verifier_method = NULL;
+
+        if (c->next_proto_data) {
+            free(c->next_proto_data);
+            c->next_proto_data = NULL;
+        }
+        c->next_proto_len = 0;
+
+        if (c->alpn_proto_data) {
+            free(c->alpn_proto_data);
+            c->alpn_proto_data = NULL;
+        }
+        c->alpn_proto_len = 0;
+
+        apr_thread_rwlock_destroy(c->mutex);
+
+        if (c->ticket_keys) {
+            free(c->ticket_keys);
+            c->ticket_keys = NULL;
+        }
+        c->ticket_keys_len = 0;
+    }
+    return APR_SUCCESS;
+}
+
+/* Initialize server context */
+TCN_IMPLEMENT_CALL(jlong, SSLContext, make)(TCN_STDARGS, jlong pool,
+                                            jint protocol, jint mode)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    tcn_ssl_ctxt_t *c = NULL;
+    SSL_CTX *ctx = NULL;
+    jclass clazz;
+
+    UNREFERENCED(o);
+
+    if (protocol == SSL_PROTOCOL_TLSV1_2) {
+#ifdef SSL_OP_NO_TLSv1_2
+        if (mode == SSL_MODE_CLIENT)
+            ctx = SSL_CTX_new(TLSv1_2_client_method());
+        else if (mode == SSL_MODE_SERVER)
+            ctx = SSL_CTX_new(TLSv1_2_server_method());
+        else
+            ctx = SSL_CTX_new(TLSv1_2_method());
+#endif
+    } else if (protocol == SSL_PROTOCOL_TLSV1_1) {
+#ifdef SSL_OP_NO_TLSv1_1
+        if (mode == SSL_MODE_CLIENT)
+            ctx = SSL_CTX_new(TLSv1_1_client_method());
+        else if (mode == SSL_MODE_SERVER)
+            ctx = SSL_CTX_new(TLSv1_1_server_method());
+        else
+            ctx = SSL_CTX_new(TLSv1_1_method());
+#endif
+    } else if (protocol == SSL_PROTOCOL_TLSV1) {
+        if (mode == SSL_MODE_CLIENT)
+            ctx = SSL_CTX_new(TLSv1_client_method());
+        else if (mode == SSL_MODE_SERVER)
+            ctx = SSL_CTX_new(TLSv1_server_method());
+        else
+            ctx = SSL_CTX_new(TLSv1_method());
+#ifndef OPENSSL_NO_SSL3
+    } else if (protocol == SSL_PROTOCOL_SSLV3) {
+        if (mode == SSL_MODE_CLIENT)
+            ctx = SSL_CTX_new(SSLv3_client_method());
+        else if (mode == SSL_MODE_SERVER)
+            ctx = SSL_CTX_new(SSLv3_server_method());
+        else
+            ctx = SSL_CTX_new(SSLv3_method());
+#endif
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) && !defined(OPENSSL_NO_SSL2)
+    } else if (protocol == SSL_PROTOCOL_SSLV2) {
+        if (mode == SSL_MODE_CLIENT)
+            ctx = SSL_CTX_new(SSLv2_client_method());
+        else if (mode == SSL_MODE_SERVER)
+            ctx = SSL_CTX_new(SSLv2_server_method());
+        else
+            ctx = SSL_CTX_new(SSLv2_method());
+#endif
+#ifndef SSL_OP_NO_TLSv1_2
+    } else if (protocol & SSL_PROTOCOL_TLSV1_2) {
+        /* requested but not supported */
+#endif
+#ifndef SSL_OP_NO_TLSv1_1
+    } else if (protocol & SSL_PROTOCOL_TLSV1_1) {
+        /* requested but not supported */
+#endif
+#ifndef OPENSSL_NO_SSL3
+    } else {
+        if (mode == SSL_MODE_CLIENT)
+            ctx = SSL_CTX_new(SSLv23_client_method());
+        else if (mode == SSL_MODE_SERVER)
+            ctx = SSL_CTX_new(SSLv23_server_method());
+        else
+            ctx = SSL_CTX_new(SSLv23_method());
+#endif
+    }
+
+    if (!ctx) {
+        char err[256];
+        ERR_error_string(ERR_get_error(), err);
+        tcn_Throw(e, "Invalid Server SSL Protocol (%s)", err);
+        goto init_failed;
+    }
+    if ((c = apr_pcalloc(p, sizeof(tcn_ssl_ctxt_t))) == NULL) {
+        tcn_ThrowAPRException(e, apr_get_os_error());
+        goto init_failed;
+    }
+
+    c->protocol = protocol;
+    c->mode     = mode;
+    c->ctx      = ctx;
+    c->pool     = p;
+    c->bio_os   = NULL;
+    SSL_CTX_set_options(c->ctx, SSL_OP_ALL);
+    if (!(protocol & SSL_PROTOCOL_SSLV2))
+        SSL_CTX_set_options(c->ctx, SSL_OP_NO_SSLv2);
+    if (!(protocol & SSL_PROTOCOL_SSLV3))
+        SSL_CTX_set_options(c->ctx, SSL_OP_NO_SSLv3);
+    if (!(protocol & SSL_PROTOCOL_TLSV1))
+        SSL_CTX_set_options(c->ctx, SSL_OP_NO_TLSv1);
+#ifdef SSL_OP_NO_TLSv1_1
+    if (!(protocol & SSL_PROTOCOL_TLSV1_1))
+        SSL_CTX_set_options(c->ctx, SSL_OP_NO_TLSv1_1);
+#endif
+#ifdef SSL_OP_NO_TLSv1_2
+    if (!(protocol & SSL_PROTOCOL_TLSV1_2))
+        SSL_CTX_set_options(c->ctx, SSL_OP_NO_TLSv1_2);
+#endif
+    /*
+     * Configure additional context ingredients
+     */
+    SSL_CTX_set_options(c->ctx, SSL_OP_SINGLE_DH_USE);
+#ifdef HAVE_ECC
+    SSL_CTX_set_options(c->ctx, SSL_OP_SINGLE_ECDH_USE);
+#endif
+
+#ifdef SSL_OP_NO_COMPRESSION
+    SSL_CTX_set_options(c->ctx, SSL_OP_NO_COMPRESSION);
+#else
+#error "SSL_OP_NO_COMPRESSION not supported in your version of OpenSSL"
+#endif
+
+#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
+    /*
+     * Disallow a session from being resumed during a renegotiation,
+     * so that an acceptable cipher suite can be negotiated.
+     */
+    SSL_CTX_set_options(c->ctx, SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+#else
+#error "SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION not supported in your version of OpenSSL"
+#endif
+
+#ifdef SSL_MODE_RELEASE_BUFFERS
+    /* Release idle buffers to the SSL_CTX free list */
+    SSL_CTX_set_mode(c->ctx, SSL_MODE_RELEASE_BUFFERS);
+#else
+#error "SSL_MODE_RELEASE_BUFFERS not supported in your version of OpenSSL"
+#endif
+    /* Default session context id and cache size */
+    SSL_CTX_sess_set_cache_size(c->ctx, SSL_DEFAULT_CACHE_SIZE);
+
+    /* Session cache is disabled by default */
+    SSL_CTX_set_session_cache_mode(c->ctx, SSL_SESS_CACHE_OFF);
+    /* Longer session timeout */
+    SSL_CTX_set_timeout(c->ctx, 14400);
+    EVP_Digest((const unsigned char *)SSL_DEFAULT_VHOST_NAME,
+               (unsigned long)((sizeof SSL_DEFAULT_VHOST_NAME) - 1),
+               &(c->context_id[0]), NULL, EVP_sha1(), NULL);
+    if (mode) {
+#ifdef HAVE_ECC
+        /* Set default (nistp256) elliptic curve for ephemeral ECDH keys */
+        EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+        SSL_CTX_set_tmp_ecdh(c->ctx, ecdh);
+        EC_KEY_free(ecdh);
+#endif
+
+// This method should not be used when using libressl as it will result in
+// error:14085042:SSL routines:SSL3_CTX_CTRL:called a function you should not call
+//
+// See also http://forum.nginx.org/read.php?2,256381,257336#msg-257336
+#ifndef LIBRESSL_VERSION_NUMBER
+        SSL_CTX_set_tmp_rsa_callback(c->ctx, SSL_callback_tmp_RSA);
+#endif
+        SSL_CTX_set_tmp_dh_callback(c->ctx,  SSL_callback_tmp_DH);
+    }
+    /* Set default Certificate verification level
+     * and depth for the Client Authentication
+     */
+    c->verify_depth  = 1;
+    c->verify_mode   = SSL_CVERIFY_UNSET;
+    c->shutdown_type = SSL_SHUTDOWN_TYPE_UNSET;
+
+    /* Set default password callback */
+    SSL_CTX_set_default_passwd_cb(c->ctx, (pem_password_cb *)SSL_password_callback);
+    SSL_CTX_set_default_passwd_cb_userdata(c->ctx, (void *)(&tcn_password_callback));
+    SSL_CTX_set_info_callback(c->ctx, SSL_callback_handshake);
+
+    apr_thread_rwlock_create(&c->mutex, p);
+    /*
+     * Let us cleanup the ssl context when the pool is destroyed
+     */
+    apr_pool_cleanup_register(p, (const void *)c,
+                              ssl_context_cleanup,
+                              apr_pool_cleanup_null);
+
+
+    // Cache the byte[].class for performance reasons
+    clazz = (*e)->FindClass(e, "[B");
+    byteArrayClass = (jclass) (*e)->NewGlobalRef(e, clazz);
+
+    return P2J(c);
+init_failed:
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSLContext, free)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(ctx != 0);
+    /* Run and destroy the cleanup callback */
+    return apr_pool_cleanup_run(c->pool, c, ssl_context_cleanup);
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setContextId)(TCN_STDARGS, jlong ctx,
+                                                   jstring id)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    TCN_ALLOC_CSTRING(id);
+
+    TCN_ASSERT(ctx != 0);
+    UNREFERENCED(o);
+    if (J2S(id)) {
+        EVP_Digest((const unsigned char *)J2S(id),
+                   (unsigned long)strlen(J2S(id)),
+                   &(c->context_id[0]), NULL, EVP_sha1(), NULL);
+    }
+    TCN_FREE_CSTRING(id);
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setBIO)(TCN_STDARGS, jlong ctx,
+                                             jlong bio, jint dir)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    BIO *bio_handle   = J2P(bio, BIO *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(ctx != 0);
+    if (dir == 0) {
+        if (c->bio_os && c->bio_os != bio_handle)
+            SSL_BIO_close(c->bio_os);
+        c->bio_os = bio_handle;
+    }
+    else if (dir == 1) {
+        if (c->bio_is && c->bio_is != bio_handle)
+            SSL_BIO_close(c->bio_is);
+        c->bio_is = bio_handle;
+    }
+    else
+        return;
+    SSL_BIO_doref(bio_handle);
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setOptions)(TCN_STDARGS, jlong ctx,
+                                                 jint opt)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(ctx != 0);
+#ifndef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
+    /* Clear the flag if not supported */
+    if (opt & 0x00040000)
+        opt &= ~0x00040000;
+#endif
+    SSL_CTX_set_options(c->ctx, opt);
+}
+
+TCN_IMPLEMENT_CALL(jint, SSLContext, getOptions)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(ctx != 0);
+
+    return SSL_CTX_get_options(c->ctx);
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, clearOptions)(TCN_STDARGS, jlong ctx,
+                                                   jint opt)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(ctx != 0);
+    SSL_CTX_clear_options(c->ctx, opt);
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setQuietShutdown)(TCN_STDARGS, jlong ctx,
+                                                       jboolean mode)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(ctx != 0);
+    SSL_CTX_set_quiet_shutdown(c->ctx, mode ? 1 : 0);
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCipherSuite)(TCN_STDARGS, jlong ctx,
+                                                         jstring ciphers)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    TCN_ALLOC_CSTRING(ciphers);
+    jboolean rv = JNI_TRUE;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+    if (!J2S(ciphers))
+        return JNI_FALSE;
+
+    if (!SSL_CTX_set_cipher_list(c->ctx, J2S(ciphers))) {
+        char err[256];
+        ERR_error_string(ERR_get_error(), err);
+        tcn_Throw(e, "Unable to configure permitted SSL ciphers (%s)", err);
+        rv = JNI_FALSE;
+    }
+    TCN_FREE_CSTRING(ciphers);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCARevocation)(TCN_STDARGS, jlong ctx,
+                                                          jstring file,
+                                                          jstring path)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    TCN_ALLOC_CSTRING(file);
+    TCN_ALLOC_CSTRING(path);
+    jboolean rv = JNI_FALSE;
+    X509_LOOKUP *lookup;
+    char err[256];
+
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+    if (J2S(file) == NULL && J2S(path) == NULL)
+        return JNI_FALSE;
+
+    if (!c->crl) {
+        if ((c->crl = X509_STORE_new()) == NULL)
+            goto cleanup;
+    }
+    if (J2S(file)) {
+        lookup = X509_STORE_add_lookup(c->crl, X509_LOOKUP_file());
+        if (lookup == NULL) {
+            ERR_error_string(ERR_get_error(), err);
+            X509_STORE_free(c->crl);
+            c->crl = NULL;
+            tcn_Throw(e, "Lookup failed for file %s (%s)", J2S(file), err);
+            goto cleanup;
+        }
+        X509_LOOKUP_load_file(lookup, J2S(file), X509_FILETYPE_PEM);
+    }
+    if (J2S(path)) {
+        lookup = X509_STORE_add_lookup(c->crl, X509_LOOKUP_hash_dir());
+        if (lookup == NULL) {
+            ERR_error_string(ERR_get_error(), err);
+            X509_STORE_free(c->crl);
+            c->crl = NULL;
+            tcn_Throw(e, "Lookup failed for path %s (%s)", J2S(file), err);
+            goto cleanup;
+        }
+        X509_LOOKUP_add_dir(lookup, J2S(path), X509_FILETYPE_PEM);
+    }
+    rv = JNI_TRUE;
+cleanup:
+    TCN_FREE_CSTRING(file);
+    TCN_FREE_CSTRING(path);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificateChainFile)(TCN_STDARGS, jlong ctx,
+                                                                  jstring file,
+                                                                  jboolean skipfirst)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jboolean rv = JNI_FALSE;
+    TCN_ALLOC_CSTRING(file);
+
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+    if (!J2S(file))
+        return JNI_FALSE;
+    if (SSL_CTX_use_certificate_chain(c->ctx, J2S(file), skipfirst) > 0)
+        rv = JNI_TRUE;
+    TCN_FREE_CSTRING(file);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificateChainBio)(TCN_STDARGS, jlong ctx,
+                                                                  jlong chain,
+                                                                  jboolean skipfirst)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    BIO *b = J2P(chain, BIO *);
+
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+    if (b == NULL)
+        return JNI_FALSE;
+    if (SSL_CTX_use_certificate_chain_bio(c->ctx, b, skipfirst) > 0)  {
+        return JNI_TRUE;
+    }
+    return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCACertificate)(TCN_STDARGS,
+                                                           jlong ctx,
+                                                           jstring file,
+                                                           jstring path)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jboolean rv = JNI_TRUE;
+    TCN_ALLOC_CSTRING(file);
+    TCN_ALLOC_CSTRING(path);
+
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+    if (file == NULL && path == NULL)
+        return JNI_FALSE;
+
+   /*
+     * Configure Client Authentication details
+     */
+    if (!SSL_CTX_load_verify_locations(c->ctx,
+                                       J2S(file), J2S(path))) {
+        char err[256];
+        ERR_error_string(ERR_get_error(), err);
+        tcn_Throw(e, "Unable to configure locations "
+                  "for client authentication (%s)", err);
+        rv = JNI_FALSE;
+        goto cleanup;
+    }
+    c->store = SSL_CTX_get_cert_store(c->ctx);
+    if (c->mode) {
+        STACK_OF(X509_NAME) *ca_certs;
+        c->ca_certs++;
+        ca_certs = SSL_CTX_get_client_CA_list(c->ctx);
+        if (ca_certs == NULL) {
+            SSL_load_client_CA_file(J2S(file));
+            if (ca_certs != NULL)
+                SSL_CTX_set_client_CA_list(c->ctx, ca_certs);
+        }
+        else {
+            if (!SSL_add_file_cert_subjects_to_stack(ca_certs, J2S(file)))
+                ca_certs = NULL;
+        }
+        if (ca_certs == NULL && c->verify_mode == SSL_CVERIFY_REQUIRE) {
+            /*
+             * Give a warning when no CAs were configured but client authentication
+             * should take place. This cannot work.
+            */
+            if (c->bio_os) {
+                BIO_printf(c->bio_os,
+                           "[WARN] Oops, you want to request client "
+                           "authentication, but no CAs are known for "
+                           "verification!?");
+            }
+            else {
+                fprintf(stderr,
+                        "[WARN] Oops, you want to request client "
+                        "authentication, but no CAs are known for "
+                        "verification!?");
+            }
+        }
+    }
+cleanup:
+    TCN_FREE_CSTRING(file);
+    TCN_FREE_CSTRING(path);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setTmpDH)(TCN_STDARGS, jlong ctx,
+                                                                  jstring file)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    BIO *bio = NULL;
+    DH *dh = NULL;
+    TCN_ALLOC_CSTRING(file);
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+    TCN_ASSERT(file);
+
+    if (!J2S(file)) {
+        tcn_Throw(e, "Error while configuring DH: no dh param file given");
+        return;
+    }
+    
+    bio = BIO_new_file(J2S(file), "r");
+    if (!bio) {
+        char err[256];
+        ERR_error_string(ERR_get_error(), err);
+        tcn_Throw(e, "Error while configuring DH using %s: %s", J2S(file), err);
+        TCN_FREE_CSTRING(file);
+        return;
+    }
+
+    dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+    BIO_free(bio);
+    if (!dh) {
+        char err[256];
+        ERR_error_string(ERR_get_error(), err);
+        tcn_Throw(e, "Error while configuring DH: no DH parameter found in %s (%s)", J2S(file), err);
+        TCN_FREE_CSTRING(file);
+        return;
+    }
+
+    if (1 != SSL_CTX_set_tmp_dh(c->ctx, dh)) {
+        char err[256];
+        DH_free(dh);
+        ERR_error_string(ERR_get_error(), err);
+        tcn_Throw(e, "Error while configuring DH with file %s: %s", J2S(file), err);
+        TCN_FREE_CSTRING(file);
+        return;
+    }
+    
+    DH_free(dh);
+    TCN_FREE_CSTRING(file);
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setTmpECDHByCurveName)(TCN_STDARGS, jlong ctx,
+                                                                  jstring curveName)
+{
+#ifdef HAVE_ECC
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    int i;
+    EC_KEY  *ecdh;
+    TCN_ALLOC_CSTRING(curveName);
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+    TCN_ASSERT(curveName);
+    
+    // First try to get curve by name
+    i = OBJ_sn2nid(J2S(curveName));
+    if (!i) {
+        tcn_Throw(e, "Can't configure elliptic curve: unknown curve name %s", J2S(curveName));
+        TCN_FREE_CSTRING(curveName);
+        return;
+    }
+    
+    ecdh = EC_KEY_new_by_curve_name(i);
+    if (!ecdh) {
+        tcn_Throw(e, "Can't configure elliptic curve: unknown curve name %s", J2S(curveName));
+        TCN_FREE_CSTRING(curveName);
+        return;
+    }
+    
+    // Setting found curve to context
+    if (1 != SSL_CTX_set_tmp_ecdh(c->ctx, ecdh)) {
+        char err[256];
+        EC_KEY_free(ecdh);
+        ERR_error_string(ERR_get_error(), err);
+        tcn_Throw(e, "Error while configuring elliptic curve %s: %s", J2S(curveName), err);
+        TCN_FREE_CSTRING(curveName);
+        return;
+    }
+    EC_KEY_free(ecdh);
+    TCN_FREE_CSTRING(curveName);
+#else
+	tcn_Throw(e, "Cant't configure elliptic curve: unsupported by this OpenSSL version");
+	return;
+#endif
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setShutdownType)(TCN_STDARGS, jlong ctx,
+                                                      jint type)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(ctx != 0);
+    c->shutdown_type = type;
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setVerify)(TCN_STDARGS, jlong ctx,
+                                                jint level, jint depth)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    int verify = SSL_VERIFY_NONE;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+    c->verify_mode = level;
+
+    if (c->verify_mode == SSL_CVERIFY_UNSET)
+        c->verify_mode = SSL_CVERIFY_NONE;
+    if (depth > 0)
+        c->verify_depth = depth;
+    /*
+     *  Configure callbacks for SSL context
+     */
+    if (c->verify_mode == SSL_CVERIFY_REQUIRE)
+        verify |= SSL_VERIFY_PEER_STRICT;
+    if ((c->verify_mode == SSL_CVERIFY_OPTIONAL) ||
+        (c->verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
+        verify |= SSL_VERIFY_PEER;
+    if (!c->store) {
+        if (SSL_CTX_set_default_verify_paths(c->ctx)) {
+            c->store = SSL_CTX_get_cert_store(c->ctx);
+            X509_STORE_set_flags(c->store, 0);
+        }
+        else {
+            /* XXX: See if this is fatal */
+        }
+    }
+
+    SSL_CTX_set_verify(c->ctx, verify, SSL_callback_SSL_verify);
+}
+
+static EVP_PKEY *load_pem_key(tcn_ssl_ctxt_t *c, const char *file)
+{
+    BIO *bio = NULL;
+    EVP_PKEY *key = NULL;
+    tcn_pass_cb_t *cb_data = c->cb_data;
+    int i;
+
+    if ((bio = BIO_new(BIO_s_file())) == NULL) {
+        return NULL;
+    }
+    if (BIO_read_filename(bio, file) <= 0) {
+        BIO_free(bio);
+        return NULL;
+    }
+    if (!cb_data)
+        cb_data = &tcn_password_callback;
+    for (i = 0; i < 3; i++) {
+        key = PEM_read_bio_PrivateKey(bio, NULL,
+                    (pem_password_cb *)SSL_password_callback,
+                    (void *)cb_data);
+        if (key != NULL)
+            break;
+        cb_data->password[0] = '\0';
+        BIO_ctrl(bio, BIO_CTRL_RESET, 0, NULL);
+    }
+    BIO_free(bio);
+    return key;
+}
+
+static EVP_PKEY *load_pem_key_bio(tcn_ssl_ctxt_t *c, const BIO *bio)
+{
+    EVP_PKEY *key = NULL;
+    tcn_pass_cb_t *cb_data = c->cb_data;
+    int i;
+
+    if (cb_data == NULL)
+        cb_data = &tcn_password_callback;
+    for (i = 0; i < 3; i++) {
+        key = PEM_read_bio_PrivateKey((BIO*) bio, NULL,
+                    (pem_password_cb *)SSL_password_callback,
+                    (void *)cb_data);
+        if (key)
+            break;
+        cb_data->password[0] = '\0';
+        BIO_ctrl((BIO*) bio, BIO_CTRL_RESET, 0, NULL);
+    }
+    return key;
+}
+
+static X509 *load_pem_cert(tcn_ssl_ctxt_t *c, const char *file)
+{
+    BIO *bio = NULL;
+    X509 *cert = NULL;
+    tcn_pass_cb_t *cb_data = c->cb_data;
+
+    if ((bio = BIO_new(BIO_s_file())) == NULL) {
+        return NULL;
+    }
+    if (BIO_read_filename(bio, file) <= 0) {
+        BIO_free(bio);
+        return NULL;
+    }
+    if (!cb_data)
+        cb_data = &tcn_password_callback;
+    cert = PEM_read_bio_X509_AUX(bio, NULL,
+                (pem_password_cb *)SSL_password_callback,
+                (void *)cb_data);
+    if (cert == NULL &&
+       (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE)) {
+        ERR_clear_error();
+        BIO_ctrl(bio, BIO_CTRL_RESET, 0, NULL);
+        cert = d2i_X509_bio(bio, NULL);
+    }
+    BIO_free(bio);
+    return cert;
+}
+
+static X509 *load_pem_cert_bio(tcn_ssl_ctxt_t *c, const BIO *bio)
+{
+    X509 *cert = NULL;
+    tcn_pass_cb_t *cb_data = c->cb_data;
+
+    if (cb_data == NULL)
+        cb_data = &tcn_password_callback;
+    cert = PEM_read_bio_X509_AUX((BIO*) bio, NULL,
+                (pem_password_cb *)SSL_password_callback,
+                (void *)cb_data);
+    if (cert == NULL &&
+       (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE)) {
+        ERR_clear_error();
+        BIO_ctrl((BIO*) bio, BIO_CTRL_RESET, 0, NULL);
+        cert = d2i_X509_bio((BIO*) bio, NULL);
+    }
+    return cert;
+}
+
+static int ssl_load_pkcs12(tcn_ssl_ctxt_t *c, const char *file,
+                           EVP_PKEY **pkey, X509 **cert, STACK_OF(X509) **ca)
+{
+    const char *pass;
+    char        buff[PEM_BUFSIZE];
+    int         len, rc = 0;
+    PKCS12     *p12;
+    BIO        *in;
+    tcn_pass_cb_t *cb_data = c->cb_data;
+
+    if ((in = BIO_new(BIO_s_file())) == 0)
+        return 0;
+    if (BIO_read_filename(in, file) <= 0) {
+        BIO_free(in);
+        return 0;
+    }
+    p12 = d2i_PKCS12_bio(in, 0);
+    if (p12 == 0) {
+        /* Error loading PKCS12 file */
+        goto cleanup;
+    }
+    /* See if an empty password will do */
+    if (PKCS12_verify_mac(p12, "", 0) || PKCS12_verify_mac(p12, 0, 0)) {
+        pass = "";
+    }
+    else {
+        if (!cb_data)
+            cb_data = &tcn_password_callback;
+        len = SSL_password_callback(buff, PEM_BUFSIZE, 0, cb_data);
+        if (len < 0) {
+            /* Passpharse callback error */
+            goto cleanup;
+        }
+        if (!PKCS12_verify_mac(p12, buff, len)) {
+            /* Mac verify error (wrong password?) in PKCS12 file */
+            goto cleanup;
+        }
+        pass = buff;
+    }
+    rc = PKCS12_parse(p12, pass, pkey, cert, ca);
+cleanup:
+    if (p12 != 0)
+        PKCS12_free(p12);
+    BIO_free(in);
+    return rc;
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setRandom)(TCN_STDARGS, jlong ctx,
+                                                jstring file)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    TCN_ALLOC_CSTRING(file);
+
+    TCN_ASSERT(ctx != 0);
+    UNREFERENCED(o);
+    if (J2S(file))
+        c->rand_file = apr_pstrdup(c->pool, J2S(file));
+    TCN_FREE_CSTRING(file);
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificate)(TCN_STDARGS, jlong ctx,
+                                                         jstring cert, jstring key,
+                                                         jstring password, jint idx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jboolean rv = JNI_TRUE;
+    TCN_ALLOC_CSTRING(cert);
+    TCN_ALLOC_CSTRING(key);
+    TCN_ALLOC_CSTRING(password);
+    const char *key_file, *cert_file;
+    const char *p;
+    char err[256];
+
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+
+    if (idx < 0 || idx >= SSL_AIDX_MAX) {
+        /* TODO: Throw something */
+        rv = JNI_FALSE;
+        goto cleanup;
+    }
+    if (J2S(password)) {
+        if (!c->cb_data)
+            c->cb_data = &tcn_password_callback;
+        strncpy(c->cb_data->password, J2S(password), SSL_MAX_PASSWORD_LEN);
+        c->cb_data->password[SSL_MAX_PASSWORD_LEN-1] = '\0';
+    }
+    key_file  = J2S(key);
+    cert_file = J2S(cert);
+    if (!key_file)
+        key_file = cert_file;
+    if (!key_file || !cert_file) {
+        tcn_Throw(e, "No Certificate file specified or invalid file format");
+        rv = JNI_FALSE;
+        goto cleanup;
+    }
+    if ((p = strrchr(cert_file, '.')) != NULL && strcmp(p, ".pkcs12") == 0) {
+        if (!ssl_load_pkcs12(c, cert_file, &c->keys[idx], &c->certs[idx], 0)) {
+            ERR_error_string(ERR_get_error(), err);
+            tcn_Throw(e, "Unable to load certificate %s (%s)",
+                      cert_file, err);
+            rv = JNI_FALSE;
+            goto cleanup;
+        }
+    }
+    else {
+        if ((c->keys[idx] = load_pem_key(c, key_file)) == NULL) {
+            ERR_error_string(ERR_get_error(), err);
+            tcn_Throw(e, "Unable to load certificate key %s (%s)",
+                      key_file, err);
+            rv = JNI_FALSE;
+            goto cleanup;
+        }
+        if ((c->certs[idx] = load_pem_cert(c, cert_file)) == NULL) {
+            ERR_error_string(ERR_get_error(), err);
+            tcn_Throw(e, "Unable to load certificate %s (%s)",
+                      cert_file, err);
+            rv = JNI_FALSE;
+            goto cleanup;
+        }
+    }
+    if (SSL_CTX_use_certificate(c->ctx, c->certs[idx]) <= 0) {
+        ERR_error_string(ERR_get_error(), err);
+        tcn_Throw(e, "Error setting certificate (%s)", err);
+        rv = JNI_FALSE;
+        goto cleanup;
+    }
+    if (SSL_CTX_use_PrivateKey(c->ctx, c->keys[idx]) <= 0) {
+        ERR_error_string(ERR_get_error(), err);
+        tcn_Throw(e, "Error setting private key (%s)", err);
+        rv = JNI_FALSE;
+        goto cleanup;
+    }
+    if (SSL_CTX_check_private_key(c->ctx) <= 0) {
+        ERR_error_string(ERR_get_error(), err);
+        tcn_Throw(e, "Private key does not match the certificate public key (%s)",
+                  err);
+        rv = JNI_FALSE;
+        goto cleanup;
+    }
+cleanup:
+    TCN_FREE_CSTRING(cert);
+    TCN_FREE_CSTRING(key);
+    TCN_FREE_CSTRING(password);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificateBio)(TCN_STDARGS, jlong ctx,
+                                                         jlong cert, jlong key,
+                                                         jstring password, jint idx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    BIO *cert_bio = J2P(cert, BIO *);
+    BIO *key_bio = J2P(key, BIO *);
+
+    jboolean rv = JNI_TRUE;
+    TCN_ALLOC_CSTRING(password);
+    char err[256];
+
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+
+    if (idx < 0 || idx >= SSL_AIDX_MAX) {
+        /* TODO: Throw something */
+        rv = JNI_FALSE;
+        goto cleanup;
+    }
+    if (J2S(password)) {
+        if (!c->cb_data)
+            c->cb_data = &tcn_password_callback;
+        strncpy(c->cb_data->password, J2S(password), SSL_MAX_PASSWORD_LEN);
+        c->cb_data->password[SSL_MAX_PASSWORD_LEN-1] = '\0';
+    }
+    if (!key)
+        key = cert;
+    if (!cert || !key) {
+        tcn_Throw(e, "No Certificate file specified or invalid file format");
+        rv = JNI_FALSE;
+        goto cleanup;
+    }
+
+    if ((c->keys[idx] = load_pem_key_bio(c, key_bio)) == NULL) {
+        ERR_error_string(ERR_get_error(), err);
+        ERR_clear_error();
+        tcn_Throw(e, "Unable to load certificate key (%s)",err);
+        rv = JNI_FALSE;
+        goto cleanup;
+    }
+    if ((c->certs[idx] = load_pem_cert_bio(c, cert_bio)) == NULL) {
+        ERR_error_string(ERR_get_error(), err);
+        ERR_clear_error();
+        tcn_Throw(e, "Unable to load certificate (%s) ", err);
+        rv = JNI_FALSE;
+        goto cleanup;
+    }
+
+    if (SSL_CTX_use_certificate(c->ctx, c->certs[idx]) <= 0) {
+        ERR_error_string(ERR_get_error(), err);
+        ERR_clear_error();
+        tcn_Throw(e, "Error setting certificate (%s)", err);
+        rv = JNI_FALSE;
+        goto cleanup;
+    }
+    if (SSL_CTX_use_PrivateKey(c->ctx, c->keys[idx]) <= 0) {
+        ERR_error_string(ERR_get_error(), err);
+        ERR_clear_error();
+        tcn_Throw(e, "Error setting private key (%s)", err);
+        rv = JNI_FALSE;
+        goto cleanup;
+    }
+    if (SSL_CTX_check_private_key(c->ctx) <= 0) {
+        ERR_error_string(ERR_get_error(), err);
+        ERR_clear_error();
+
+        tcn_Throw(e, "Private key does not match the certificate public key (%s)",
+                  err);
+        rv = JNI_FALSE;
+        goto cleanup;
+    }
+cleanup:
+    TCN_FREE_CSTRING(password);
+    return rv;
+}
+
+
+// Convert protos to wire format
+static int initProtocols(JNIEnv *e, unsigned char **proto_data,
+            unsigned int *proto_len, jobjectArray protos) {
+    int i;
+    unsigned char *p_data;
+    // We start with allocate 128 bytes which should be good enough for most use-cases while still be pretty low.
+    // We will call realloc to increate this if needed.
+    size_t p_data_size = 128;
+    size_t p_data_len = 0;
+    jstring proto_string;
+    const char *proto_chars;
+    size_t proto_chars_len;
+    int cnt;
+
+    if (protos == NULL) {
+        // Guard against NULL protos.
+        return -1;
+    }
+
+    cnt = (*e)->GetArrayLength(e, protos);
+
+    if (cnt == 0) {
+        // if cnt is 0 we not need to continue and can just fail fast.
+        return -1;
+    }
+
+    p_data = (unsigned char *) malloc(p_data_size);
+    if (p_data == NULL) {
+        // Not enough memory?
+        return -1;
+    }
+
+    for (i = 0; i < cnt; ++i) {
+         proto_string = (jstring) (*e)->GetObjectArrayElement(e, protos, i);
+         proto_chars = (*e)->GetStringUTFChars(e, proto_string, 0);
+
+         proto_chars_len = strlen(proto_chars);
+         if (proto_chars_len > 0 && proto_chars_len <= MAX_ALPN_NPN_PROTO_SIZE) {
+            // We need to add +1 as each protocol is prefixed by it's length (unsigned char).
+            // For all except of the last one we already have the extra space as everything is
+            // delimited by ','.
+            p_data_len += 1 + proto_chars_len;
+            if (p_data_len > p_data_size) {
+                // double size
+                p_data_size <<= 1;
+                p_data = realloc(p_data, p_data_size);
+                if (p_data == NULL) {
+                    // Not enough memory?
+                    (*e)->ReleaseStringUTFChars(e, proto_string, proto_chars);
+                    break;
+                }
+            }
+            // Write the length of the protocol and then increment before memcpy the protocol itself.
+            *p_data = proto_chars_len;
+            ++p_data;
+            memcpy(p_data, proto_chars, proto_chars_len);
+            p_data += proto_chars_len;
+         }
+
+         // Release the string to prevent memory leaks
+         (*e)->ReleaseStringUTFChars(e, proto_string, proto_chars);
+    }
+
+    if (p_data == NULL) {
+        // Something went wrong so update the proto_len and return -1
+        *proto_len = 0;
+        return -1;
+    } else {
+        if (*proto_data != NULL) {
+            // Free old data
+            free(*proto_data);
+        }
+        // Decrement pointer again as we incremented it while creating the protocols in wire format.
+        p_data -= p_data_len;
+        *proto_data = p_data;
+        *proto_len = p_data_len;
+        return 0;
+    }
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setNpnProtos)(TCN_STDARGS, jlong ctx, jobjectArray next_protos,
+        jint selectorFailureBehavior)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+
+    TCN_ASSERT(ctx != 0);
+    UNREFERENCED(o);
+
+    if (initProtocols(e, &c->next_proto_data, &c->next_proto_len, next_protos) == 0) {
+        c->next_selector_failure_behavior = selectorFailureBehavior;
+
+        // depending on if it's client mode or not we need to call different functions.
+        if (c->mode == SSL_MODE_CLIENT)  {
+            SSL_CTX_set_next_proto_select_cb(c->ctx, SSL_callback_select_next_proto, (void *)c);
+        } else {
+            SSL_CTX_set_next_protos_advertised_cb(c->ctx, SSL_callback_next_protos, (void *)c);
+        }
+    }
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setAlpnProtos)(TCN_STDARGS, jlong ctx, jobjectArray alpn_protos,
+        jint selectorFailureBehavior)
+{
+    // Only supported with GCC
+    #if defined(__GNUC__) || defined(__GNUG__)
+        if (!SSL_CTX_set_alpn_protos || !SSL_CTX_set_alpn_select_cb) {
+            UNREFERENCED_STDARGS;
+            UNREFERENCED(ctx);
+            UNREFERENCED(alpn_protos);
+            return;
+        }
+    #endif
+
+    // We can only support it when either use openssl version >= 1.0.2 or GCC as this way we can use weak linking
+    #if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined(__GNUC__) || defined(__GNUG__)
+        tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+
+        TCN_ASSERT(ctx != 0);
+        UNREFERENCED(o);
+
+        if (initProtocols(e, &c->alpn_proto_data, &c->alpn_proto_len, alpn_protos) == 0) {
+            c->alpn_selector_failure_behavior = selectorFailureBehavior;
+
+            // depending on if it's client mode or not we need to call different functions.
+            if (c->mode == SSL_MODE_CLIENT)  {
+                SSL_CTX_set_alpn_protos(c->ctx, c->alpn_proto_data, c->alpn_proto_len);
+            } else {
+                SSL_CTX_set_alpn_select_cb(c->ctx, SSL_callback_alpn_select_proto, (void *) c);
+
+            }
+        }
+    #else
+        UNREFERENCED_STDARGS;
+        UNREFERENCED(ctx);
+        UNREFERENCED(alpn_protos);
+    #endif
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, setSessionCacheMode)(TCN_STDARGS, jlong ctx, jlong mode)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    return SSL_CTX_set_session_cache_mode(c->ctx, mode);
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, getSessionCacheMode)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    return SSL_CTX_get_session_cache_mode(c->ctx);
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, setSessionCacheTimeout)(TCN_STDARGS, jlong ctx, jlong timeout)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = SSL_CTX_set_timeout(c->ctx, timeout);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, getSessionCacheTimeout)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    return SSL_CTX_get_timeout(c->ctx);
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, setSessionCacheSize)(TCN_STDARGS, jlong ctx, jlong size)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = 0;
+
+    // Also allow size of 0 which is unlimited
+    if (size >= 0) {
+      SSL_CTX_set_session_cache_mode(c->ctx, SSL_SESS_CACHE_SERVER);
+      rv = SSL_CTX_sess_set_cache_size(c->ctx, size);
+    }
+
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, getSessionCacheSize)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    return SSL_CTX_sess_get_cache_size(c->ctx);
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionNumber)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = SSL_CTX_sess_number(c->ctx);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionConnect)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = SSL_CTX_sess_connect(c->ctx);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionConnectGood)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = SSL_CTX_sess_connect_good(c->ctx);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionConnectRenegotiate)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = SSL_CTX_sess_connect_renegotiate(c->ctx);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionAccept)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = SSL_CTX_sess_accept(c->ctx);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionAcceptGood)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = SSL_CTX_sess_accept_good(c->ctx);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionAcceptRenegotiate)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = SSL_CTX_sess_accept_renegotiate(c->ctx);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionHits)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = SSL_CTX_sess_hits(c->ctx);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionCbHits)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = SSL_CTX_sess_cb_hits(c->ctx);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionMisses)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = SSL_CTX_sess_misses(c->ctx);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionTimeouts)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = SSL_CTX_sess_timeouts(c->ctx);
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionCacheFull)(TCN_STDARGS, jlong ctx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jlong rv = SSL_CTX_sess_cache_full(c->ctx);
+    return rv;
+}
+
+static int current_session_key(tcn_ssl_ctxt_t *c, tcn_ssl_ticket_key_t *key) {
+    int result = JNI_FALSE;
+    apr_thread_rwlock_rdlock(c->mutex);
+    if (c->ticket_keys_len > 0) {
+        *key = c->ticket_keys[0];
+        result = JNI_TRUE;
+    }
+    apr_thread_rwlock_unlock(c->mutex);
+    return result;
+}
+
+static int find_session_key(tcn_ssl_ctxt_t *c, unsigned char key_name[16], tcn_ssl_ticket_key_t *key, int *is_current_key) {
+    int result = JNI_FALSE;
+    int i;
+
+    apr_thread_rwlock_rdlock(c->mutex);
+    for (i = 0; i < c->ticket_keys_len; ++i) {
+        // Check if we have a match for tickets.
+        if (memcmp(c->ticket_keys[i].key_name, key_name, 16) == 0) {
+            *key = c->ticket_keys[i];
+            result = JNI_TRUE;
+            *is_current_key = (i == 0);
+            break;
+        }
+    }
+    apr_thread_rwlock_unlock(c->mutex);
+    return result;
+}
+
+static int ssl_tlsext_ticket_key_cb(SSL *s, unsigned char key_name[16], unsigned char *iv, EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int enc) {
+    tcn_ssl_ctxt_t *c = SSL_get_app_data2(s);
+    tcn_ssl_ticket_key_t key;
+    int is_current_key;
+
+    if (enc) { /* create new session */
+        if (current_session_key(c, &key)) {
+            if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0) {
+                return -1; /* insufficient random */
+            }
+
+            memcpy(key_name, key.key_name, 16);
+
+            EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key.aes_key, iv);
+            HMAC_Init_ex(hctx, key.hmac_key, 16, EVP_sha256(), NULL);
+            return 1;
+        }
+        // No ticket configured
+        return 0;
+    } else { /* retrieve session */
+        if (find_session_key(c, key_name, &key, &is_current_key)) {
+            HMAC_Init_ex(hctx, key.hmac_key, 16, EVP_sha256(), NULL);
+            EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key.aes_key, iv );
+            if (!is_current_key) {
+                return 2;
+            }
+            return 1;
+        }
+        // No ticket
+        return 0;
+    }
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setSessionTicketKeys0)(TCN_STDARGS, jlong ctx, jbyteArray keys)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    jbyte* b;
+    jbyte* key;
+    tcn_ssl_ticket_key_t* ticket_keys;
+    int i;
+    int cnt;
+
+    cnt = (*e)->GetArrayLength(e, keys) / SSL_SESSION_TICKET_KEY_SIZE;
+    b = (*e)->GetByteArrayElements(e, keys, NULL);
+
+    ticket_keys = malloc(sizeof(tcn_ssl_ticket_key_t) * cnt);
+
+    for (i = 0; i < cnt; ++i) {
+        key = b + (SSL_SESSION_TICKET_KEY_SIZE * i);
+        memcpy(ticket_keys[i].key_name, key, 16);
+        memcpy(ticket_keys[i].hmac_key, key + 16, 16);
+        memcpy(ticket_keys[i].aes_key, key + 32, 16);
+    }
+
+    (*e)->ReleaseByteArrayElements(e, keys, b, 0);
+
+    apr_thread_rwlock_wrlock(c->mutex);
+    if (c->ticket_keys) {
+        free(c->ticket_keys);
+    }
+    c->ticket_keys_len = cnt;
+    c->ticket_keys = ticket_keys;
+    apr_thread_rwlock_unlock(c->mutex);
+
+    SSL_CTX_set_tlsext_ticket_key_cb(c->ctx, ssl_tlsext_ticket_key_cb);
+}
+
+
+/*
+ * Adapted from OpenSSL:
+ * http://osxr.org/openssl/source/ssl/ssl_locl.h#0291
+ */
+/* Bits for algorithm_mkey (key exchange algorithm) */
+#define SSL_kRSA        0x00000001L /* RSA key exchange */
+#define SSL_kDHr        0x00000002L /* DH cert, RSA CA cert */ /* no such ciphersuites supported! */
+#define SSL_kDHd        0x00000004L /* DH cert, DSA CA cert */ /* no such ciphersuite supported! */
+#define SSL_kEDH        0x00000008L /* tmp DH key no DH cert */
+#define SSL_kKRB5       0x00000010L /* Kerberos5 key exchange */
+#define SSL_kECDHr      0x00000020L /* ECDH cert, RSA CA cert */
+#define SSL_kECDHe      0x00000040L /* ECDH cert, ECDSA CA cert */
+#define SSL_kEECDH      0x00000080L /* ephemeral ECDH */
+#define SSL_kPSK        0x00000100L /* PSK */
+#define SSL_kGOST       0x00000200L /* GOST key exchange */
+#define SSL_kSRP        0x00000400L /* SRP */
+
+/* Bits for algorithm_auth (server authentication) */
+#define SSL_aRSA        0x00000001L /* RSA auth */
+#define SSL_aDSS        0x00000002L /* DSS auth */
+#define SSL_aNULL       0x00000004L /* no auth (i.e. use ADH or AECDH) */
+#define SSL_aDH         0x00000008L /* Fixed DH auth (kDHd or kDHr) */ /* no such ciphersuites supported! */
+#define SSL_aECDH       0x00000010L /* Fixed ECDH auth (kECDHe or kECDHr) */
+#define SSL_aKRB5       0x00000020L /* KRB5 auth */
+#define SSL_aECDSA      0x00000040L /* ECDSA auth*/
+#define SSL_aPSK        0x00000080L /* PSK auth */
+#define SSL_aGOST94     0x00000100L /* GOST R 34.10-94 signature auth */
+#define SSL_aGOST01     0x00000200L /* GOST R 34.10-2001 signature auth */
+
+/* OpenSSL end */
+
+/*
+ * Adapted from Android:
+ * https://android.googlesource.com/platform/external/openssl/+/master/patches/0003-jsse.patch
+ */
+const char* cipher_authentication_method(const SSL_CIPHER* cipher){
+#ifndef OPENSSL_IS_BORINGSSL
+    switch (cipher->algorithm_mkey)
+        {
+    case SSL_kRSA:
+        return SSL_TXT_RSA;
+    case SSL_kDHr:
+        return SSL_TXT_DH "_" SSL_TXT_RSA;
+
+    case SSL_kDHd:
+        return SSL_TXT_DH "_" SSL_TXT_DSS;
+    case SSL_kEDH:
+        switch (cipher->algorithm_auth)
+            {
+        case SSL_aDSS:
+            return "DHE_" SSL_TXT_DSS;
+        case SSL_aRSA:
+            return "DHE_" SSL_TXT_RSA;
+        case SSL_aNULL:
+            return SSL_TXT_DH "_anon";
+        default:
+            return "UNKNOWN";
+            }
+    case SSL_kKRB5:
+        return SSL_TXT_KRB5;
+    case SSL_kECDHr:
+        return SSL_TXT_ECDH "_" SSL_TXT_RSA;
+    case SSL_kECDHe:
+        return SSL_TXT_ECDH "_" SSL_TXT_ECDSA;
+    case SSL_kEECDH:
+        switch (cipher->algorithm_auth)
+            {
+        case SSL_aECDSA:
+            return "ECDHE_" SSL_TXT_ECDSA;
+        case SSL_aRSA:
+            return "ECDHE_" SSL_TXT_RSA;
+        case SSL_aNULL:
+            return SSL_TXT_ECDH "_anon";
+        default:
+            return "UNKNOWN";
+            }
+    default:
+        return "UNKNOWN";
+    }
+#else
+    return SSL_CIPHER_get_kx_name(cipher);
+#endif
+
+}
+
+static const char* authentication_method(const SSL* ssl) {
+{
+    switch (ssl->version)
+        {
+        case SSL2_VERSION:
+            return SSL_TXT_RSA;
+        default:
+            return cipher_authentication_method(ssl->s3->tmp.new_cipher);
+        }
+    }
+}
+/* Android end */
+
+static int SSL_cert_verify(X509_STORE_CTX *ctx, void *arg) {
+    /* Get Apache context back through OpenSSL context */
+    SSL *ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+    tcn_ssl_ctxt_t *c = SSL_get_app_data2(ssl);
+
+
+    // Get a stack of all certs in the chain
+    STACK_OF(X509) *sk = ctx->untrusted;
+
+    int len = sk_num((_STACK*) sk);
+    unsigned i;
+    X509 *cert;
+    int length;
+    unsigned char *buf;
+    JNIEnv *e;
+    jbyteArray array;
+    jbyteArray bArray;
+    const char *authMethod;
+    jstring authMethodString;
+    jboolean result;
+    int r;
+    tcn_get_java_env(&e);
+
+    // Create the byte[][] array that holds all the certs
+    array = (*e)->NewObjectArray(e, len, byteArrayClass, NULL);
+
+    for(i = 0; i < len; i++) {
+        cert = (X509*) sk_value((_STACK*) sk, i);
+
+        buf = NULL;
+        length = i2d_X509(cert, &buf);
+        if (length < 0) {
+            // In case of error just return an empty byte[][]
+            array = (*e)->NewObjectArray(e, 0, byteArrayClass, NULL);
+            // We need to delete the local references so we not leak memory as this method is called via callback.
+            OPENSSL_free(buf);
+            break;
+        }
+        bArray = (*e)->NewByteArray(e, length);
+        (*e)->SetByteArrayRegion(e, bArray, 0, length, (jbyte*) buf);
+        (*e)->SetObjectArrayElement(e, array, i, bArray);
+
+        // Delete the local reference as we not know how long the chain is and local references are otherwise
+        // only freed once jni method returns.
+        (*e)->DeleteLocalRef(e, bArray);
+        OPENSSL_free(buf);
+    }
+
+    authMethod = authentication_method(ssl);
+    authMethodString = (*e)->NewStringUTF(e, authMethod);
+
+    result = (*e)->CallBooleanMethod(e, c->verifier, c->verifier_method, P2J(ssl), array,
+            authMethodString);
+
+    r = result == JNI_TRUE ? 1 : 0;
+
+    // We need to delete the local references so we not leak memory as this method is called via callback.
+    (*e)->DeleteLocalRef(e, authMethodString);
+    (*e)->DeleteLocalRef(e, array);
+    return r;
+}
+
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setCertVerifyCallback)(TCN_STDARGS, jlong ctx, jobject verifier)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+
+    if (verifier == NULL) {
+        SSL_CTX_set_cert_verify_callback(c->ctx, NULL, NULL);
+    } else {
+        jclass verifier_class = (*e)->GetObjectClass(e, verifier);
+        jmethodID method = (*e)->GetMethodID(e, verifier_class, "verify", "(J[[BLjava/lang/String;)Z");
+
+        if (method == NULL) {
+            return;
+        }
+        // Delete the reference to the previous specified verifier if needed.
+        if (c->verifier != NULL) {
+            (*e)->DeleteLocalRef(e, c->verifier);
+        }
+        c->verifier = (*e)->NewGlobalRef(e, verifier);
+        c->verifier_method = method;
+
+        SSL_CTX_set_cert_verify_callback(c->ctx, SSL_cert_verify, NULL);
+    }
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setSessionIdContext)(TCN_STDARGS, jlong ctx, jbyteArray sidCtx)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    int len = (*e)->GetArrayLength(e, sidCtx);
+    unsigned char *buf;
+    int res;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+
+    buf = malloc(len);
+
+    (*e)->GetByteArrayRegion(e, sidCtx, 0, len, (jbyte*) buf);
+
+    res = SSL_CTX_set_session_id_context(c->ctx, buf, len);
+    free(buf);
+
+    if (res == 1) {
+        return JNI_TRUE;
+    }
+    return JNI_FALSE;
+}
+#else
+/* OpenSSL is not supported.
+ * Create empty stubs.
+ */
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, make)(TCN_STDARGS, jlong pool,
+                                            jint protocol, jint mode)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(pool);
+    UNREFERENCED(protocol);
+    UNREFERENCED(mode);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSLContext, free)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return APR_ENOTIMPL;
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setContextId)(TCN_STDARGS, jlong ctx,
+                                                   jstring id)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(id);
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setBIO)(TCN_STDARGS, jlong ctx,
+                                             jlong bio, jint dir)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(bio);
+    UNREFERENCED(dir);
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setOptions)(TCN_STDARGS, jlong ctx,
+                                                 jint opt)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(opt);
+}
+
+TCN_IMPLEMENT_CALL(jint, SSLContext, getOptions)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, clearOptions)(TCN_STDARGS, jlong ctx,
+                                                   jint opt)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(opt);
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setQuietShutdown)(TCN_STDARGS, jlong ctx,
+                                                       jboolean mode)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(mode);
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCipherSuite)(TCN_STDARGS, jlong ctx,
+                                                         jstring ciphers)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(ciphers);
+    return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCARevocation)(TCN_STDARGS, jlong ctx,
+                                                          jstring file,
+                                                          jstring path)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(file);
+    UNREFERENCED(path);
+    return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificateChainFile)(TCN_STDARGS, jlong ctx,
+                                                                  jstring file,
+                                                                  jboolean skipfirst)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(file);
+    UNREFERENCED(skipfirst);
+    return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificateChainBio)(TCN_STDARGS, jlong ctx,
+                                                                  jlong chain,
+                                                                  jboolean skipfirst)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(chain);
+    UNREFERENCED(skipfirst);
+    return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCACertificate)(TCN_STDARGS,
+                                                           jlong ctx,
+                                                           jstring file,
+                                                           jstring path)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(file);
+    UNREFERENCED(path);
+    return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setShutdownType)(TCN_STDARGS, jlong ctx,
+                                                      jint type)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(type);
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setVerify)(TCN_STDARGS, jlong ctx,
+                                                jint level, jint depth)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(level);
+    UNREFERENCED(depth);
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setRandom)(TCN_STDARGS, jlong ctx,
+                                                jstring file)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(file);
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificate)(TCN_STDARGS, jlong ctx,
+                                                         jstring cert, jstring key,
+                                                         jstring password, jint idx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(cert);
+    UNREFERENCED(key);
+    UNREFERENCED(password);
+    UNREFERENCED(idx);
+    return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificateBio)(TCN_STDARGS, jlong ctx,
+                                                         jlong cert, jlong key,
+                                                         jstring password, jint idx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(cert);
+    UNREFERENCED(key);
+    UNREFERENCED(password);
+    UNREFERENCED(idx);
+    return JNI_FALSE;
+}
+TCN_IMPLEMENT_CALL(void, SSLContext, setNpnProtos)(TCN_STDARGS, jlong ctx, jobjectArray next_protos,
+        jint selectorFailureBehavior)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(next_protos);
+}
+
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setAlpnProtos)(TCN_STDARGS, jlong ctx, jobjectArray alpn_protos,
+        jint selectorFailureBehavior)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(alpn_protos);
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, setSessionCacheMode)(TCN_STDARGS, jlong ctx, jlong mode)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(mode);
+    return -1;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, getSessionCacheMode)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return -1;
+}
+
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, setSessionCacheTimeout)(TCN_STDARGS, jlong ctx, jlong timeout)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(timeout);
+    return -1;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, getSessionCacheTimeout)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return -1;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, setSessionCacheSize)(TCN_STDARGS, jlong ctx, jlong size)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(size);
+    return -1;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, getSessionCacheSize)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return -1;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionNumber)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionConnect)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionConnectGood)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionConnectRenegotiate)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionAccept)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionAcceptGood)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionAcceptRenegotiate)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionHits)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionCbHits)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionTimeouts)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionCacheFull)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionMisses)(TCN_STDARGS, jlong ctx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    return 0;
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setSessionTicketKeys0)(TCN_STDARGS, jlong ctx, jbyteArray keys)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(keys);
+}
+
+TCN_IMPLEMENT_CALL(void, SSLContext, setCertVerifyCallback)(TCN_STDARGS, jlong ctx, jobject verifier)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(verifier);
+}
+TCN_IMPLEMENT_CALL(jboolean, SSLContext, setSessionIdContext)(TCN_STDARGS, jlong ctx, jbyteArray sidCtx)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(sidCtx);
+    return JNI_FALSE;
+}
+#endif
diff --git a/c/sslinfo.c b/c/sslinfo.c
new file mode 100644
index 0000000..0ee3829
--- /dev/null
+++ b/c/sslinfo.c
@@ -0,0 +1,591 @@
+/* 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.
+ */
+
+/** SSL info wrapper
+ *
+ * @author Mladen Turk
+ * @version $Id: sslinfo.c 1658603 2015-02-09 23:26:44Z kkolinko $
+ */
+
+#include "tcn.h"
+#include "apr_file_io.h"
+#include "apr_thread_mutex.h"
+#include "apr_poll.h"
+
+#ifdef HAVE_OPENSSL
+#include "ssl_private.h"
+
+static const char *hex_basis = "0123456789ABCDEF";
+
+static char *convert_to_hex(const void *buf, size_t len)
+{
+    const unsigned char *p = ( const unsigned char *)buf;
+    char *str, *s;
+    size_t i;
+
+    if ((len < 1) || ((str = malloc(len * 2 + 1)) == NULL))
+        return NULL;
+    for (i = 0, s = str; i < len; i++) {
+        unsigned char c = *p++;
+        *s++ = hex_basis[c >> 4];
+        *s++ = hex_basis[c & 0x0F];
+    }
+    *s = '\0';
+    return str;
+}
+
+#define DIGIT2NUM(x) (((x)[0] - '0') * 10 + (x)[1] - '0')
+
+static int get_days_remaining(ASN1_UTCTIME *tm)
+{
+    apr_time_t then, now = apr_time_now();
+    apr_time_exp_t exp = {0};
+    int diff;
+
+    /* Fail if the time isn't a valid ASN.1 UTCTIME; RFC3280 mandates
+     * that the seconds digits are present even though ASN.1
+     * doesn't. */
+    if (tm->length < 11 || !ASN1_UTCTIME_check(tm))
+        return 0;
+
+    exp.tm_year = DIGIT2NUM(tm->data);
+    exp.tm_mon  = DIGIT2NUM(tm->data + 2) - 1;
+    exp.tm_mday = DIGIT2NUM(tm->data + 4) + 1;
+    exp.tm_hour = DIGIT2NUM(tm->data + 6);
+    exp.tm_min  = DIGIT2NUM(tm->data + 8);
+    exp.tm_sec  = DIGIT2NUM(tm->data + 10);
+
+    if (exp.tm_year <= 50)
+        exp.tm_year += 100;
+    if (apr_time_exp_gmt_get(&then, &exp) != APR_SUCCESS)
+        return 0;
+
+    diff = (int)((apr_time_sec(then) - apr_time_sec(now)) / (60*60*24));
+    return diff > 0 ? diff : 0;
+}
+
+static char *get_cert_valid(ASN1_UTCTIME *tm)
+{
+    char *result;
+    BIO* bio;
+    int n;
+
+    if ((bio = BIO_new(BIO_s_mem())) == NULL)
+        return NULL;
+    ASN1_UTCTIME_print(bio, tm);
+    n = BIO_pending(bio);
+    result = malloc(n+1);
+    n = BIO_read(bio, result, n);
+    result[n] = '\0';
+    BIO_free(bio);
+    return result;
+}
+
+static char *get_cert_PEM(X509 *xs)
+{
+    char *result = NULL;
+    BIO *bio;
+
+    if ((bio = BIO_new(BIO_s_mem())) == NULL)
+        return NULL;
+    if (PEM_write_bio_X509(bio, xs)) {
+        int n = BIO_pending(bio);
+        result = malloc(n+1);
+        n = BIO_read(bio, result, n);
+        result[n] = '\0';
+    }
+    BIO_free(bio);
+    return result;
+}
+
+static unsigned char *get_cert_ASN1(X509 *xs, int *len)
+{
+    unsigned char *result = NULL;
+    BIO *bio;
+
+    *len = 0;
+    if ((bio = BIO_new(BIO_s_mem())) == NULL)
+        return NULL;
+    if (i2d_X509_bio(bio, xs)) {
+        int n = BIO_pending(bio);
+        result = malloc(n);
+        n = BIO_read(bio, result, n);
+        *len = n;
+    }
+    BIO_free(bio);
+    return result;
+}
+
+
+static char *get_cert_serial(X509 *xs)
+{
+    char *result;
+    BIO *bio;
+    int n;
+
+    if ((bio = BIO_new(BIO_s_mem())) == NULL)
+        return NULL;
+    i2a_ASN1_INTEGER(bio, X509_get_serialNumber(xs));
+    n = BIO_pending(bio);
+    result = malloc(n+1);
+    n = BIO_read(bio, result, n);
+    result[n] = '\0';
+    BIO_free(bio);
+    return result;
+}
+
+static const struct {
+    int   fid;
+    int   nid;
+} info_cert_dn_rec[] = {
+    { SSL_INFO_DN_COUNTRYNAME,            NID_countryName            },
+    { SSL_INFO_DN_STATEORPROVINCENAME,    NID_stateOrProvinceName    },
+    { SSL_INFO_DN_LOCALITYNAME,           NID_localityName           },
+    { SSL_INFO_DN_ORGANIZATIONNAME,       NID_organizationName       },
+    { SSL_INFO_DN_ORGANIZATIONALUNITNAME, NID_organizationalUnitName },
+    { SSL_INFO_DN_COMMONNAME,             NID_commonName             },
+    { SSL_INFO_DN_TITLE,                  NID_title                  },
+    { SSL_INFO_DN_INITIALS,               NID_initials               },
+    { SSL_INFO_DN_GIVENNAME,              NID_givenName              },
+    { SSL_INFO_DN_SURNAME,                NID_surname                },
+    { SSL_INFO_DN_DESCRIPTION,            NID_description            },
+    { SSL_INFO_DN_UNIQUEIDENTIFIER,       NID_x500UniqueIdentifier   },
+    { SSL_INFO_DN_EMAILADDRESS,           NID_pkcs9_emailAddress     },
+    { 0,                                  0                          }
+};
+
+static char *lookup_ssl_cert_dn(X509_NAME *xsname, int dnidx)
+{
+    char *result;
+    X509_NAME_ENTRY *xsne;
+    int i, j, n, idx = 0;
+
+    result = NULL;
+
+    for (i = 0; info_cert_dn_rec[i].fid != 0; i++) {
+        if (info_cert_dn_rec[i].fid == dnidx) {
+            for (j = 0; j < sk_X509_NAME_ENTRY_num((STACK_OF(X509_NAME_ENTRY) *)
+                                                   (xsname->entries)); j++) {
+                xsne = sk_X509_NAME_ENTRY_value((STACK_OF(X509_NAME_ENTRY) *)
+                                                (xsname->entries), j);
+
+                n =OBJ_obj2nid((ASN1_OBJECT *)X509_NAME_ENTRY_get_object(xsne));
+                if (n == info_cert_dn_rec[i].nid && idx-- == 0) {
+                    result = malloc(xsne->value->length + 1);
+                    memcpy(result, xsne->value->data,
+                                   xsne->value->length);
+                    result[xsne->value->length] = '\0';
+
+#if APR_CHARSET_EBCDIC
+                    ap_xlate_proto_from_ascii(result, xsne->value->length);
+#endif /* APR_CHARSET_EBCDIC */
+                    break;
+                }
+            }
+            break;
+        }
+    }
+    return result;
+}
+
+TCN_IMPLEMENT_CALL(jobject, SSLSocket, getInfoB)(TCN_STDARGS, jlong sock,
+                                                 jint what)
+{
+    tcn_socket_t   *a = J2P(sock, tcn_socket_t *);
+    tcn_ssl_conn_t *s;
+    jbyteArray array = NULL;
+    apr_status_t rv = APR_SUCCESS;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+
+    s = (tcn_ssl_conn_t *)(a->opaque);
+    switch (what) {
+        case SSL_INFO_SESSION_ID:
+        {
+            SSL_SESSION *session  = SSL_get_session(s->ssl);
+            if (session) {
+                unsigned int len;
+                const unsigned char *id = SSL_SESSION_get_id(session, &len);
+                array = tcn_new_arrayb(e, id, len);
+            }
+        }
+        break;
+        default:
+            rv = APR_EINVAL;
+        break;
+    }
+    if (what & SSL_INFO_CLIENT_MASK) {
+        X509 *xs;
+        unsigned char *result;
+        int len;
+        if ((xs = SSL_get_peer_certificate(s->ssl)) != NULL) {
+            switch (what) {
+                case SSL_INFO_CLIENT_CERT:
+                    if ((result = get_cert_ASN1(xs, &len))) {
+                        array = tcn_new_arrayb(e, result, len);
+                        free(result);
+                    }
+                break;
+            }
+            X509_free(xs);
+        }
+        rv = APR_SUCCESS;
+    }
+    else if (what & SSL_INFO_SERVER_MASK) {
+        X509 *xs;
+        unsigned char *result;
+        int len;
+        if ((xs = SSL_get_certificate(s->ssl)) != NULL) {
+            switch (what) {
+                case SSL_INFO_SERVER_CERT:
+                    if ((result = get_cert_ASN1(xs, &len))) {
+                        array = tcn_new_arrayb(e, result, len);
+                        free(result);
+                    }
+                break;
+            }
+            /* XXX: No need to call the X509_free(xs); */
+        }
+        rv = APR_SUCCESS;
+    }
+    else if (what & SSL_INFO_CLIENT_CERT_CHAIN) {
+        X509 *xs;
+        unsigned char *result;
+        STACK_OF(X509) *sk =  SSL_get_peer_cert_chain(s->ssl);
+        int len, n = what & 0x0F;
+        if (n < sk_X509_num(sk)) {
+            xs = sk_X509_value(sk, n);
+            if ((result = get_cert_ASN1(xs, &len))) {
+                array = tcn_new_arrayb(e, result, len);
+                free(result);
+            }
+        }
+        rv = APR_SUCCESS;
+    }
+    if (rv != APR_SUCCESS)
+        tcn_ThrowAPRException(e, rv);
+
+    return array;
+}
+
+TCN_IMPLEMENT_CALL(jstring, SSLSocket, getInfoS)(TCN_STDARGS, jlong sock,
+                                                 jint what)
+{
+    tcn_socket_t   *a = J2P(sock, tcn_socket_t *);
+    tcn_ssl_conn_t *s;
+    jstring value = NULL;
+    apr_status_t rv = APR_SUCCESS;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+
+    s = (tcn_ssl_conn_t *)(a->opaque);
+    switch (what) {
+        case SSL_INFO_SESSION_ID:
+        {
+            SSL_SESSION *session  = SSL_get_session(s->ssl);
+            if (session) {
+                unsigned int len;
+                const unsigned char *id = SSL_SESSION_get_id(session, &len);
+                char *hs = convert_to_hex(id, len);
+                if (hs) {
+                    value = tcn_new_string(e, hs);
+                    free(hs);
+                }
+            }
+        }
+        break;
+        case SSL_INFO_PROTOCOL:
+            value = tcn_new_string(e, SSL_get_version(s->ssl));
+        break;
+        case SSL_INFO_CIPHER:
+            value = tcn_new_string(e, SSL_get_cipher_name(s->ssl));
+        break;
+        case SSL_INFO_CIPHER_VERSION:
+            value = tcn_new_string(e, SSL_get_cipher_version(s->ssl));
+        break;
+        case SSL_INFO_CIPHER_DESCRIPTION:
+            {
+                SSL_CIPHER *cipher = (SSL_CIPHER *)SSL_get_current_cipher(s->ssl);
+                if (cipher) {
+                    char buf[256];
+                    const char *desc = SSL_CIPHER_description(cipher, buf, 256);
+                    value = tcn_new_string(e, desc);
+                }
+            }
+        break;
+        default:
+            rv = APR_EINVAL;
+        break;
+    }
+    if (what & (SSL_INFO_CLIENT_S_DN | SSL_INFO_CLIENT_I_DN)) {
+        X509 *xs;
+        X509_NAME *xsname;
+        if ((xs = SSL_get_peer_certificate(s->ssl)) != NULL) {
+            char *result;
+            int idx = what & 0x0F;
+            if (what & SSL_INFO_CLIENT_S_DN)
+                xsname = X509_get_subject_name(xs);
+            else
+                xsname = X509_get_issuer_name(xs);
+            if (idx) {
+                result = lookup_ssl_cert_dn(xsname, idx);
+                if (result) {
+                    value = tcn_new_string(e, result);
+                    free(result);
+                }
+            }
+            else
+                value = tcn_new_string(e, X509_NAME_oneline(xsname, NULL, 0));
+            X509_free(xs);
+        }
+        rv = APR_SUCCESS;
+    }
+    else if (what & (SSL_INFO_SERVER_S_DN | SSL_INFO_SERVER_I_DN)) {
+        X509 *xs;
+        X509_NAME *xsname;
+        if ((xs = SSL_get_certificate(s->ssl)) != NULL) {
+            char *result;
+            int idx = what & 0x0F;
+            if (what & SSL_INFO_SERVER_S_DN)
+                xsname = X509_get_subject_name(xs);
+            else
+                xsname = X509_get_issuer_name(xs);
+            if (idx) {
+                result = lookup_ssl_cert_dn(xsname, what & 0x0F);
+                if (result) {
+                    value = tcn_new_string(e, result);
+                    free(result);
+                }
+            }
+            else
+                value = tcn_new_string(e, X509_NAME_oneline(xsname, NULL, 0));
+            /* XXX: No need to call the X509_free(xs); */
+        }
+        rv = APR_SUCCESS;
+    }
+    else if (what & SSL_INFO_CLIENT_MASK) {
+        X509 *xs;
+        char *result;
+        int nid;
+        if ((xs = SSL_get_peer_certificate(s->ssl)) != NULL) {
+            switch (what) {
+                case SSL_INFO_CLIENT_V_START:
+                    if ((result = get_cert_valid(X509_get_notBefore(xs)))) {
+                        value = tcn_new_string(e, result);
+                        free(result);
+                    }
+                break;
+                case SSL_INFO_CLIENT_V_END:
+                    if ((result = get_cert_valid(X509_get_notAfter(xs)))) {
+                        value = tcn_new_string(e, result);
+                        free(result);
+                    }
+                break;
+                case SSL_INFO_CLIENT_A_SIG:
+                    nid = OBJ_obj2nid((ASN1_OBJECT *)xs->cert_info->signature->algorithm);
+                    if (nid == NID_undef)
+                        value = tcn_new_string(e, "UNKNOWN");
+                    else
+                        value = tcn_new_string(e, OBJ_nid2ln(nid));
+                break;
+                case SSL_INFO_CLIENT_A_KEY:
+                    nid = OBJ_obj2nid((ASN1_OBJECT *)xs->cert_info->key->algor->algorithm);
+                    if (nid == NID_undef)
+                        value = tcn_new_string(e, "UNKNOWN");
+                    else
+                        value = tcn_new_string(e, OBJ_nid2ln(nid));
+                break;
+                case SSL_INFO_CLIENT_CERT:
+                    if ((result = get_cert_PEM(xs))) {
+                        value = tcn_new_string(e, result);
+                        free(result);
+                    }
+                break;
+                case SSL_INFO_CLIENT_M_SERIAL:
+                    if ((result = get_cert_serial(xs))) {
+                        value = tcn_new_string(e, result);
+                        free(result);
+                    }
+                break;
+            }
+            X509_free(xs);
+        }
+        rv = APR_SUCCESS;
+    }
+    else if (what & SSL_INFO_SERVER_MASK) {
+        X509 *xs;
+        char *result;
+        int nid;
+        if ((xs = SSL_get_certificate(s->ssl)) != NULL) {
+            switch (what) {
+                case SSL_INFO_SERVER_V_START:
+                    if ((result = get_cert_valid(X509_get_notBefore(xs)))) {
+                        value = tcn_new_string(e, result);
+                        free(result);
+                    }
+                break;
+                case SSL_INFO_SERVER_V_END:
+                    if ((result = get_cert_valid(X509_get_notAfter(xs)))) {
+                        value = tcn_new_string(e, result);
+                        free(result);
+                    }
+                break;
+                case SSL_INFO_SERVER_A_SIG:
+                    nid = OBJ_obj2nid((ASN1_OBJECT *)xs->cert_info->signature->algorithm);
+                    if (nid == NID_undef)
+                        value = tcn_new_string(e, "UNKNOWN");
+                    else
+                        value = tcn_new_string(e, OBJ_nid2ln(nid));
+                break;
+                case SSL_INFO_SERVER_A_KEY:
+                    nid = OBJ_obj2nid((ASN1_OBJECT *)xs->cert_info->key->algor->algorithm);
+                    if (nid == NID_undef)
+                        value = tcn_new_string(e, "UNKNOWN");
+                    else
+                        value = tcn_new_string(e, OBJ_nid2ln(nid));
+                break;
+                case SSL_INFO_SERVER_CERT:
+                    if ((result = get_cert_PEM(xs))) {
+                        value = tcn_new_string(e, result);
+                        free(result);
+                    }
+                break;
+                case SSL_INFO_SERVER_M_SERIAL:
+                    if ((result = get_cert_serial(xs))) {
+                        value = tcn_new_string(e, result);
+                        free(result);
+                    }
+                break;
+            }
+            /* XXX: No need to call the X509_free(xs); */
+        }
+        rv = APR_SUCCESS;
+    }
+    else if (what & SSL_INFO_CLIENT_CERT_CHAIN) {
+        X509 *xs;
+        char *result;
+        STACK_OF(X509) *sk =  SSL_get_peer_cert_chain(s->ssl);
+        int n = what & 0x0F;
+        if (n < sk_X509_num(sk)) {
+            xs = sk_X509_value(sk, n);
+            if ((result = get_cert_PEM(xs))) {
+                value = tcn_new_string(e, result);
+                free(result);
+            }
+        }
+        rv = APR_SUCCESS;
+    }
+    if (rv != APR_SUCCESS)
+        tcn_ThrowAPRException(e, rv);
+
+    return value;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSLSocket, getInfoI)(TCN_STDARGS, jlong sock,
+                                              jint what)
+{
+    tcn_socket_t   *a = J2P(sock, tcn_socket_t *);
+    tcn_ssl_conn_t *s;
+    apr_status_t rv = APR_SUCCESS;
+    jint value = -1;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(sock != 0);
+
+    s = (tcn_ssl_conn_t *)(a->opaque);
+
+    switch (what) {
+        case SSL_INFO_CIPHER_USEKEYSIZE:
+        case SSL_INFO_CIPHER_ALGKEYSIZE:
+        {
+            int usekeysize = 0;
+            int algkeysize = 0;
+            const SSL_CIPHER *cipher = SSL_get_current_cipher(s->ssl);
+            if (cipher) {
+                usekeysize = SSL_CIPHER_get_bits(cipher, &algkeysize);
+                if (what == SSL_INFO_CIPHER_USEKEYSIZE)
+                    value = usekeysize;
+                else
+                    value = algkeysize;
+            }
+        }
+        break;
+        case SSL_INFO_CLIENT_CERT_CHAIN:
+        {
+            STACK_OF(X509) *sk =  SSL_get_peer_cert_chain(s->ssl);
+            value = sk_X509_num(sk);
+        }
+        break;
+        default:
+            rv = APR_EINVAL;
+        break;
+    }
+    if (what & SSL_INFO_CLIENT_MASK) {
+        X509 *xs;
+        if ((xs = SSL_get_peer_certificate(s->ssl)) != NULL) {
+            switch (what) {
+                case SSL_INFO_CLIENT_V_REMAIN:
+                    value = get_days_remaining(X509_get_notAfter(xs));
+                    rv = APR_SUCCESS;
+                break;
+                default:
+                    rv = APR_EINVAL;
+                break;
+           }
+           X509_free(xs);
+        }
+    }
+
+    if (rv != APR_SUCCESS)
+        tcn_ThrowAPRException(e, rv);
+    return value;
+}
+
+#else
+/* OpenSSL is not supported.
+ * Create empty stubs.
+ */
+
+TCN_IMPLEMENT_CALL(jobject, SSLSocket, getInfoB)(TCN_STDARGS, jlong sock,
+                                                 jint what)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(sock);
+    UNREFERENCED(what);
+    return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jstring, SSLSocket, getInfoS)(TCN_STDARGS, jlong sock,
+                                                 jint what)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(sock);
+    UNREFERENCED(what);
+    return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSLSocket, getInfoI)(TCN_STDARGS, jlong sock,
+                                              jint what)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(sock);
+    UNREFERENCED(what);
+    return 0;
+}
+
+#endif
diff --git a/c/sslnetwork.c b/c/sslnetwork.c
new file mode 100644
index 0000000..e092e28
--- /dev/null
+++ b/c/sslnetwork.c
@@ -0,0 +1,736 @@
+/* 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.
+ */
+
+/** SSL network wrapper
+ *
+ * @author Mladen Turk
+ * @version $Id: sslnetwork.c 1658646 2015-02-10 09:00:11Z rjung $
+ */
+
+#include "tcn.h"
+#include "apr_thread_mutex.h"
+#include "apr_poll.h"
+
+
+#ifdef HAVE_OPENSSL
+#include "ssl_private.h"
+
+#ifdef TCN_DO_STATISTICS
+#include "apr_atomic.h"
+
+static volatile apr_uint32_t ssl_created  = 0;
+static volatile apr_uint32_t ssl_closed   = 0;
+static volatile apr_uint32_t ssl_cleared  = 0;
+static volatile apr_uint32_t ssl_accepted = 0;
+
+void ssl_network_dump_statistics()
+{
+    fprintf(stderr, "SSL Network Statistics ..\n");
+    fprintf(stderr, "Sockets created         : %d\n", ssl_created);
+    fprintf(stderr, "Sockets accepted        : %d\n", ssl_accepted);
+    fprintf(stderr, "Sockets closed          : %d\n", ssl_closed);
+    fprintf(stderr, "Sockets cleared         : %d\n", ssl_cleared);
+}
+
+#endif
+
+static int ssl_smart_shutdown(SSL *ssl, int shutdown_type)
+{
+    int i;
+    int rc = 0;
+
+    switch (shutdown_type) {
+        case SSL_SHUTDOWN_TYPE_UNCLEAN:
+            /* perform no close notify handshake at all
+             * (violates the SSL/TLS standard!)
+             */
+            shutdown_type = SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN;
+        break;
+        case SSL_SHUTDOWN_TYPE_ACCURATE:
+            /* send close notify and wait for clients close notify
+             * (standard compliant, but usually causes connection hangs)
+             */
+            shutdown_type = 0;
+        break;
+        default:
+            /*
+             * case SSL_SHUTDOWN_TYPE_UNSET:
+             * case SSL_SHUTDOWN_TYPE_STANDARD:
+             * send close notify, but don't wait for clients close notify
+             * (standard compliant and safe, so it's the DEFAULT!)
+             */
+            shutdown_type = SSL_RECEIVED_SHUTDOWN;
+        break;
+    }
+
+    SSL_set_shutdown(ssl, shutdown_type);
+    /*
+     * Repeat the calls, because SSL_shutdown internally dispatches through a
+     * little state machine. Usually only one or two interation should be
+     * needed, so we restrict the total number of restrictions in order to
+     * avoid process hangs in case the client played bad with the socket
+     * connection and OpenSSL cannot recognize it.
+     *  max 2x pending + 2x data = 4
+     */
+    for (i = 0; i < 4; i++) {
+        if ((rc = SSL_shutdown(ssl)))
+            break;
+    }
+    return rc;
+}
+
+static apr_status_t ssl_cleanup(void *data)
+{
+    tcn_ssl_conn_t *con = (tcn_ssl_conn_t *)data;
+
+    if (con) {
+        /* Pollset was already destroyed by
+         * the pool cleanup/destroy.
+         */
+        con->pollset = NULL;
+        if (con->ssl) {
+            SSL *ssl = con->ssl;
+            con->ssl = NULL;
+            ssl_smart_shutdown(ssl, con->shutdown_type);
+            SSL_free(ssl);
+        }
+        if (con->peer) {
+            X509_free(con->peer);
+            con->peer = NULL;
+        }
+    }
+
+#ifdef TCN_DO_STATISTICS
+    apr_atomic_inc32(&ssl_cleared);
+#endif
+    return APR_SUCCESS;
+}
+
+static tcn_ssl_conn_t *ssl_create(JNIEnv *env, tcn_ssl_ctxt_t *ctx, apr_pool_t *pool)
+{
+    tcn_ssl_conn_t *con;
+    SSL *ssl;
+
+    if ((con = apr_pcalloc(pool, sizeof(tcn_ssl_conn_t))) == NULL) {
+        tcn_ThrowAPRException(env, apr_get_os_error());
+        return NULL;
+    }
+    if ((ssl = SSL_new(ctx->ctx)) == NULL) {
+        char err[256];
+        ERR_error_string(ERR_get_error(), err);
+        tcn_Throw(env, "SSL_new failed (%s)", err);
+        con = NULL;
+        return NULL;
+    }
+    SSL_clear(ssl);
+    con->pool = pool;
+    con->ctx  = ctx;
+    con->ssl  = ssl;
+    con->shutdown_type = ctx->shutdown_type;
+    apr_pollset_create(&(con->pollset), 1, pool, 0);
+
+    SSL_set_app_data(ssl, (void *)con);
+
+    // Store for later usage in SSL_callback_SSL_verify
+    SSL_set_app_data2(ssl, ctx);
+
+    if (ctx->mode) {
+        /*
+         *  Configure callbacks for SSL connection
+         */
+        SSL_set_tmp_rsa_callback(ssl, SSL_callback_tmp_RSA);
+        SSL_set_tmp_dh_callback(ssl,  SSL_callback_tmp_DH);
+        SSL_set_session_id_context(ssl, &(ctx->context_id[0]),
+                                   sizeof ctx->context_id);
+    }
+    SSL_set_verify_result(ssl, X509_V_OK);
+    SSL_rand_seed(ctx->rand_file);
+
+#ifdef TCN_DO_STATISTICS
+    ssl_created++;
+#endif
+    return con;
+}
+
+#ifdef WIN32
+#define APR_INVALID_SOCKET  INVALID_SOCKET
+#else
+#define APR_INVALID_SOCKET  -1
+#endif
+
+static apr_status_t wait_for_io_or_timeout(tcn_ssl_conn_t *con,
+                                           int for_what,
+                                           apr_interval_time_t timeout)
+{
+    apr_pollfd_t pfd;
+    int type;
+    apr_status_t status;
+    apr_os_sock_t sock;
+
+    if (!con->pollset)
+        return APR_ENOPOLL;
+    if (!con->sock)
+        return APR_ENOTSOCK;
+    if (con->reneg_state == RENEG_ABORT) {
+        con->shutdown_type = SSL_SHUTDOWN_TYPE_UNCLEAN;
+        return APR_ECONNABORTED;
+    }
+
+    /* Check if the socket was already closed
+     */
+    apr_os_sock_get(&sock, con->sock);
+    if (sock == APR_INVALID_SOCKET)
+        return APR_ENOTSOCK;
+
+    /* Figure out the the poll direction */
+    switch (for_what) {
+        case SSL_ERROR_WANT_WRITE:
+        case SSL_ERROR_WANT_CONNECT:
+        case SSL_ERROR_WANT_ACCEPT:
+            type = APR_POLLOUT;
+        break;
+        case SSL_ERROR_WANT_READ:
+            type = APR_POLLIN;
+        break;
+        default:
+            return APR_EINVAL;
+        break;
+    }
+    if (timeout <= 0) {
+        /* Waiting on zero or infinite timeouts is not allowed
+         */
+        return APR_EAGAIN;
+    }
+    pfd.desc_type = APR_POLL_SOCKET;
+    pfd.desc.s    = con->sock;
+    pfd.reqevents = type;
+
+    /* Remove the object if it was in the pollset, then add in the new
+     * object with the correct reqevents value. Ignore the status result
+     * on the remove, because it might not be in there (yet).
+     */
+    apr_pollset_remove(con->pollset, &pfd);
+
+    /* ### check status code */
+    apr_pollset_add(con->pollset, &pfd);
+
+    do {
+        int numdesc;
+        const apr_pollfd_t *pdesc;
+
+        status = apr_pollset_poll(con->pollset, timeout, &numdesc, &pdesc);
+        if (numdesc == 1 && (pdesc[0].rtnevents & type) != 0)
+            return APR_SUCCESS;
+    } while (APR_STATUS_IS_EINTR(status));
+
+    return status;
+}
+
+static apr_status_t APR_THREAD_FUNC
+ssl_socket_timeout_set(apr_socket_t *sock, apr_interval_time_t t)
+{
+    tcn_ssl_conn_t *con = (tcn_ssl_conn_t *)sock;
+    return apr_socket_timeout_set(con->sock, t);
+}
+
+static apr_status_t APR_THREAD_FUNC
+ssl_socket_timeout_get(apr_socket_t *sock, apr_interval_time_t *t)
+{
+    tcn_ssl_conn_t *con = (tcn_ssl_conn_t *)sock;
+    return apr_socket_timeout_get(con->sock, t);
+}
+
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+ssl_socket_opt_set(apr_socket_t *sock, apr_int32_t opt, apr_int32_t on)
+{
+    tcn_ssl_conn_t *con = (tcn_ssl_conn_t *)sock;
+    return apr_socket_opt_set(con->sock, opt, on);
+}
+
+static APR_INLINE apr_status_t APR_THREAD_FUNC
+ssl_socket_opt_get(apr_socket_t *sock, apr_int32_t opt, apr_int32_t *on)
+{
+    tcn_ssl_conn_t *con = (tcn_ssl_conn_t *)sock;
+    return apr_socket_opt_get(con->sock, opt, on);
+}
+
+static apr_status_t APR_THREAD_FUNC
+ssl_socket_shutdown(apr_socket_t *sock, apr_shutdown_how_e how)
+{
+    apr_status_t rv = APR_SUCCESS;
+    tcn_ssl_conn_t *con = (tcn_ssl_conn_t *)sock;
+
+    if (con->ssl) {
+        SSL *ssl = con->ssl;
+        con->ssl = NULL;
+        if (how < 1)
+            how = con->shutdown_type;
+        rv = ssl_smart_shutdown(ssl, how);
+        /* TODO: Translate OpenSSL Error codes */
+        SSL_free(ssl);
+    }
+    return rv;
+}
+
+static apr_status_t APR_THREAD_FUNC
+ssl_socket_close(apr_socket_t *sock)
+{
+    tcn_ssl_conn_t *con = (tcn_ssl_conn_t *)sock;
+    apr_status_t rv = APR_SUCCESS;
+
+#ifdef TCN_DO_STATISTICS
+    apr_atomic_inc32(&ssl_closed);
+#endif
+    if (con->ssl) {
+        SSL *ssl = con->ssl;
+        con->ssl = NULL;
+        rv = ssl_smart_shutdown(ssl, con->shutdown_type);
+        SSL_free(ssl);
+    }
+    if (con->peer) {
+        X509_free(con->peer);
+        con->peer = NULL;
+    }
+    return rv;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSLSocket, handshake)(TCN_STDARGS, jlong sock)
+{
+    tcn_socket_t *ss = J2P(sock, tcn_socket_t *);
+    tcn_ssl_conn_t *con;
+    apr_interval_time_t timeout;
+    int s, i;
+    long vr;
+    apr_status_t rv;
+    X509 *peer;
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(sock != 0);
+    if (ss->net->type != TCN_SOCKET_SSL)
+        return APR_EINVAL;
+    con = (tcn_ssl_conn_t *)ss->opaque;
+
+    apr_socket_timeout_get(con->sock, &timeout);
+    while (!SSL_is_init_finished(con->ssl)) {
+        ERR_clear_error();
+        if ((s = SSL_do_handshake(con->ssl)) <= 0) {
+            if (!con->ssl)
+                return APR_ENOTSOCK;
+            rv = apr_get_netos_error();
+            i  = SSL_get_error(con->ssl, s);
+            switch (i) {
+                case SSL_ERROR_NONE:
+                    con->shutdown_type = SSL_SHUTDOWN_TYPE_STANDARD;
+                    return APR_SUCCESS;
+                break;
+                case SSL_ERROR_WANT_READ:
+                case SSL_ERROR_WANT_WRITE:
+                    if ((rv = wait_for_io_or_timeout(con, i, timeout)) != APR_SUCCESS) {
+                        con->shutdown_type = SSL_SHUTDOWN_TYPE_UNCLEAN;
+                        return rv;
+                    }
+                break;
+                case SSL_ERROR_SYSCALL:
+#if !defined(_WIN32)
+                      if (APR_STATUS_IS_EINTR(rv)) {
+                          /* Interrupted by signal */
+                          continue;
+                      }
+#endif
+                    /* Fall trough */
+                default:
+                    /*
+                     * Anything else is a fatal error
+                     */
+                    con->shutdown_type = SSL_SHUTDOWN_TYPE_UNCLEAN;
+                    return SSL_TO_APR_ERROR(i);
+                break;
+            }
+        }
+        if (!con->ssl)
+            return APR_ENOTSOCK;
+
+        /*
+        * Check for failed client authentication
+        */
+        if ((vr = SSL_get_verify_result(con->ssl)) != X509_V_OK) {
+            if (SSL_VERIFY_ERROR_IS_OPTIONAL(vr) &&
+                con->ctx->verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA) {
+                /* TODO: Log optionalNoCA */
+            }
+            else {
+                /* TODO: Log SSL client authentication failed */
+                con->shutdown_type = SSL_SHUTDOWN_TYPE_UNCLEAN;
+                /* TODO: Figure out the correct return value */
+                return APR_EGENERAL;
+            }
+        }
+
+        /*
+         * Remember the peer certificate
+         */
+        if ((peer = SSL_get_peer_certificate(con->ssl)) != NULL) {
+            if (con->peer)
+                X509_free(con->peer);
+            con->peer = peer;
+        }
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t APR_THREAD_FUNC
+ssl_socket_recv(apr_socket_t *sock, char *buf, apr_size_t *len)
+{
+    tcn_ssl_conn_t *con = (tcn_ssl_conn_t *)sock;
+    int s, i, rd = (int)(*len);
+    apr_status_t rv;
+    apr_interval_time_t timeout;
+
+    *len = 0;
+    if (con->reneg_state == RENEG_ABORT) {
+        con->shutdown_type = SSL_SHUTDOWN_TYPE_UNCLEAN;
+        return APR_ECONNABORTED;
+    }
+    apr_socket_timeout_get(con->sock, &timeout);
+    for (;;) {
+        ERR_clear_error();
+        if ((s = SSL_read(con->ssl, buf, rd)) <= 0) {
+            if (!con->ssl)
+                return APR_ENOTSOCK;
+            rv  = apr_get_netos_error();
+            i   = SSL_get_error(con->ssl, s);
+            /* Special case if the "close notify" alert send by peer */
+            if (s == 0 && (SSL_get_shutdown(con->ssl) & SSL_RECEIVED_SHUTDOWN)) {
+                con->shutdown_type = SSL_SHUTDOWN_TYPE_STANDARD;
+                return APR_EOF;
+            }
+            switch (i) {
+                case SSL_ERROR_WANT_READ:
+                case SSL_ERROR_WANT_WRITE:
+                    if ((rv = wait_for_io_or_timeout(con, i, timeout)) != APR_SUCCESS) {
+                        con->shutdown_type = SSL_SHUTDOWN_TYPE_UNCLEAN;
+                        return rv;
+                    }
+                break;
+                case SSL_ERROR_SYSCALL:
+                    if (APR_STATUS_IS_EPIPE(rv) || APR_STATUS_IS_ECONNRESET(rv)) {
+                        con->shutdown_type = SSL_SHUTDOWN_TYPE_STANDARD;
+                        return APR_EOF;
+                    }
+#if !defined(_WIN32)
+                    else if (APR_STATUS_IS_EINTR(rv)) {
+                        /* Interrupted by signal
+                         */
+                        continue;
+                    }
+#endif
+                    /* Fall trough */
+                case SSL_ERROR_ZERO_RETURN:
+                    if (s == 0) {
+                        con->shutdown_type = SSL_SHUTDOWN_TYPE_STANDARD;
+                        return APR_EOF;
+                    }
+                    /* Fall trough */
+                default:
+                    con->shutdown_type = SSL_SHUTDOWN_TYPE_UNCLEAN;
+                    return APR_EGENERAL;
+                break;
+            }
+        }
+        else {
+            *len = s;
+            con->shutdown_type = SSL_SHUTDOWN_TYPE_STANDARD;
+            break;
+        }
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t APR_THREAD_FUNC
+ssl_socket_send(apr_socket_t *sock, const char *buf,
+                apr_size_t *len)
+{
+    tcn_ssl_conn_t *con = (tcn_ssl_conn_t *)sock;
+    int s, i, wr = (int)(*len);
+    apr_status_t rv;
+    apr_interval_time_t timeout;
+
+    *len = 0;
+    if (con->reneg_state == RENEG_ABORT) {
+        con->shutdown_type = SSL_SHUTDOWN_TYPE_UNCLEAN;
+        return APR_ECONNABORTED;
+    }
+    if (!SSL_is_init_finished(con->ssl)) {
+        /* XXX: Is this a correct retval ? */ 
+        return APR_EINPROGRESS;
+    }
+    if (wr == 0) {
+        /* According to docs calling SSL_write() with num=0 bytes
+         * to be sent the behaviour is undefined.
+         */
+        return APR_EINVAL;
+    }
+    apr_socket_timeout_get(con->sock, &timeout);
+    for (;;) {
+        ERR_clear_error();
+        if ((s = SSL_write(con->ssl, buf, wr)) <= 0) {
+            if (!con->ssl)
+                return APR_ENOTSOCK;
+            rv  = apr_get_netos_error();
+            i   = SSL_get_error(con->ssl, s);
+            switch (i) {
+                case SSL_ERROR_WANT_READ:
+                case SSL_ERROR_WANT_WRITE:
+                    if ((rv = wait_for_io_or_timeout(con, i, timeout)) != APR_SUCCESS) {
+                        con->shutdown_type = SSL_SHUTDOWN_TYPE_UNCLEAN;
+                        return rv;
+                    }
+                break;
+                case SSL_ERROR_SYSCALL:
+                    if (s == -1) {
+                        if (APR_STATUS_IS_EPIPE(rv) || APR_STATUS_IS_ECONNRESET(rv)) {
+                            con->shutdown_type = SSL_SHUTDOWN_TYPE_STANDARD;
+                            return APR_EOF;
+                        }
+#if !defined(_WIN32)
+                        else if (APR_STATUS_IS_EINTR(rv)) {
+                            /* Interrupted by signal
+                             */
+                            continue;
+                        }
+#endif
+                    }
+                    con->shutdown_type = SSL_SHUTDOWN_TYPE_UNCLEAN;
+                    return rv;
+                break;
+                case SSL_ERROR_SSL:
+                    /* Probably caused by buffer missmatch */
+                    rv = APR_EINVAL;
+                case SSL_ERROR_ZERO_RETURN:
+                    if (s == 0) {
+                        con->shutdown_type = SSL_SHUTDOWN_TYPE_STANDARD;
+                        return APR_EOF;
+                    }
+                    /* Fall trough */
+                default:
+                    con->shutdown_type = SSL_SHUTDOWN_TYPE_UNCLEAN;
+                    return rv;
+                break;
+            }
+        }
+        else {
+            *len = s;
+            break;
+        }
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t APR_THREAD_FUNC
+ssl_socket_sendv(apr_socket_t *sock,
+                 const struct iovec *vec,
+                 apr_int32_t nvec, apr_size_t *len)
+{
+    tcn_ssl_conn_t *con = (tcn_ssl_conn_t *)sock;
+    apr_status_t rv;
+    apr_size_t written = 0;
+    apr_int32_t i;
+
+    for (i = 0; i < nvec; i++) {
+        apr_size_t rd = vec[i].iov_len;
+        if ((rv = ssl_socket_send((apr_socket_t *)con,
+                                  vec[i].iov_base, &rd)) != APR_SUCCESS) {
+            *len = written;
+            return rv;
+        }
+        written += rd;
+    }
+    *len = written;
+    return APR_SUCCESS;
+}
+
+static tcn_nlayer_t ssl_socket_layer = {
+    TCN_SOCKET_SSL,
+    ssl_cleanup,
+    ssl_socket_close,
+    ssl_socket_shutdown,
+    ssl_socket_opt_get,
+    ssl_socket_opt_set,
+    ssl_socket_timeout_get,
+    ssl_socket_timeout_set,
+    ssl_socket_send,
+    ssl_socket_sendv,
+    ssl_socket_recv
+};
+
+
+TCN_IMPLEMENT_CALL(jint, SSLSocket, attach)(TCN_STDARGS, jlong ctx,
+                                            jlong sock)
+{
+    tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
+    tcn_socket_t *s   = J2P(sock, tcn_socket_t *);
+    tcn_ssl_conn_t *con;
+    apr_os_sock_t  oss;
+    apr_status_t rv;
+
+    UNREFERENCED(o);
+    TCN_ASSERT(ctx != 0);
+    TCN_ASSERT(sock != 0);
+
+    if (!s->sock)
+        return APR_ENOTSOCK;
+
+    if ((rv = apr_os_sock_get(&oss, s->sock)) != APR_SUCCESS)
+        return rv;
+    if (oss == APR_INVALID_SOCKET)
+        return APR_ENOTSOCK;
+
+    if ((con = ssl_create(e, c, s->pool)) == NULL)
+        return APR_EGENERAL;
+    con->sock = s->sock;
+
+    SSL_set_fd(con->ssl, (int)oss);
+    if (c->mode)
+        SSL_set_accept_state(con->ssl);
+    else
+        SSL_set_connect_state(con->ssl);
+    /* Change socket type */
+    s->net    = &ssl_socket_layer;
+    s->opaque = con;
+
+    return APR_SUCCESS;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSLSocket, renegotiate)(TCN_STDARGS,
+                                                 jlong sock)
+{
+    tcn_socket_t *s   = J2P(sock, tcn_socket_t *);
+    tcn_ssl_conn_t *con;
+    int retVal;
+    int ecode = SSL_ERROR_WANT_READ;
+    apr_status_t rv;
+    apr_interval_time_t timeout;
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(sock != 0);
+    con = (tcn_ssl_conn_t *)s->opaque;
+
+    /* Sequence to renegotiate is
+     *  SSL_renegotiate()
+     *  SSL_do_handshake()
+     *  ssl->state = SSL_ST_ACCEPT
+     *  SSL_do_handshake()
+     */
+
+    /* Toggle the renegotiation state to allow the new
+     * handshake to proceed.
+     */
+    con->reneg_state = RENEG_ALLOW;
+    retVal = SSL_renegotiate(con->ssl);
+    if (retVal <= 0)
+        return APR_EGENERAL;
+
+    retVal = SSL_do_handshake(con->ssl);
+    if (retVal <= 0)
+        return APR_EGENERAL;
+
+    if (SSL_get_state(con->ssl) != SSL_ST_OK) {
+        return APR_EGENERAL;
+    }
+    SSL_set_state(con->ssl, SSL_ST_ACCEPT);
+
+    apr_socket_timeout_get(con->sock, &timeout);
+    ecode = SSL_ERROR_WANT_READ;
+    while (ecode == SSL_ERROR_WANT_READ) {
+        retVal = SSL_do_handshake(con->ssl);
+        if (retVal <= 0) {
+            ecode = SSL_get_error(con->ssl, retVal);
+            if (ecode == SSL_ERROR_WANT_READ) {
+                if ((rv = wait_for_io_or_timeout(con, ecode, timeout)) != APR_SUCCESS)
+                    return rv; /* Can't wait */
+                continue; /* It should be ok now */
+            }
+            else
+                return APR_EGENERAL;
+        } else
+            break;
+    }
+    con->reneg_state = RENEG_REJECT;
+
+    if (SSL_get_state(con->ssl) != SSL_ST_OK) {
+        return APR_EGENERAL;
+    }
+
+    return APR_SUCCESS;
+}
+
+TCN_IMPLEMENT_CALL(void, SSLSocket, setVerify)(TCN_STDARGS,
+                                               jlong sock,
+                                               jint cverify,
+                                               jint depth)
+{
+    tcn_socket_t *s   = J2P(sock, tcn_socket_t *);
+    tcn_ssl_conn_t *con;
+    int verify = SSL_VERIFY_NONE;
+
+    UNREFERENCED_STDARGS;
+    TCN_ASSERT(sock != 0);
+    con = (tcn_ssl_conn_t *)s->opaque;
+
+    if (cverify == SSL_CVERIFY_UNSET)
+        cverify = SSL_CVERIFY_NONE;
+    if (depth > 0)
+        SSL_set_verify_depth(con->ssl, depth);
+
+    if (cverify == SSL_CVERIFY_REQUIRE)
+        verify |= SSL_VERIFY_PEER_STRICT;
+    if ((cverify == SSL_CVERIFY_OPTIONAL) ||
+        (cverify == SSL_CVERIFY_OPTIONAL_NO_CA))
+        verify |= SSL_VERIFY_PEER;
+
+    SSL_set_verify(con->ssl, verify, NULL);
+}
+
+#else
+/* OpenSSL is not supported.
+ * Create empty stubs.
+ */
+
+TCN_IMPLEMENT_CALL(jint, SSLSocket, handshake)(TCN_STDARGS, jlong sock)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(sock);
+    return (jint)APR_ENOTIMPL;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSLSocket, attach)(TCN_STDARGS, jlong ctx,
+                                            jlong sock)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(ctx);
+    UNREFERENCED(sock);
+    return (jint)APR_ENOTIMPL;
+}
+
+TCN_IMPLEMENT_CALL(jint, SSLSocket, renegotiate)(TCN_STDARGS,
+                                                 jlong sock)
+{
+    UNREFERENCED_STDARGS;
+    UNREFERENCED(sock);
+    return (jint)APR_ENOTIMPL;
+}
+
+#endif
diff --git a/c/sslutils.c b/c/sslutils.c
new file mode 100644
index 0000000..f49f0bd
--- /dev/null
+++ b/c/sslutils.c
@@ -0,0 +1,1360 @@
+/* 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.
+ */
+
+/** SSL Utilities
+ *
+ * @author Mladen Turk
+ * @version $Id: sslutils.c 1658728 2015-02-10 14:45:19Z kkolinko $
+ */
+
+#include "tcn.h"
+
+#ifdef HAVE_OPENSSL
+#include "apr_poll.h"
+#include "ssl_private.h"
+
+#ifdef WIN32
+extern int WIN32_SSL_password_prompt(tcn_pass_cb_t *data);
+#endif
+
+#ifdef HAVE_OPENSSL_OCSP
+#include <openssl/bio.h>
+#include <openssl/ocsp.h>
+/* defines with the values as seen by the asn1parse -dump openssl command */
+#define ASN1_SEQUENCE 0x30
+#define ASN1_OID      0x06
+#define ASN1_STRING   0x86
+#pragma message("Using OCSP")
+static int ssl_verify_OCSP(int ok, X509_STORE_CTX *ctx);
+static int ssl_ocsp_request(X509 *cert, X509 *issuer);
+#endif
+
+/*  _________________________________________________________________
+**
+**  Additional High-Level Functions for OpenSSL
+**  _________________________________________________________________
+*/
+
+/* we initialize this index at startup time
+ * and never write to it at request time,
+ * so this static is thread safe.
+ * also note that OpenSSL increments at static variable when
+ * SSL_get_ex_new_index() is called, so we _must_ do this at startup.
+ */
+static int SSL_app_data2_idx = -1;
+static int SSL_app_data3_idx = -1;
+void SSL_init_app_data2_3_idx(void)
+{
+    int i;
+
+    if (SSL_app_data2_idx > -1) {
+        return;
+    }
+
+    /* we _do_ need to call this two times */
+    for (i = 0; i <= 1; i++) {
+        SSL_app_data2_idx =
+            SSL_get_ex_new_index(0,
+                                 "Second Application Data for SSL",
+                                 NULL, NULL, NULL);
+    }
+
+    if (SSL_app_data3_idx > -1) {
+        return;
+    }
+
+    SSL_app_data3_idx =
+            SSL_get_ex_new_index(0,
+                                 "Third Application Data for SSL",
+                                  NULL, NULL, NULL);
+}
+
+void *SSL_get_app_data2(SSL *ssl)
+{
+    return (void *)SSL_get_ex_data(ssl, SSL_app_data2_idx);
+}
+
+void SSL_set_app_data2(SSL *ssl, void *arg)
+{
+    SSL_set_ex_data(ssl, SSL_app_data2_idx, (char *)arg);
+    return;
+}
+
+void *SSL_get_app_data3(SSL *ssl)
+{
+    return SSL_get_ex_data(ssl, SSL_app_data3_idx);
+}
+
+void SSL_set_app_data3(SSL *ssl, void *arg)
+{
+    SSL_set_ex_data(ssl, SSL_app_data3_idx, arg);
+}
+
+/* Simple echo password prompting */
+int SSL_password_prompt(tcn_pass_cb_t *data)
+{
+    int rv = 0;
+    data->password[0] = '\0';
+    if (data->cb.obj) {
+        JNIEnv *e;
+        jobject  o;
+        jstring  prompt;
+        tcn_get_java_env(&e);
+        prompt = AJP_TO_JSTRING(data->prompt);
+        if ((o = (*e)->CallObjectMethod(e, data->cb.obj,
+                            data->cb.mid[0], prompt))) {
+            TCN_ALLOC_CSTRING(o);
+            if (J2S(o)) {
+                strncpy(data->password, J2S(o), SSL_MAX_PASSWORD_LEN);
+                data->password[SSL_MAX_PASSWORD_LEN-1] = '\0';
+                rv = (int)strlen(data->password);
+            }
+            TCN_FREE_CSTRING(o);
+        }
+    }
+    else {
+#ifdef WIN32
+        rv = WIN32_SSL_password_prompt(data);
+#elif !defined(OPENSSL_IS_BORINGSSL)
+        EVP_read_pw_string(data->password, SSL_MAX_PASSWORD_LEN,
+                           data->prompt, 0);
+#endif
+        rv = (int)strlen(data->password);
+    }
+    if (rv > 0) {
+        /* Remove LF char if present */
+        char *r = strchr(data->password, '\n');
+        if (r) {
+            *r = '\0';
+            rv--;
+        }
+#ifdef WIN32
+        if ((r = strchr(data->password, '\r'))) {
+            *r = '\0';
+            rv--;
+        }
+#endif
+    }
+    return rv;
+}
+
+int SSL_password_callback(char *buf, int bufsiz, int verify,
+                          void *cb)
+{
+    tcn_pass_cb_t *cb_data = (tcn_pass_cb_t *)cb;
+
+    if (buf == NULL)
+        return 0;
+    *buf = '\0';
+    if (cb_data == NULL)
+        cb_data = &tcn_password_callback;
+    if (!cb_data->prompt)
+        cb_data->prompt = SSL_DEFAULT_PASS_PROMPT;
+    if (cb_data->password[0]) {
+        /* Return already obtained password */
+        strncpy(buf, cb_data->password, bufsiz);
+        buf[bufsiz - 1] = '\0';
+        return (int)strlen(buf);
+    }
+    else {
+        if (SSL_password_prompt(cb_data) > 0)
+            strncpy(buf, cb_data->password, bufsiz);
+    }
+    buf[bufsiz - 1] = '\0';
+    return (int)strlen(buf);
+}
+
+static unsigned char dh0512_p[]={
+    0xD9,0xBA,0xBF,0xFD,0x69,0x38,0xC9,0x51,0x2D,0x19,0x37,0x39,
+    0xD7,0x7D,0x7E,0x3E,0x25,0x58,0x55,0x94,0x90,0x60,0x93,0x7A,
+    0xF2,0xD5,0x61,0x5F,0x06,0xE8,0x08,0xB4,0x57,0xF4,0xCF,0xB4,
+    0x41,0xCC,0xC4,0xAC,0xD4,0xF0,0x45,0x88,0xC9,0xD1,0x21,0x4C,
+    0xB6,0x72,0x48,0xBD,0x73,0x80,0xE0,0xDD,0x88,0x41,0xA0,0xF1,
+    0xEA,0x4B,0x71,0x13
+};
+static unsigned char dh1024_p[]={
+    0xA2,0x95,0x7E,0x7C,0xA9,0xD5,0x55,0x1D,0x7C,0x77,0x11,0xAC,
+    0xFD,0x48,0x8C,0x3B,0x94,0x1B,0xC5,0xC0,0x99,0x93,0xB5,0xDC,
+    0xDC,0x06,0x76,0x9E,0xED,0x1E,0x3D,0xBB,0x9A,0x29,0xD6,0x8B,
+    0x1F,0xF6,0xDA,0xC9,0xDF,0xD5,0x02,0x4F,0x09,0xDE,0xEC,0x2C,
+    0x59,0x1E,0x82,0x32,0x80,0x9B,0xED,0x51,0x68,0xD2,0xFB,0x1E,
+    0x25,0xDB,0xDF,0x9C,0x11,0x70,0xDF,0xCA,0x19,0x03,0x3D,0x3D,
+    0xC1,0xAC,0x28,0x88,0x4F,0x13,0xAF,0x16,0x60,0x6B,0x5B,0x2F,
+    0x56,0xC7,0x5B,0x5D,0xDE,0x8F,0x50,0x08,0xEC,0xB1,0xB9,0x29,
+    0xAA,0x54,0xF4,0x05,0xC9,0xDF,0x95,0x9D,0x79,0xC6,0xEA,0x3F,
+    0xC9,0x70,0x42,0xDA,0x90,0xC7,0xCC,0x12,0xB9,0x87,0x86,0x39,
+    0x1E,0x1A,0xCE,0xF7,0x3F,0x15,0xB5,0x2B
+};
+static unsigned char dh2048_p[]={
+    0xF2,0x4A,0xFC,0x7E,0x73,0x48,0x21,0x03,0xD1,0x1D,0xA8,0x16,
+    0x87,0xD0,0xD2,0xDC,0x42,0xA8,0xD2,0x73,0xE3,0xA9,0x21,0x31,
+    0x70,0x5D,0x69,0xC7,0x8F,0x95,0x0C,0x9F,0xB8,0x0E,0x37,0xAE,
+    0xD1,0x6F,0x36,0x1C,0x26,0x63,0x2A,0x36,0xBA,0x0D,0x2A,0xF5,
+    0x1A,0x0F,0xE8,0xC0,0xEA,0xD1,0xB5,0x52,0x47,0x1F,0x9A,0x0C,
+    0x0F,0xED,0x71,0x51,0xED,0xE6,0x62,0xD5,0xF8,0x81,0x93,0x55,
+    0xC1,0x0F,0xB4,0x72,0x64,0xB3,0x73,0xAA,0x90,0x9A,0x81,0xCE,
+    0x03,0xFD,0x6D,0xB1,0x27,0x7D,0xE9,0x90,0x5E,0xE2,0x10,0x74,
+    0x4F,0x94,0xC3,0x05,0x21,0x73,0xA9,0x12,0x06,0x9B,0x0E,0x20,
+    0xD1,0x5F,0xF7,0xC9,0x4C,0x9D,0x4F,0xFA,0xCA,0x4D,0xFD,0xFF,
+    0x6A,0x62,0x9F,0xF0,0x0F,0x3B,0xA9,0x1D,0xF2,0x69,0x29,0x00,
+    0xBD,0xE9,0xB0,0x9D,0x88,0xC7,0x4A,0xAE,0xB0,0x53,0xAC,0xA2,
+    0x27,0x40,0x88,0x58,0x8F,0x26,0xB2,0xC2,0x34,0x7D,0xA2,0xCF,
+    0x92,0x60,0x9B,0x35,0xF6,0xF3,0x3B,0xC3,0xAA,0xD8,0x58,0x9C,
+    0xCF,0x5D,0x9F,0xDB,0x14,0x93,0xFA,0xA3,0xFA,0x44,0xB1,0xB2,
+    0x4B,0x0F,0x08,0x70,0x44,0x71,0x3A,0x73,0x45,0x8E,0x6D,0x9C,
+    0x56,0xBC,0x9A,0xB5,0xB1,0x3D,0x8B,0x1F,0x1E,0x2B,0x0E,0x93,
+    0xC2,0x9B,0x84,0xE2,0xE8,0xFC,0x29,0x85,0x83,0x8D,0x2E,0x5C,
+    0xDD,0x9A,0xBB,0xFD,0xF0,0x87,0xBF,0xAF,0xC4,0xB6,0x1D,0xE7,
+    0xF9,0x46,0x50,0x7F,0xC3,0xAC,0xFD,0xC9,0x8C,0x9D,0x66,0x6B,
+    0x4C,0x6A,0xC9,0x3F,0x0C,0x0A,0x74,0x94,0x41,0x85,0x26,0x8F,
+    0x9F,0xF0,0x7C,0x0B
+};
+static unsigned char dh4096_p[] = {
+    0x8D,0xD3,0x8F,0x77,0x6F,0x6F,0xB0,0x74,0x3F,0x22,0xE9,0xD1,
+    0x17,0x15,0x69,0xD8,0x24,0x85,0xCD,0xC4,0xE4,0x0E,0xF6,0x52,
+    0x40,0xF7,0x1C,0x34,0xD0,0xA5,0x20,0x77,0xE2,0xFC,0x7D,0xA1,
+    0x82,0xF1,0xF3,0x78,0x95,0x05,0x5B,0xB8,0xDB,0xB3,0xE4,0x17,
+    0x93,0xD6,0x68,0xA7,0x0A,0x0C,0xC5,0xBB,0x9C,0x5E,0x1E,0x83,
+    0x72,0xB3,0x12,0x81,0xA2,0xF5,0xCD,0x44,0x67,0xAA,0xE8,0xAD,
+    0x1E,0x8F,0x26,0x25,0xF2,0x8A,0xA0,0xA5,0xF4,0xFB,0x95,0xAE,
+    0x06,0x50,0x4B,0xD0,0xE7,0x0C,0x55,0x88,0xAA,0xE6,0xB8,0xF6,
+    0xE9,0x2F,0x8D,0xA7,0xAD,0x84,0xBC,0x8D,0x4C,0xFE,0x76,0x60,
+    0xCD,0xC8,0xED,0x7C,0xBF,0xF3,0xC1,0xF8,0x6A,0xED,0xEC,0xE9,
+    0x13,0x7D,0x4E,0x72,0x20,0x77,0x06,0xA4,0x12,0xF8,0xD2,0x34,
+    0x6F,0xDC,0x97,0xAB,0xD3,0xA0,0x45,0x8E,0x7D,0x21,0xA9,0x35,
+    0x6E,0xE4,0xC9,0xC4,0x53,0xFF,0xE5,0xD9,0x72,0x61,0xC4,0x8A,
+    0x75,0x78,0x36,0x97,0x1A,0xAB,0x92,0x85,0x74,0x61,0x7B,0xE0,
+    0x92,0xB8,0xC6,0x12,0xA1,0x72,0xBB,0x5B,0x61,0xAA,0xE6,0x2C,
+    0x2D,0x9F,0x45,0x79,0x9E,0xF4,0x41,0x93,0x93,0xEF,0x8B,0xEF,
+    0xB7,0xBF,0x6D,0xF0,0x91,0x11,0x4F,0x7C,0x71,0x84,0xB5,0x88,
+    0xA3,0x8C,0x1A,0xD5,0xD0,0x81,0x9C,0x50,0xAC,0xA9,0x2B,0xE9,
+    0x92,0x2D,0x73,0x7C,0x0A,0xA3,0xFA,0xD3,0x6C,0x91,0x43,0xA6,
+    0x80,0x7F,0xD7,0xC4,0xD8,0x6F,0x85,0xF8,0x15,0xFD,0x08,0xA6,
+    0xF8,0x7B,0x3A,0xF4,0xD3,0x50,0xB4,0x2F,0x75,0xC8,0x48,0xB8,
+    0xA8,0xFD,0xCA,0x8F,0x62,0xF1,0x4C,0x89,0xB7,0x18,0x67,0xB2,
+    0x93,0x2C,0xC4,0xD4,0x71,0x29,0xA9,0x26,0x20,0xED,0x65,0x37,
+    0x06,0x87,0xFC,0xFB,0x65,0x02,0x1B,0x3C,0x52,0x03,0xA1,0xBB,
+    0xCF,0xE7,0x1B,0xA4,0x1A,0xE3,0x94,0x97,0x66,0x06,0xBF,0xA9,
+    0xCE,0x1B,0x07,0x10,0xBA,0xF8,0xD4,0xD4,0x05,0xCF,0x53,0x47,
+    0x16,0x2C,0xA1,0xFC,0x6B,0xEF,0xF8,0x6C,0x23,0x34,0xEF,0xB7,
+    0xD3,0x3F,0xC2,0x42,0x5C,0x53,0x9A,0x00,0x52,0xCF,0xAC,0x42,
+    0xD3,0x3B,0x2E,0xB6,0x04,0x32,0xE1,0x09,0xED,0x64,0xCD,0x6A,
+    0x63,0x58,0xB8,0x43,0x56,0x5A,0xBE,0xA4,0x9F,0x68,0xD4,0xF7,
+    0xC9,0x04,0xDF,0xCD,0xE5,0x93,0xB0,0x2F,0x06,0x19,0x3E,0xB8,
+    0xAB,0x7E,0xF8,0xE7,0xE7,0xC8,0x53,0xA2,0x06,0xC3,0xC7,0xF9,
+    0x18,0x3B,0x51,0xC3,0x9B,0xFF,0x8F,0x00,0x0E,0x87,0x19,0x68,
+    0x2F,0x40,0xC0,0x68,0xFA,0x12,0xAE,0x57,0xB5,0xF0,0x97,0xCA,
+    0x78,0x23,0x31,0xAB,0x67,0x7B,0x10,0x6B,0x59,0x32,0x9C,0x64,
+    0x20,0x38,0x1F,0xC5,0x07,0x84,0x9E,0xC4,0x49,0xB1,0xDF,0xED,
+    0x7A,0x8A,0xC3,0xE0,0xDD,0x30,0x55,0xFF,0x95,0x45,0xA6,0xEE,
+    0xCB,0xE4,0x26,0xB9,0x8E,0x89,0x37,0x63,0xD4,0x02,0x3D,0x5B,
+    0x4F,0xE5,0x90,0xF6,0x72,0xF8,0x10,0xEE,0x31,0x04,0x54,0x17,
+    0xE3,0xD5,0x63,0x84,0x80,0x62,0x54,0x46,0x85,0x6C,0xD2,0xC1,
+    0x3E,0x19,0xBD,0xE2,0x80,0x11,0x86,0xC7,0x4B,0x7F,0x67,0x86,
+    0x47,0xD2,0x38,0xCD,0x8F,0xFE,0x65,0x3C,0x11,0xCD,0x96,0x99,
+    0x4E,0x45,0xEB,0xEC,0x1D,0x94,0x8C,0x53,
+};
+static unsigned char dhxxx2_g[]={
+    0x02
+};
+
+static DH *get_dh(int idx)
+{
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(OPENSSL_USE_DEPRECATED)
+    DH *dh;
+
+    if ((dh = DH_new()) == NULL)
+        return NULL;
+    switch (idx) {
+        case SSL_TMP_KEY_DH_512:
+            dh->p = BN_bin2bn(dh0512_p, sizeof(dh0512_p), NULL);
+        break;
+        case SSL_TMP_KEY_DH_1024:
+            dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
+        break;
+        case SSL_TMP_KEY_DH_2048:
+            dh->p = BN_bin2bn(dh2048_p, sizeof(dh2048_p), NULL);
+        break;
+        case SSL_TMP_KEY_DH_4096:
+            dh->p = BN_bin2bn(dh4096_p, sizeof(dh2048_p), NULL);
+        break;
+    }
+    dh->g = BN_bin2bn(dhxxx2_g, sizeof(dhxxx2_g), NULL);
+    if ((dh->p == NULL) || (dh->g == NULL)) {
+        DH_free(dh);
+        return NULL;
+    }
+    else
+        return dh;
+#else
+    return NULL;
+#endif
+}
+
+DH *SSL_dh_get_tmp_param(int key_len)
+{
+    DH *dh;
+
+    if (key_len == 512)
+        dh = get_dh(SSL_TMP_KEY_DH_512);
+    else if (key_len == 1024)
+        dh = get_dh(SSL_TMP_KEY_DH_1024);
+    else if (key_len == 2048)
+        dh = get_dh(SSL_TMP_KEY_DH_2048);
+    else if (key_len == 4096)
+        dh = get_dh(SSL_TMP_KEY_DH_4096);
+    else
+        dh = get_dh(SSL_TMP_KEY_DH_1024);
+    return dh;
+}
+
+DH *SSL_dh_get_param_from_file(const char *file)
+{
+    DH *dh = NULL;
+    BIO *bio;
+
+    if ((bio = BIO_new_file(file, "r")) == NULL)
+        return NULL;
+    dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+    BIO_free(bio);
+    return dh;
+}
+
+/*
+ * Handle out temporary RSA private keys on demand
+ *
+ * The background of this as the TLSv1 standard explains it:
+ *
+ * | D.1. Temporary RSA keys
+ * |
+ * |    US Export restrictions limit RSA keys used for encryption to 512
+ * |    bits, but do not place any limit on lengths of RSA keys used for
+ * |    signing operations. Certificates often need to be larger than 512
+ * |    bits, since 512-bit RSA keys are not secure enough for high-value
+ * |    transactions or for applications requiring long-term security. Some
+ * |    certificates are also designated signing-only, in which case they
+ * |    cannot be used for key exchange.
+ * |
+ * |    When the public key in the certificate cannot be used for encryption,
+ * |    the server signs a temporary RSA key, which is then exchanged. In
+ * |    exportable applications, the temporary RSA key should be the maximum
+ * |    allowable length (i.e., 512 bits). Because 512-bit RSA keys are
+ * |    relatively insecure, they should be changed often. For typical
+ * |    electronic commerce applications, it is suggested that keys be
+ * |    changed daily or every 500 transactions, and more often if possible.
+ * |    Note that while it is acceptable to use the same temporary key for
+ * |    multiple transactions, it must be signed each time it is used.
+ * |
+ * |    RSA key generation is a time-consuming process. In many cases, a
+ * |    low-priority process can be assigned the task of key generation.
+ * |    Whenever a new key is completed, the existing temporary key can be
+ * |    replaced with the new one.
+ *
+ * XXX: base on comment above, if thread support is enabled,
+ * we should spawn a low-priority thread to generate new keys
+ * on the fly.
+ *
+ * So we generated 512 and 1024 bit temporary keys on startup
+ * which we now just hand out on demand....
+ */
+
+RSA *SSL_callback_tmp_RSA(SSL *ssl, int export, int keylen)
+{
+    int idx;
+
+    /* doesn't matter if export flag is on,
+     * we won't be asked for keylen > 512 in that case.
+     * if we are asked for a keylen > 1024, it is too expensive
+     * to generate on the fly.
+     */
+
+    switch (keylen) {
+        case 512:
+            idx = SSL_TMP_KEY_RSA_512;
+        break;
+        case 2048:
+            idx = SSL_TMP_KEY_RSA_2048;
+            if (SSL_temp_keys[idx] == NULL)
+                idx = SSL_TMP_KEY_RSA_1024;
+        break;
+        case 4096:
+            idx = SSL_TMP_KEY_RSA_4096;
+            if (SSL_temp_keys[idx] == NULL)
+                idx = SSL_TMP_KEY_RSA_2048;
+        break;
+        case 1024:
+        default:
+            idx = SSL_TMP_KEY_RSA_1024;
+        break;
+    }
+    return (RSA *)SSL_temp_keys[idx];
+}
+
+/*
+ * Hand out the already generated DH parameters...
+ */
+DH *SSL_callback_tmp_DH(SSL *ssl, int export, int keylen)
+{
+    int idx;
+    switch (keylen) {
+        case 512:
+            idx = SSL_TMP_KEY_DH_512;
+        break;
+        case 2048:
+            idx = SSL_TMP_KEY_DH_2048;
+        break;
+        case 4096:
+            idx = SSL_TMP_KEY_DH_4096;
+        break;
+        case 1024:
+        default:
+            idx = SSL_TMP_KEY_DH_1024;
+        break;
+    }
+    return (DH *)SSL_temp_keys[idx];
+}
+
+/*
+ * Read a file that optionally contains the server certificate in PEM
+ * format, possibly followed by a sequence of CA certificates that
+ * should be sent to the peer in the SSL Certificate message.
+ */
+int SSL_CTX_use_certificate_chain(SSL_CTX *ctx, const char *file,
+                                  int skipfirst)
+{
+    BIO *bio;
+    int n;
+
+    if ((bio = BIO_new(BIO_s_file())) == NULL)
+        return -1;
+    if (BIO_read_filename(bio, file) <= 0) {
+        BIO_free(bio);
+        return -1;
+    }
+    n = SSL_CTX_use_certificate_chain_bio(ctx, bio, skipfirst);
+    BIO_free(bio);
+    return n;
+}
+
+int SSL_CTX_use_certificate_chain_bio(SSL_CTX *ctx, BIO *bio,
+                                  int skipfirst)
+{
+    X509 *x509;
+    unsigned long err;
+    int n;
+
+    /* optionally skip a leading server certificate */
+    if (skipfirst) {
+        if ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL)) == NULL) {
+            return -1;
+        }
+        X509_free(x509);
+    }
+
+    /* free a perhaps already configured extra chain */
+    SSL_CTX_clear_extra_chain_certs(ctx);
+
+    /* create new extra chain by loading the certs */
+    n = 0;
+    while ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
+        if (SSL_CTX_add_extra_chain_cert(ctx, x509) != 1) {
+            X509_free(x509);
+            return -1;
+        }
+        n++;
+    }
+    /* Make sure that only the error is just an EOF */
+    if ((err = ERR_peek_error()) > 0) {
+        if (!(   ERR_GET_LIB(err) == ERR_LIB_PEM
+              && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
+            return -1;
+        }
+        while (ERR_get_error() > 0) ;
+    }
+    return n;
+}
+
+static int ssl_X509_STORE_lookup(X509_STORE *store, int yype,
+                                 X509_NAME *name, X509_OBJECT *obj)
+{
+    X509_STORE_CTX ctx;
+    int rc;
+
+    X509_STORE_CTX_init(&ctx, store, NULL, NULL);
+    rc = X509_STORE_get_by_subject(&ctx, yype, name, obj);
+    X509_STORE_CTX_cleanup(&ctx);
+    return rc;
+}
+
+static int ssl_verify_CRL(int ok, X509_STORE_CTX *ctx, tcn_ssl_ctxt_t *c)
+{
+    X509_OBJECT obj;
+    X509_NAME *subject, *issuer;
+    X509 *cert;
+    X509_CRL *crl;
+    EVP_PKEY *pubkey;
+    int i, n, rc;
+
+    /*
+     * Determine certificate ingredients in advance
+     */
+    cert    = X509_STORE_CTX_get_current_cert(ctx);
+    subject = X509_get_subject_name(cert);
+    issuer  = X509_get_issuer_name(cert);
+
+    /*
+     * OpenSSL provides the general mechanism to deal with CRLs but does not
+     * use them automatically when verifying certificates, so we do it
+     * explicitly here. We will check the CRL for the currently checked
+     * certificate, if there is such a CRL in the store.
+     *
+     * We come through this procedure for each certificate in the certificate
+     * chain, starting with the root-CA's certificate. At each step we've to
+     * both verify the signature on the CRL (to make sure it's a valid CRL)
+     * and it's revocation list (to make sure the current certificate isn't
+     * revoked).  But because to check the signature on the CRL we need the
+     * public key of the issuing CA certificate (which was already processed
+     * one round before), we've a little problem. But we can both solve it and
+     * at the same time optimize the processing by using the following
+     * verification scheme (idea and code snippets borrowed from the GLOBUS
+     * project):
+     *
+     * 1. We'll check the signature of a CRL in each step when we find a CRL
+     *    through the _subject_ name of the current certificate. This CRL
+     *    itself will be needed the first time in the next round, of course.
+     *    But we do the signature processing one round before this where the
+     *    public key of the CA is available.
+     *
+     * 2. We'll check the revocation list of a CRL in each step when
+     *    we find a CRL through the _issuer_ name of the current certificate.
+     *    This CRLs signature was then already verified one round before.
+     *
+     * This verification scheme allows a CA to revoke its own certificate as
+     * well, of course.
+     */
+
+    /*
+     * Try to retrieve a CRL corresponding to the _subject_ of
+     * the current certificate in order to verify it's integrity.
+     */
+    memset((char *)&obj, 0, sizeof(obj));
+    rc = ssl_X509_STORE_lookup(c->crl,
+                               X509_LU_CRL, subject, &obj);
+    crl = obj.data.crl;
+
+    if ((rc > 0) && crl) {
+        /*
+         * Log information about CRL
+         * (A little bit complicated because of ASN.1 and BIOs...)
+         */
+        /*
+         * Verify the signature on this CRL
+         */
+        pubkey = X509_get_pubkey(cert);
+        rc = X509_CRL_verify(crl, pubkey);
+        /* Only refcounted in OpenSSL */
+        if (pubkey)
+            EVP_PKEY_free(pubkey);
+        if (rc <= 0) {
+            /* TODO: Log Invalid signature on CRL */
+            X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE);
+            X509_OBJECT_free_contents(&obj);
+            return 0;
+        }
+
+        /*
+         * Check date of CRL to make sure it's not expired
+         */
+        i = X509_cmp_current_time(X509_CRL_get_nextUpdate(crl));
+
+        if (i == 0) {
+            /* TODO: Log Found CRL has invalid nextUpdate field */
+
+            X509_STORE_CTX_set_error(ctx,
+                                     X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
+            X509_OBJECT_free_contents(&obj);
+            return 0;
+        }
+
+        if (i < 0) {
+            /* TODO: Log Found CRL is expired */
+            X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_HAS_EXPIRED);
+            X509_OBJECT_free_contents(&obj);
+
+            return 0;
+        }
+
+        X509_OBJECT_free_contents(&obj);
+    }
+
+    /*
+     * Try to retrieve a CRL corresponding to the _issuer_ of
+     * the current certificate in order to check for revocation.
+     */
+    memset((char *)&obj, 0, sizeof(obj));
+    rc = ssl_X509_STORE_lookup(c->crl,
+                               X509_LU_CRL, issuer, &obj);
+
+    crl = obj.data.crl;
+    if ((rc > 0) && crl) {
+        /*
+         * Check if the current certificate is revoked by this CRL
+         */
+        n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
+
+        for (i = 0; i < n; i++) {
+            X509_REVOKED *revoked =
+                sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
+
+            ASN1_INTEGER *sn = revoked->serialNumber;
+
+            if (!ASN1_INTEGER_cmp(sn, X509_get_serialNumber(cert))) {
+                X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED);
+                X509_OBJECT_free_contents(&obj);
+
+                return 0;
+            }
+        }
+
+        X509_OBJECT_free_contents(&obj);
+    }
+
+    return ok;
+}
+
+/*
+ * This OpenSSL callback function is called when OpenSSL
+ * does client authentication and verifies the certificate chain.
+ */
+
+
+int SSL_callback_SSL_verify(int ok, X509_STORE_CTX *ctx)
+{
+   /* Get Apache context back through OpenSSL context */
+    SSL *ssl = X509_STORE_CTX_get_ex_data(ctx,
+                                          SSL_get_ex_data_X509_STORE_CTX_idx());
+    tcn_ssl_ctxt_t *c = SSL_get_app_data2(ssl);
+
+    /* Get verify ingredients */
+    int errnum   = X509_STORE_CTX_get_error(ctx);
+    int errdepth = X509_STORE_CTX_get_error_depth(ctx);
+    int verify   = c->verify_mode;
+    int depth    = c->verify_depth;
+    int skip_crl = 0;
+
+    if (verify == SSL_CVERIFY_UNSET ||
+        verify == SSL_CVERIFY_NONE)
+        return 1;
+
+    if (SSL_VERIFY_ERROR_IS_OPTIONAL(errnum) &&
+        (verify == SSL_CVERIFY_OPTIONAL_NO_CA)) {
+        ok = 1;
+        SSL_set_verify_result(ssl, X509_V_OK);
+    }
+
+#ifdef HAVE_OPENSSL_OCSP
+    /* First perform OCSP validation if possible */
+    if (ok) {
+        /* If there was an optional verification error, it's not
+         * possible to perform OCSP validation since the issuer may be
+         * missing/untrusted.  Fail in that case.
+         */
+        if (SSL_VERIFY_ERROR_IS_OPTIONAL(errnum)) {
+            X509_STORE_CTX_set_error(ctx, X509_V_ERR_APPLICATION_VERIFICATION);
+            errnum = X509_V_ERR_APPLICATION_VERIFICATION;
+            ok = 0;
+        }
+        else {
+            int ocsp_response = ssl_verify_OCSP(ok, ctx);
+            if (ocsp_response == OCSP_STATUS_OK) {
+                skip_crl = 1; /* we know it is valid we skip crl evaluation */
+            }
+            else if (ocsp_response == OCSP_STATUS_REVOKED) {
+                ok = 0 ;
+                errnum = X509_STORE_CTX_get_error(ctx);
+            }
+            else if (ocsp_response == OCSP_STATUS_UNKNOWN) {
+                /* TODO: do nothing for time being, continue with CRL */
+                ;
+            }
+        }
+    }
+#endif
+    /*
+     * Additionally perform CRL-based revocation checks
+     */
+    if (ok && c->crl && !skip_crl) {
+        if (!(ok = ssl_verify_CRL(ok, ctx, c))) {
+            errnum = X509_STORE_CTX_get_error(ctx);
+            /* TODO: Log something */
+        }
+    }
+    /*
+     * If we already know it's not ok, log the real reason
+     */
+    if (!ok) {
+        // Just in case that sslnetwork stuff was used, which is not true for netty but it can't harm to still
+        // guard against it.
+        tcn_ssl_conn_t *con = (tcn_ssl_conn_t *)SSL_get_app_data(ssl);
+
+        /* TODO: Some logging
+         * Certificate Verification: Error
+         */
+        if (con != NULL && con->peer) {
+            X509_free(con->peer);
+            con->peer = NULL;
+        }
+    }
+    if (errdepth > depth) {
+        /* TODO: Some logging
+         * Certificate Verification: Certificate Chain too long
+         */
+        ok = 0;
+    }
+    return ok;
+}
+
+/*
+ * This callback function is executed while OpenSSL processes the SSL
+ * handshake and does SSL record layer stuff.  It's used to trap
+ * client-initiated renegotiations, and for dumping everything to the
+ * log.
+ */
+void SSL_callback_handshake(const SSL *ssl, int where, int rc)
+{
+    tcn_ssl_conn_t *con = (tcn_ssl_conn_t *)SSL_get_app_data(ssl);
+
+    /* Retrieve the conn_rec and the associated SSLConnRec. */
+    if (con == NULL) {
+        return;
+    }
+
+
+    /* If the reneg state is to reject renegotiations, check the SSL
+     * state machine and move to ABORT if a Client Hello is being
+     * read. */
+    if ((where & SSL_CB_ACCEPT_LOOP) && con->reneg_state == RENEG_REJECT) {
+        int state = SSL_get_state(ssl);
+
+        if (state == SSL3_ST_SR_CLNT_HELLO_A
+#ifndef OPENSSL_IS_BORINGSSL
+                || state == SSL23_ST_SR_CLNT_HELLO_A
+#endif
+                ) {
+            con->reneg_state = RENEG_ABORT;
+            /* XXX: rejecting client initiated renegotiation
+             */
+        }
+    }
+    /* If the first handshake is complete, change state to reject any
+     * subsequent client-initated renegotiation. */
+    else if ((where & SSL_CB_HANDSHAKE_DONE) && con->reneg_state == RENEG_INIT) {
+        con->reneg_state = RENEG_REJECT;
+    }
+
+}
+
+int SSL_callback_next_protos(SSL *ssl, const unsigned char **data,
+                             unsigned int *len, void *arg)
+{
+    tcn_ssl_ctxt_t *ssl_ctxt = arg;
+
+    *data = ssl_ctxt->next_proto_data;
+    *len = ssl_ctxt->next_proto_len;
+
+    return SSL_TLSEXT_ERR_OK;
+}
+
+/* The code here is inspired by nghttp2
+ *
+ * See https://github.com/tatsuhiro-t/nghttp2/blob/ae0100a9abfcf3149b8d9e62aae216e946b517fb/src/shrpx_ssl.cc#L244 */
+int select_next_proto(SSL *ssl, const unsigned char **out, unsigned char *outlen,
+        const unsigned char *in, unsigned int inlen, unsigned char *supported_protos,
+        unsigned int supported_protos_len, int failure_behavior) {
+
+    unsigned int i = 0;
+    unsigned char target_proto_len;
+    unsigned char *p;
+    const unsigned char *end;
+    unsigned char *proto;
+    unsigned char proto_len;
+
+    while (i < supported_protos_len) {
+        target_proto_len = *supported_protos;
+        ++supported_protos;
+
+        p = (unsigned char*) in;
+        end = p + inlen;
+
+        while (p < end) {
+            proto_len = *p;
+            proto = ++p;
+
+            if (proto + proto_len <= end && target_proto_len == proto_len &&
+                    memcmp(supported_protos, proto, proto_len) == 0) {
+
+                // We found a match, so set the output and return with OK!
+                *out = proto;
+                *outlen = proto_len;
+
+                return SSL_TLSEXT_ERR_OK;
+            }
+            // Move on to the next protocol.
+            p += proto_len;
+        }
+
+        // increment len and pointers.
+        i += target_proto_len;
+        supported_protos += target_proto_len;
+    }
+
+    if (failure_behavior == SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL) {
+         // There were no match but we just select our last protocol and hope the other peer support it.
+         //
+         // decrement the pointer again so the pointer points to the start of the protocol.
+         p -= proto_len;
+         *out = p;
+         *outlen = proto_len;
+         return SSL_TLSEXT_ERR_OK;
+    }
+    // TODO: OpenSSL currently not support to fail with fatal error. Once this changes we can also support it here.
+    //       Issue https://github.com/openssl/openssl/issues/188 has been created for this.
+    // Nothing matched so not select anything and just accept.
+    return SSL_TLSEXT_ERR_NOACK;
+}
+
+int SSL_callback_select_next_proto(SSL *ssl, unsigned char **out, unsigned char *outlen,
+                         const unsigned char *in, unsigned int inlen,
+                         void *arg) {
+    tcn_ssl_ctxt_t *ssl_ctxt = arg;
+    return select_next_proto(ssl, (const unsigned char**) out, outlen, in, inlen, ssl_ctxt->next_proto_data, ssl_ctxt->next_proto_len, ssl_ctxt->next_selector_failure_behavior);
+}
+
+int SSL_callback_alpn_select_proto(SSL* ssl, const unsigned char **out, unsigned char *outlen,
+        const unsigned char *in, unsigned int inlen, void *arg) {
+    tcn_ssl_ctxt_t *ssl_ctxt = arg;
+    return select_next_proto(ssl, out, outlen, in, inlen, ssl_ctxt->alpn_proto_data, ssl_ctxt->alpn_proto_len, ssl_ctxt->alpn_selector_failure_behavior);
+}
+
+#ifdef HAVE_OPENSSL_OCSP
+
+/* Function that is used to do the OCSP verification */
+static int ssl_verify_OCSP(int ok, X509_STORE_CTX *ctx)
+{
+    X509 *cert, *issuer;
+    int r = OCSP_STATUS_UNKNOWN;
+
+    cert = X509_STORE_CTX_get_current_cert(ctx);
+    /* if we can't get the issuer, we cannot perform OCSP verification */
+    if (X509_STORE_CTX_get1_issuer(&issuer, ctx, cert) == 1 ) {
+        r = ssl_ocsp_request(cert, issuer);
+        if (r == OCSP_STATUS_REVOKED) {
+            /* we set the error if we know that it is revoked */
+            X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED);
+        }
+        else {
+            /* else we return unknown, so that we can continue with the crl */
+            r = OCSP_STATUS_UNKNOWN;
+        }
+        X509_free(issuer); /* It appears that we  should free issuer since
+                            * X509_STORE_CTX_get1_issuer() calls X509_OBJECT_up_ref_count()
+                            * on the issuer object (unline X509_STORE_CTX_get_current_cert()
+                            * that just returns the pointer
+                            */
+    }
+    return r;
+}
+
+
+/* Helps with error handling or realloc */
+static void *apr_xrealloc(void *buf, size_t oldlen, size_t len, apr_pool_t *p)
+{
+    void *newp = apr_palloc(p, len);
+
+    if(newp)
+        memcpy(newp, buf, oldlen);
+    return newp;
+}
+
+/* parses the ocsp url and updates the ocsp_urls and nocsp_urls variables
+   returns 0 on success, 1 on failure */
+static int parse_ocsp_url(unsigned char *asn1, char ***ocsp_urls,
+                          int *nocsp_urls, apr_pool_t *p)
+{
+    char **new_ocsp_urls, *ocsp_url;
+    int len, err = 0, new_nocsp_urls;
+
+    if (*asn1 == ASN1_STRING) {
+        len = *++asn1;
+        asn1++;
+        new_nocsp_urls = *nocsp_urls+1;
+        if ((new_ocsp_urls = apr_xrealloc(*ocsp_urls,*nocsp_urls, new_nocsp_urls, p)) == NULL)
+            err = 1;
+        if (!err) {
+            *ocsp_urls  = new_ocsp_urls;
+            *nocsp_urls = new_nocsp_urls;
+            *(*ocsp_urls + *nocsp_urls) = NULL;
+            if ((ocsp_url = apr_palloc(p, len + 1)) == NULL) {
+                err = 1;
+            }
+            else {
+                memcpy(ocsp_url, asn1, len);
+                ocsp_url[len] = '\0';
+                *(*ocsp_urls + *nocsp_urls - 1) = ocsp_url;
+            }
+        }
+    }
+    return err;
+
+}
+
+/* parses the ANS1 OID and if it is an OCSP OID then calls the parse_ocsp_url function */
+static int parse_ASN1_OID(unsigned char *asn1, char ***ocsp_urls, int *nocsp_urls, apr_pool_t *p)
+{
+    int len, err = 0 ;
+    const unsigned char OCSP_OID[] = {0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01};
+
+    len = *++asn1;
+    asn1++;
+    if (memcmp(asn1, OCSP_OID, len) == 0) {
+        asn1+=len;
+        err = parse_ocsp_url(asn1, ocsp_urls, nocsp_urls, p);
+    }
+    return err;
+}
+
+
+/* Parses an ASN1 Sequence. It is a recursive function, since if it finds a  sequence
+   within the sequence it calls recursively itself. This function stops when it finds
+   the end of the ASN1 sequence (marked by '\0'), so if there are other sequences within
+   the same sequence the while loop parses the sequences */
+
+/* This algo was developed with AIA in mind so it was tested only with this extension */
+static int parse_ASN1_Sequence(unsigned char *asn1, char ***ocsp_urls,
+                               int *nocsp_urls, apr_pool_t *p)
+{
+    int len = 0 , err = 0;
+
+    while (!err && *asn1 != '\0') {
+        switch(*asn1) {
+            case ASN1_SEQUENCE:
+                len = *++asn1;
+                asn1++;
+                err = parse_ASN1_Sequence(asn1, ocsp_urls, nocsp_urls, p);
+            break;
+            case ASN1_OID:
+                err = parse_ASN1_OID(asn1,ocsp_urls,nocsp_urls, p);
+                return 0;
+            break;
+            default:
+                err = 1; /* we shouldn't have any errors */
+            break;
+        }
+        asn1+=len;
+    }
+    return err;
+}
+
+/* the main function that gets the ASN1 encoding string and returns
+   a pointer to a NULL terminated "array" of char *, that contains
+   the ocsp_urls */
+static char **decode_OCSP_url(ASN1_OCTET_STRING *os, apr_pool_t *p)
+{
+    char **response = NULL;
+    unsigned char *ocsp_urls;
+    int len, numofresponses = 0 ;
+
+    len = ASN1_STRING_length(os);
+
+    ocsp_urls = apr_palloc(p,  len + 1);
+    memcpy(ocsp_urls,os->data, len);
+    ocsp_urls[len] = '\0';
+
+    if ((response = apr_pcalloc(p, sizeof(char *))) == NULL)
+        return NULL;
+    if (parse_ASN1_Sequence(ocsp_urls, &response, &numofresponses, p))
+        response = NULL;
+    return response;
+}
+
+
+/* stolen from openssl ocsp command */
+static int add_ocsp_cert(OCSP_REQUEST **req, X509 *cert, X509 *issuer,
+                         STACK_OF(OCSP_CERTID) *ids)
+{
+    OCSP_CERTID *id;
+
+    if (!issuer)
+        return 0;
+    if (!*req)
+        *req = OCSP_REQUEST_new();
+    if (!*req)
+        return 0;
+    id = OCSP_cert_to_id(NULL, cert, issuer);
+    if (!id || !sk_OCSP_CERTID_push(ids, id))
+        return 0;
+    if (!OCSP_request_add0_id(*req, id))
+        return 0;
+    else
+        return 1;
+}
+
+
+/* Creates the APR socket and connect to the hostname. Returns the
+   socket or NULL if there is an error.
+*/
+static apr_socket_t *make_socket(char *hostname, int port, apr_pool_t *mp)
+{
+    apr_sockaddr_t *sa_in;
+    apr_status_t status;
+    apr_socket_t *sock = NULL;
+
+
+    status = apr_sockaddr_info_get(&sa_in, hostname, APR_INET, port, 0, mp);
+
+    if (status == APR_SUCCESS)
+        status = apr_socket_create(&sock, sa_in->family, SOCK_STREAM, APR_PROTO_TCP, mp);
+    if (status == APR_SUCCESS)
+        status = apr_socket_connect(sock, sa_in);
+
+    if (status == APR_SUCCESS)
+        return sock;
+    return NULL;
+}
+
+
+/* Creates the request in a memory BIO in order to send it to the OCSP server.
+   Most parts of this function are taken from mod_ssl support for OCSP (with some
+   minor modifications
+*/
+static BIO *serialize_request(OCSP_REQUEST *req, char *host, int port, char *path)
+{
+    BIO *bio;
+    int len;
+
+    len = i2d_OCSP_REQUEST(req, NULL);
+
+    bio = BIO_new(BIO_s_mem());
+
+    BIO_printf(bio, "POST %s HTTP/1.0\r\n"
+      "Host: %s:%d\r\n"
+      "Content-Type: application/ocsp-request\r\n"
+      "Content-Length: %d\r\n"
+      "\r\n",
+      path, host, port, len);
+
+    if (i2d_OCSP_REQUEST_bio(bio, req) != 1) {
+        BIO_free(bio);
+        return NULL;
+    }
+
+    return bio;
+}
+
+
+/* Send the OCSP request to the OCSP server. Taken from mod_ssl OCSP support */
+static int ocsp_send_req(apr_socket_t *sock, BIO *req)
+{
+    int len;
+    char buf[TCN_BUFFER_SZ];
+    apr_status_t rv;
+    int ok = 1;
+
+    while ((len = BIO_read(req, buf, sizeof buf)) > 0) {
+        char *wbuf = buf;
+        apr_size_t remain = len;
+
+        do {
+            apr_size_t wlen = remain;
+            rv = apr_socket_send(sock, wbuf, &wlen);
+            wbuf += remain;
+            remain -= wlen;
+        } while (rv == APR_SUCCESS && remain > 0);
+
+        if (rv != APR_SUCCESS) {
+            apr_socket_close(sock);
+            ok = 0;
+        }
+    }
+
+    return ok;
+}
+
+
+
+/* Parses the buffer from the response and extracts the OCSP response.
+   Taken from openssl library */
+static OCSP_RESPONSE *parse_ocsp_resp(char *buf, int len)
+{
+    BIO *mem = NULL;
+    char tmpbuf[1024];
+    OCSP_RESPONSE *resp = NULL;
+    char *p, *q, *r;
+    int retcode;
+
+    mem = BIO_new(BIO_s_mem());
+    if(mem == NULL)
+        return NULL;
+
+    BIO_write(mem, buf, len);  /* write the buffer to the bio */
+    if (BIO_gets(mem, tmpbuf, 512) <= 0) {
+        OCSPerr(OCSP_F_OCSP_SENDREQ_BIO,OCSP_R_SERVER_RESPONSE_PARSE_ERROR);
+        goto err;
+    }
+    /* Parse the HTTP response. This will look like this:
+     * "HTTP/1.0 200 OK". We need to obtain the numeric code and
+     * (optional) informational message.
+     */
+
+    /* Skip to first white space (passed protocol info) */
+    for (p = tmpbuf; *p && !apr_isspace(*p); p++)
+        continue;
+    if (!*p) {
+        goto err;
+    }
+    /* Skip past white space to start of response code */
+    while (apr_isspace(*p))
+        p++;
+    if (!*p) {
+        goto err;
+    }
+    /* Find end of response code: first whitespace after start of code */
+    for (q = p; *q && !apr_isspace(*q); q++)
+        continue;
+    if (!*q) {
+        goto err;
+    }
+    /* Set end of response code and start of message */
+    *q++ = 0;
+    /* Attempt to parse numeric code */
+    retcode = strtoul(p, &r, 10);
+    if (*r)
+        goto err;
+    /* Skip over any leading white space in message */
+    while (apr_isspace(*q))
+        q++;
+    if (*q) {
+        /* Finally zap any trailing white space in message (include CRLF) */
+        /* We know q has a non white space character so this is OK */
+        for(r = q + strlen(q) - 1; apr_isspace(*r); r--) *r = 0;
+    }
+    if (retcode != 200) {
+        goto err;
+    }
+    /* Find blank line marking beginning of content */
+    while (BIO_gets(mem, tmpbuf, 512) > 0) {
+        for (p = tmpbuf; apr_isspace(*p); p++)
+            continue;
+        if (!*p)
+            break;
+    }
+    if (*p) {
+        goto err;
+    }
+    if (!(resp = d2i_OCSP_RESPONSE_bio(mem, NULL))) {
+        goto err;
+    }
+err:
+    BIO_free(mem);
+    return resp;
+}
+
+
+/* Reads the respnse from the APR socket to a buffer, and parses the buffer to
+   return the OCSP response  */
+#define ADDLEN 512
+static OCSP_RESPONSE *ocsp_get_resp(apr_socket_t *sock)
+{
+    int buflen;
+    apr_size_t totalread = 0;
+    apr_size_t readlen;
+    char *buf, tmpbuf[ADDLEN];
+    apr_status_t rv = APR_SUCCESS;
+    apr_pool_t *p;
+    OCSP_RESPONSE *resp;
+
+    apr_pool_create(&p, NULL);
+    buflen = ADDLEN;
+    buf = apr_palloc(p, buflen);
+    if (buf == NULL) {
+        apr_pool_destroy(p);
+        return NULL;
+    }
+
+    while (rv == APR_SUCCESS ) {
+        readlen = sizeof(tmpbuf);
+        rv = apr_socket_recv(sock, tmpbuf, &readlen);
+        if (rv == APR_SUCCESS) { /* if we have read something .. we can put it in the buffer*/
+            if ((totalread + readlen) >= buflen) {
+                buf = apr_xrealloc(buf, buflen, buflen + ADDLEN, p);
+                if (buf == NULL) {
+                    apr_pool_destroy(p);
+                    return NULL;
+                }
+                buflen += ADDLEN; /* if needed we enlarge the buffer */
+            }
+            memcpy(buf + totalread, tmpbuf, readlen); /* the copy to the buffer */
+            totalread += readlen; /* update the total bytes read */
+        }
+        else {
+            if (rv == APR_EOF && readlen == 0)
+                ; /* EOF, normal situation */
+            else if (readlen == 0) {
+                /* Not success, and readlen == 0 .. some error */
+                apr_pool_destroy(p);
+                return NULL;
+            }
+        }
+    }
+
+    resp = parse_ocsp_resp(buf, buflen);
+    apr_pool_destroy(p);
+    return resp;
+}
+
+/* Creates and OCSP request and returns the OCSP_RESPONSE */
+static OCSP_RESPONSE *get_ocsp_response(X509 *cert, X509 *issuer, char *url)
+{
+    OCSP_RESPONSE *ocsp_resp = NULL;
+    OCSP_REQUEST *ocsp_req = NULL;
+    BIO *bio_req;
+    char *hostname, *path, *c_port;
+    int port, use_ssl;
+    STACK_OF(OCSP_CERTID) *ids = NULL;
+    int ok = 0;
+    apr_socket_t *apr_sock = NULL;
+    apr_pool_t *mp;
+
+    apr_pool_create(&mp, NULL);
+    ids = sk_OCSP_CERTID_new_null();
+
+    /* problem parsing the URL */
+    if (OCSP_parse_url(url,&hostname, &c_port, &path, &use_ssl) == 0 ) {
+        sk_OCSP_CERTID_free(ids);
+        return NULL;
+    }
+
+    /* Create the OCSP request */
+    if (sscanf(c_port, "%d", &port) != 1)
+        goto end;
+    ocsp_req = OCSP_REQUEST_new();
+    if (ocsp_req == NULL)
+        return NULL;
+    if (add_ocsp_cert(&ocsp_req,cert,issuer,ids) == 0 )
+        goto free_req;
+
+    /* create the BIO with the request to send */
+    bio_req = serialize_request(ocsp_req, hostname, port, path);
+    if (bio_req == NULL) {
+        goto free_req;
+    }
+
+    apr_sock = make_socket(hostname, port, mp);
+    if (apr_sock == NULL) {
+        ocsp_resp = NULL;
+        goto free_bio;
+    }
+
+    ok = ocsp_send_req(apr_sock, bio_req);
+    if (ok)
+        ocsp_resp = ocsp_get_resp(apr_sock);
+
+free_bio:
+    BIO_free(bio_req);
+
+free_req:
+    if(apr_sock && ok) /* if ok == 0 we have already closed the socket */
+        apr_socket_close(apr_sock);
+
+    apr_pool_destroy(mp);
+
+    sk_OCSP_CERTID_free(ids);
+    OCSP_REQUEST_free(ocsp_req);
+
+end:
+    return ocsp_resp;
+}
+
+/* Process the OCSP_RESPONSE and returns the corresponding
+   answert according to the status.
+*/
+static int process_ocsp_response(OCSP_RESPONSE *ocsp_resp)
+{
+    int r, o = V_OCSP_CERTSTATUS_UNKNOWN, i;
+    OCSP_BASICRESP *bs;
+    OCSP_SINGLERESP *ss;
+
+    r = OCSP_response_status(ocsp_resp);
+
+    if (r != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+        OCSP_RESPONSE_free(ocsp_resp);
+        return OCSP_STATUS_UNKNOWN;
+    }
+    bs = OCSP_response_get1_basic(ocsp_resp);
+
+    ss = OCSP_resp_get0(bs,0); /* we know we have only 1 request */
+
+    i = OCSP_single_get0_status(ss, NULL, NULL, NULL, NULL);
+    if (i == V_OCSP_CERTSTATUS_GOOD)
+        o =  OCSP_STATUS_OK;
+    else if (i == V_OCSP_CERTSTATUS_REVOKED)
+        o = OCSP_STATUS_REVOKED;
+    else if (i == V_OCSP_CERTSTATUS_UNKNOWN)
+        o = OCSP_STATUS_UNKNOWN;
+
+    /* we clean up */
+    OCSP_RESPONSE_free(ocsp_resp);
+    return o;
+}
+
+static int ssl_ocsp_request(X509 *cert, X509 *issuer)
+{
+    char **ocsp_urls = NULL;
+    int nid;
+    X509_EXTENSION *ext;
+    ASN1_OCTET_STRING *os;
+    apr_pool_t *p;
+
+    apr_pool_create(&p, NULL);
+
+    /* Get the proper extension */
+    nid = X509_get_ext_by_NID(cert,NID_info_access,-1);
+    if (nid >= 0 ) {
+        ext = X509_get_ext(cert,nid);
+        os = X509_EXTENSION_get_data(ext);
+
+        ocsp_urls = decode_OCSP_url(os, p);
+    }
+
+    /* if we find the extensions and we can parse it check
+       the ocsp status. Otherwise, return OCSP_STATUS_UNKNOWN */
+    if (ocsp_urls != NULL) {
+        OCSP_RESPONSE *resp;
+        /* for the time being just check for the fist response .. a better
+           approach is to iterate for all the possible ocsp urls */
+        resp = get_ocsp_response(cert, issuer, ocsp_urls[0]);
+
+        if (resp != NULL) {
+            apr_pool_destroy(p);
+            return process_ocsp_response(resp);
+        }
+    }
+    apr_pool_destroy(p);
+    return OCSP_STATUS_UNKNOWN;
+}
+
+#endif /* HAS_OCSP_ENABLED */
+#endif /* HAVE_OPENSSL  */
diff --git a/c/stdlib.c b/c/stdlib.c
new file mode 100644
index 0000000..3295934
--- /dev/null
+++ b/c/stdlib.c
@@ -0,0 +1,120 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: stdlib.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+ 
+#include "tcn.h"
+
+extern int tcn_parent_pid;
+
+TCN_IMPLEMENT_CALL(jlong, Stdlib, malloc)(TCN_STDARGS, jint size)
+{
+    UNREFERENCED_STDARGS;
+    if (size)
+        return P2J(malloc((size_t)size));
+    else
+        return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, Stdlib, realloc)(TCN_STDARGS, jlong mem, jint size)
+{
+    void *ptr = J2P(mem, void *);
+    UNREFERENCED_STDARGS;
+    if (size)
+        return P2J(realloc(ptr, (size_t)size));
+    else
+        return 0;
+}
+
+TCN_IMPLEMENT_CALL(jlong, Stdlib, calloc)(TCN_STDARGS, jint num, jint size)
+{
+    UNREFERENCED_STDARGS;
+    if (num && size)
+        return P2J(calloc((size_t)num, (size_t)size));
+    else
+        return 0;
+}
+
+TCN_IMPLEMENT_CALL(void, Stdlib, free)(TCN_STDARGS, jlong mem)
+{
+    void *ptr = J2P(mem, void *);
+
+    UNREFERENCED_STDARGS;
+    if (ptr)
+        free(ptr);
+}
+
+TCN_IMPLEMENT_CALL(jboolean, Stdlib, memread)(TCN_STDARGS,
+                                              jbyteArray dst,
+                                              jlong src, jint sz)
+{
+    jbyte *s = J2P(src, jbyte *);
+    jbyte *dest = (*e)->GetPrimitiveArrayCritical(e, dst, NULL);
+
+    UNREFERENCED(o);
+    if (s && dest) {
+        memcpy(dest, s, (size_t)sz);
+        (*e)->ReleasePrimitiveArrayCritical(e, dst, dest, 0);
+        return JNI_TRUE;
+    }
+    else
+        return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, Stdlib, memwrite)(TCN_STDARGS, jlong dst,
+                                               jbyteArray src, jint sz)
+{
+    jbyte *dest = J2P(dst, jbyte *);
+    jbyte *s = (*e)->GetPrimitiveArrayCritical(e, src, NULL);
+
+    UNREFERENCED(o);
+    if (s && dest) {
+        memcpy(dest, s, (size_t)sz);
+        (*e)->ReleasePrimitiveArrayCritical(e, src, s, JNI_ABORT);
+        return JNI_TRUE;
+    }
+    else
+        return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jboolean, Stdlib, memset)(TCN_STDARGS, jlong dst,
+                                             jint  c, jint sz)
+{
+    jbyte *dest = J2P(dst, jbyte *);
+
+    UNREFERENCED_STDARGS;
+    if (memset(dest, (int)c, (size_t)sz))
+        return JNI_TRUE;
+    else
+        return JNI_FALSE;
+}
+
+TCN_IMPLEMENT_CALL(jint, Stdlib, getpid)(TCN_STDARGS)
+{
+    UNREFERENCED_STDARGS;
+    return (jint)getpid();
+}
+
+TCN_IMPLEMENT_CALL(jint, Stdlib, getppid)(TCN_STDARGS)
+{
+    UNREFERENCED_STDARGS;
+    return (jint)tcn_parent_pid;
+}
+
diff --git a/c/tcn.h b/c/tcn.h
new file mode 100644
index 0000000..9758912
--- /dev/null
+++ b/c/tcn.h
@@ -0,0 +1,317 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: tcn.h 1446443 2013-02-15 04:18:04Z rjung $
+ */
+
+#ifndef TCN_H
+#define TCN_H
+
+#include "apr.h"
+#include "apr_general.h"
+#include "apr_lib.h"
+#include "apr_pools.h"
+#include "apr_portable.h"
+#include "apr_network_io.h"
+#include "apr_poll.h"
+#include "apr_ring.h"
+#include "apr_strings.h"
+
+#ifndef APR_HAS_THREADS
+#error "Missing APR_HAS_THREADS support from APR."
+#endif
+
+#if defined(DEBUG) || defined(_DEBUG)
+/* On -DDEBUG use the statistics */
+#ifndef TCN_DO_STATISTICS
+#define TCN_DO_STATISTICS
+#endif
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#if defined(_WIN32) && !defined(__CYGWIN__)
+#include <process.h>
+#else
+#include <unistd.h>
+#endif
+
+#if !defined(APR_POLLSET_NOCOPY)
+/* Add missing API */
+#define APR_RING_FOREACH(ep, head, elem, link)                          \
+    for (ep = APR_RING_FIRST(head);                                     \
+         ep != APR_RING_SENTINEL(head, elem, link);                     \
+         ep = APR_RING_NEXT(ep, link))
+
+#define APR_RING_FOREACH_SAFE(ep1, ep2, head, elem, link)               \
+    for (ep1 = APR_RING_FIRST(head), ep2 = APR_RING_NEXT(ep1, link);    \
+         ep1 != APR_RING_SENTINEL(head, elem, link);                    \
+         ep1 = ep2, ep2 = APR_RING_NEXT(ep1, link))
+
+#define APR_POLLSET_NOCOPY  0
+#endif 
+
+#include "tcn_api.h"
+
+
+#if defined(_DEBUG) || defined(DEBUG)
+#include <assert.h>
+#define TCN_ASSERT(x)  assert((x))
+#else
+#define TCN_ASSERT(x) (void)0
+#endif
+
+#ifndef APR_MAX_IOVEC_SIZE
+#define APR_MAX_IOVEC_SIZE 1024
+#endif
+
+#define TCN_TIMEUP      APR_OS_START_USERERR + 1
+#define TCN_EAGAIN      APR_OS_START_USERERR + 2
+#define TCN_EINTR       APR_OS_START_USERERR + 3
+#define TCN_EINPROGRESS APR_OS_START_USERERR + 4
+#define TCN_ETIMEDOUT   APR_OS_START_USERERR + 5
+
+#define TCN_LOG_EMERG  1
+#define TCN_LOG_ERROR  2
+#define TCN_LOG_NOTICE 3
+#define TCN_LOG_WARN   4
+#define TCN_LOG_INFO   5
+#define TCN_LOG_DEBUG  6
+
+#define TCN_ERROR_WRAP(E)                   \
+    if (APR_STATUS_IS_TIMEUP(E))            \
+        (E) = TCN_TIMEUP;                   \
+    else if (APR_STATUS_IS_EAGAIN(E))       \
+        (E) = TCN_EAGAIN;                   \
+    else if (APR_STATUS_IS_EINTR(E))        \
+        (E) = TCN_EINTR;                    \
+    else if (APR_STATUS_IS_EINPROGRESS(E))  \
+        (E) = TCN_EINPROGRESS;              \
+    else if (APR_STATUS_IS_ETIMEDOUT(E))    \
+        (E) = TCN_ETIMEDOUT;                \
+    else                                    \
+        (E) = (E)
+
+#define TCN_CLASS_PATH  "org/apache/tomcat/jni/"
+#define TCN_FINFO_CLASS TCN_CLASS_PATH "FileInfo"
+#define TCN_AINFO_CLASS TCN_CLASS_PATH "Sockaddr"
+#define TCN_ERROR_CLASS TCN_CLASS_PATH "Error"
+#define TCN_PARENT_IDE  "TCN_PARENT_ID"
+
+#define UNREFERENCED(P)      (P) = (P)
+#define UNREFERENCED_STDARGS e = e; o = o
+#ifdef WIN32
+#define LLT(X) (X)
+#else
+#define LLT(X) ((long)(X))
+#endif
+#define P2J(P)          ((jlong)LLT(P))
+#define J2P(P, T)       ((T)LLT((jlong)P))
+/* On stack buffer size */
+#define TCN_BUFFER_SZ   8192
+#define TCN_STDARGS     JNIEnv *e, jobject o
+#define TCN_IMPARGS     JNIEnv *e, jobject o, void *sock
+#define TCN_IMPCALL(X)  e, o, X->opaque
+
+#define TCN_IMPLEMENT_CALL(RT, CL, FN)  \
+    JNIEXPORT RT JNICALL Java_org_apache_tomcat_jni_##CL##_##FN
+
+#define TCN_IMPLEMENT_METHOD(RT, FN)    \
+    static RT method_##FN
+
+#define TCN_GETNET_METHOD(FN)  method_##FN
+
+#define TCN_SOCKET_UNKNOWN  0
+#define TCN_SOCKET_APR      1
+#define TCN_SOCKET_SSL      2
+#define TCN_SOCKET_UNIX     3
+#define TCN_SOCKET_NTPIPE   4
+
+#define TCN_SOCKET_GET_POOL 0
+#define TCN_SOCKET_GET_IMPL 1
+#define TCN_SOCKET_GET_APRS 2
+#define TCN_SOCKET_GET_TYPE 3
+
+typedef struct {
+    int type;
+    apr_status_t (*cleanup)(void *);
+    apr_status_t (APR_THREAD_FUNC *close) (apr_socket_t *);
+    apr_status_t (APR_THREAD_FUNC *shutdown) (apr_socket_t *, apr_shutdown_how_e);
+    apr_status_t (APR_THREAD_FUNC *opt_get)(apr_socket_t *, apr_int32_t, apr_int32_t *);
+    apr_status_t (APR_THREAD_FUNC *opt_set)(apr_socket_t *, apr_int32_t, apr_int32_t);
+    apr_status_t (APR_THREAD_FUNC *timeout_get)(apr_socket_t *, apr_interval_time_t *);
+    apr_status_t (APR_THREAD_FUNC *timeout_set)(apr_socket_t *, apr_interval_time_t);
+    apr_status_t (APR_THREAD_FUNC *send) (apr_socket_t *, const char *, apr_size_t *);
+    apr_status_t (APR_THREAD_FUNC *sendv)(apr_socket_t *, const struct iovec *, apr_int32_t, apr_size_t *);
+    apr_status_t (APR_THREAD_FUNC *recv) (apr_socket_t *, char *, apr_size_t *);
+} tcn_nlayer_t;
+
+typedef struct tcn_socket_t tcn_socket_t;
+typedef struct tcn_pfde_t   tcn_pfde_t;
+
+struct tcn_pfde_t {
+    APR_RING_ENTRY(tcn_pfde_t) link;
+    apr_pollfd_t fd;
+};
+
+struct tcn_socket_t {
+    apr_pool_t   *pool;
+    apr_pool_t   *child;
+    apr_socket_t *sock;
+    void         *opaque;
+    char         *jsbbuff;
+    char         *jrbbuff;
+    tcn_nlayer_t *net;
+    tcn_pfde_t   *pe;
+    apr_time_t          last_active;
+    apr_interval_time_t timeout;
+};
+
+/* Private helper functions */
+void            tcn_Throw(JNIEnv *, const char *, ...);
+void            tcn_ThrowException(JNIEnv *, const char *);
+void            tcn_ThrowMemoryException(JNIEnv *, const char *, int, const char *);
+void            tcn_ThrowAPRException(JNIEnv *, apr_status_t);
+jstring         tcn_new_string(JNIEnv *, const char *);
+jstring         tcn_new_stringn(JNIEnv *, const char *, size_t);
+jbyteArray      tcn_new_arrayb(JNIEnv *, const unsigned char *, size_t);
+jobjectArray    tcn_new_arrays(JNIEnv *env, size_t len);
+char           *tcn_get_string(JNIEnv *, jstring);
+char           *tcn_strdup(JNIEnv *, jstring);
+char           *tcn_pstrdup(JNIEnv *, jstring, apr_pool_t *);
+apr_status_t    tcn_load_finfo_class(JNIEnv *, jclass);
+apr_status_t    tcn_load_ainfo_class(JNIEnv *, jclass);
+
+#define J2S(V)  c##V
+#define J2L(V)  p##V
+
+#define J2T(T) (apr_time_t)((T))
+
+#define TCN_BEGIN_MACRO     if (1) {
+#define TCN_END_MACRO       } else (void)(0)
+
+#define TCN_ALLOC_CSTRING(V)     \
+    const char *c##V = V ? (const char *)((*e)->GetStringUTFChars(e, V, 0)) : NULL
+
+#define TCN_FREE_CSTRING(V)      \
+    if (c##V) (*e)->ReleaseStringUTFChars(e, V, c##V)
+
+#define TCN_ALLOC_JSTRING(V)     \
+    char *c##V = tcn_get_string(e, (V))
+
+#define AJP_TO_JSTRING(V)   (*e)->NewStringUTF((e), (V))
+
+#define TCN_FREE_JSTRING(V)      \
+    TCN_BEGIN_MACRO              \
+        if (c##V)                \
+            free(c##V);          \
+    TCN_END_MACRO
+
+#define TCN_CHECK_ALLOCATED(x)                              \
+        if (x == NULL) {                                    \
+            tcn_ThrowMemoryException(e, __FILE__, __LINE__, \
+            "APR memory allocation failed");                \
+            goto cleanup;                                   \
+        } else (void)(0)
+
+#define TCN_THROW_IF_ERR(x, r)                  \
+    TCN_BEGIN_MACRO                             \
+        apr_status_t R = (x);                   \
+        if (R != APR_SUCCESS) {                 \
+            tcn_ThrowAPRException(e, R);        \
+            (r) = 0;                            \
+            goto cleanup;                       \
+        }                                       \
+    TCN_END_MACRO
+
+#define TCN_THROW_OS_ERROR(E)   \
+    tcn_ThrowAPRException((E), apr_get_os_error())
+
+#define TCN_LOAD_CLASS(E, C, N, R)                  \
+    TCN_BEGIN_MACRO                                 \
+        jclass _##C = (*(E))->FindClass((E), N);    \
+        if (_##C == NULL) {                         \
+            (*(E))->ExceptionClear((E));            \
+            return R;                               \
+        }                                           \
+        C = (*(E))->NewGlobalRef((E), _##C);        \
+        (*(E))->DeleteLocalRef((E), _##C);          \
+    TCN_END_MACRO
+
+#define TCN_UNLOAD_CLASS(E, C)                      \
+        (*(E))->DeleteGlobalRef((E), (C))
+
+#define TCN_IS_NULL(E, O)                           \
+        ((*(E))->IsSameObject((E), (O), NULL) == JNI_TRUE)
+
+#define TCN_GET_METHOD(E, C, M, N, S, R)            \
+    TCN_BEGIN_MACRO                                 \
+        M = (*(E))->GetMethodID((E), C, N, S);      \
+        if (M == NULL) {                            \
+            return R;                               \
+        }                                           \
+    TCN_END_MACRO
+
+#define TCN_MAX_METHODS 8
+
+typedef struct {
+    jobject     obj;
+    jmethodID   mid[TCN_MAX_METHODS];
+    void        *opaque;
+} tcn_callback_t;
+
+#define TCN_MIN(a, b) ((a) < (b) ? (a) : (b))
+#define TCN_MAX(a, b) ((a) > (b) ? (a) : (b))
+
+#ifdef WIN32
+#define TCN_ALLOC_WSTRING(V)     \
+    jsize wl##V = (*e)->GetStringLength(e, V);   \
+    const jchar *ws##V = V ? (const jchar *)((*e)->GetStringChars(e, V, 0)) : NULL; \
+    jchar *w##V = NULL
+
+#define TCN_INIT_WSTRING(V)                                     \
+        w##V = (jchar *)malloc((wl##V + 1) * sizeof(jchar));    \
+        wcsncpy(w##V, ws##V, wl##V);                        \
+        w##V[wl##V] = 0
+
+#define TCN_FREE_WSTRING(V)      \
+    if (ws##V) (*e)->ReleaseStringChars(e, V, ws##V); \
+    if (ws##V) free (w##V)
+
+#define J2W(V)  w##V
+
+#endif
+
+#if  !APR_HAVE_IPV6
+#define APR_INET6 APR_INET
+#endif
+
+#define GET_S_FAMILY(T, F)           \
+    if (F == 0) T = APR_UNSPEC;      \
+    else if (F == 1) T = APR_INET;   \
+    else if (F == 2) T = APR_INET6;  \
+    else T = F
+
+#define GET_S_TYPE(T, F)             \
+    if (F == 0) T = SOCK_STREAM;     \
+    else if (F == 1) T = SOCK_DGRAM; \
+    else T = F
+
+#define TCN_NO_SOCKET_TIMEOUT -2
+
+#endif /* TCN_H */
diff --git a/c/tcn_api.h b/c/tcn_api.h
new file mode 100644
index 0000000..f5477fc
--- /dev/null
+++ b/c/tcn_api.h
@@ -0,0 +1,65 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: tcn_api.h 1442587 2013-02-05 13:49:48Z rjung $
+ */
+
+#ifndef TCN_API_H
+#define TCN_API_H
+
+#include "apr.h"
+#include "apr_general.h"
+#include "apr_pools.h"
+#include "apr_portable.h"
+#include "apr_network_io.h"
+#include "apr_strings.h"
+
+#ifndef APR_HAS_THREADS
+#error "Missing APR_HAS_THREADS support from APR."
+#endif
+#include <jni.h>
+
+/**
+ * @file tcn_api.h
+ * @brief
+ *
+ * Tomcat Native Public API
+ */
+
+/* Return global apr pool
+ */
+apr_pool_t *tcn_get_global_pool(void);
+
+/* Return global String class
+ */
+jclass tcn_get_string_class(void);
+
+/* Return global JVM initalized on JNI_OnLoad
+ */
+JavaVM *tcn_get_java_vm(void);
+
+/* Get current thread JNIEnv
+ */
+jint tcn_get_java_env(JNIEnv **);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TCN_API_H */
diff --git a/c/tcn_version.h b/c/tcn_version.h
new file mode 100644
index 0000000..bf5eb98
--- /dev/null
+++ b/c/tcn_version.h
@@ -0,0 +1,100 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: tcn_version.h 1667779 2015-03-19 14:43:26Z markt $
+ */
+
+#ifndef TCN_VERSION_H
+#define TCN_VERSION_H
+
+#include "apr_version.h"
+
+#include "tcn.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @file tcn_version.h
+ * @brief
+ *
+ * Tomcat Native Version
+ *
+ * There are several different mechanisms for accessing the version. There
+ * is a string form, and a set of numbers; in addition, there are constants
+ * which can be compiled into your application, and you can query the library
+ * being used for its actual version.
+ *
+ * Note that it is possible for an application to detect that it has been
+ * compiled against a different version of APU by use of the compile-time
+ * constants and the use of the run-time query function.
+ *
+ * TCN version numbering follows the guidelines specified in:
+ *
+ *     http://apr.apache.org/versioning.html
+ */
+
+/* The numeric compile-time version constants. These constants are the
+ * authoritative version numbers for TCN.
+ */
+
+/** major version
+ * Major API changes that could cause compatibility problems for older
+ * programs such as structure size changes.  No binary compatibility is
+ * possible across a change in the major version.
+ */
+#define TCN_MAJOR_VERSION       1
+
+/**
+ * Minor API changes that do not cause binary compatibility problems.
+ * Should be reset to 0 when upgrading TCN_MAJOR_VERSION
+ */
+#define TCN_MINOR_VERSION       1
+
+/** patch level */
+#define TCN_PATCH_VERSION       33
+
+/**
+ *  This symbol is defined for internal, "development" copies of TCN. This
+ *  symbol will be #undef'd for releases.
+ */
+#define TCN_IS_DEV_VERSION      0
+
+
+/** The formatted string of APU's version */
+#define TCN_VERSION_STRING \
+     APR_STRINGIFY(TCN_MAJOR_VERSION) "."\
+     APR_STRINGIFY(TCN_MINOR_VERSION) "."\
+     APR_STRINGIFY(TCN_PATCH_VERSION)\
+     TCN_IS_DEV_STRING
+
+/** Internal: string form of the "is dev" flag */
+#if TCN_IS_DEV_VERSION
+#define TCN_IS_DEV_STRING "-dev"
+#else
+#define TCN_IS_DEV_STRING ""
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TCN_VERSION_H */
+
diff --git a/c/thread.c b/c/thread.c
new file mode 100644
index 0000000..ccd6c78
--- /dev/null
+++ b/c/thread.c
@@ -0,0 +1,29 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: thread.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+
+#include "tcn.h"
+
+TCN_IMPLEMENT_CALL(jlong, Thread, current)(TCN_STDARGS)
+{
+    UNREFERENCED_STDARGS;
+    return (jlong)((unsigned long)apr_os_thread_current());
+}
diff --git a/c/user.c b/c/user.c
new file mode 100644
index 0000000..d3e7d2a
--- /dev/null
+++ b/c/user.c
@@ -0,0 +1,163 @@
+/* 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.
+ */
+
+/*
+ *
+ * @author Mladen Turk
+ * @version $Id: user.c 1442587 2013-02-05 13:49:48Z rjung $
+ */
+ 
+#include "tcn.h"
+#include "apr_user.h"
+#include "apr_network_io.h"
+
+TCN_IMPLEMENT_CALL(jlong, User, uidCurrent)(TCN_STDARGS, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_uid_t uid;
+    apr_gid_t gid;
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_uid_current(&uid, &gid, p), uid);
+
+cleanup:
+    return (jlong)uid;
+}
+
+TCN_IMPLEMENT_CALL(jlong, User, gidCurrent)(TCN_STDARGS, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_uid_t uid;
+    apr_gid_t gid;
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_uid_current(&uid, &gid, p), gid);
+
+cleanup:
+    return (jlong)gid;
+}
+
+TCN_IMPLEMENT_CALL(jlong, User, uid)(TCN_STDARGS, jstring uname, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_uid_t uid;
+    apr_gid_t gid;
+    TCN_ALLOC_CSTRING(uname);
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_uid_get(&uid, &gid, J2S(uname), p), uid);
+
+cleanup:
+    TCN_FREE_CSTRING(uname);
+    return (jlong)uid;
+}
+
+TCN_IMPLEMENT_CALL(jlong, User, usergid)(TCN_STDARGS, jstring uname,
+                                         jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_uid_t uid;
+    apr_gid_t gid;
+    TCN_ALLOC_CSTRING(uname);
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_uid_get(&uid, &gid, J2S(uname), p), gid);
+
+cleanup:
+    TCN_FREE_CSTRING(uname);
+    return (jlong)gid;
+}
+
+TCN_IMPLEMENT_CALL(jlong, User, gid)(TCN_STDARGS, jstring gname, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_gid_t gid;
+    TCN_ALLOC_CSTRING(gname);
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR( apr_gid_get(&gid, J2S(gname), p), gid);
+
+cleanup:
+    TCN_FREE_CSTRING(gname);
+    return (jlong)gid;
+}
+
+TCN_IMPLEMENT_CALL(jstring, User, username)(TCN_STDARGS, jlong userid, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_uid_t uid = (apr_uid_t)userid;
+    char *uname = NULL;
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_uid_name_get(&uname, uid, p), uname);
+
+cleanup:
+    if (uname)
+        return AJP_TO_JSTRING(uname);
+    else
+        return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jstring, User, groupname)(TCN_STDARGS, jlong grpid, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    apr_gid_t gid = (apr_uid_t)grpid;
+    char *gname = NULL;
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_gid_name_get(&gname, gid, p), gname);
+
+cleanup:
+    if (gname)
+        return AJP_TO_JSTRING(gname);
+    else
+        return NULL;
+}
+
+TCN_IMPLEMENT_CALL(jint, User,uidcompare)(TCN_STDARGS, jlong left, jlong right)
+{
+
+    UNREFERENCED_STDARGS;
+    return (int)apr_uid_compare((apr_uid_t)left,
+                                (apr_uid_t)right);
+}
+
+TCN_IMPLEMENT_CALL(jint, User,gidcompare)(TCN_STDARGS, jlong left, jlong right)
+{
+
+    UNREFERENCED_STDARGS;
+    return (int)apr_gid_compare((apr_gid_t)left,
+                                (apr_gid_t)right);
+}
+
+TCN_IMPLEMENT_CALL(jstring, User, homepath)(TCN_STDARGS, jstring uname, jlong pool)
+{
+    apr_pool_t *p = J2P(pool, apr_pool_t *);
+    char *dirname = NULL;
+    TCN_ALLOC_CSTRING(uname);
+
+    UNREFERENCED(o);
+    TCN_THROW_IF_ERR(apr_uid_homepath_get(&dirname, J2S(uname),
+                                          p), dirname);
+
+cleanup:
+    TCN_FREE_CSTRING(uname);
+    if (dirname)
+        return AJP_TO_JSTRING(dirname);
+    else
+        return NULL;
+}
+
diff --git a/java/src/org/apache/tomcat/Apr.java b/java/src/org/apache/tomcat/Apr.java
new file mode 100644
index 0000000..53d5d06
--- /dev/null
+++ b/java/src/org/apache/tomcat/Apr.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.tomcat;
+
+import java.io.InputStream;
+import java.util.Properties;
+
+public class Apr {
+    private static String aprInfo = null;
+
+    static {
+
+        try {
+            InputStream is = Apr.class.getResourceAsStream
+                ("/org/apache/tomcat/apr.properties");
+            Properties props = new Properties();
+            props.load(is);
+            is.close();
+            aprInfo = props.getProperty("tcn.info");
+        }
+        catch (Throwable t) {
+            ; // Nothing
+        }
+    }
+}
diff --git a/java/src/org/apache/tomcat/jni/Address.java b/java/src/org/apache/tomcat/jni/Address.java
new file mode 100644
index 0000000..d04be3b
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Address.java
@@ -0,0 +1,112 @@
+/*
+ *  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.tomcat.jni;
+
+/** Address
+ *
+ * @author Mladen Turk
+ */
+public class Address {
+
+    public static final String APR_ANYADDR = "0.0.0.0";
+    /**
+     * Fill the Sockaddr class from apr_sockaddr_t
+     * @param info Sockaddr class to fill
+     * @param sa Structure pointer
+     */
+    public static native boolean fill(Sockaddr info, long sa);
+
+    /**
+     * Create the Sockaddr object from apr_sockaddr_t
+     * @param sa Structure pointer
+     */
+    public static native Sockaddr getInfo(long sa);
+
+    /**
+     * Create apr_sockaddr_t from hostname, address family, and port.
+     * @param hostname The hostname or numeric address string to resolve/parse, or
+     *               NULL to build an address that corresponds to 0.0.0.0 or ::
+     * @param family The address family to use, or APR_UNSPEC if the system should
+     *               decide.
+     * @param port The port number.
+     * @param flags Special processing flags:
+     * <PRE>
+     *       APR_IPV4_ADDR_OK          first query for IPv4 addresses; only look
+     *                                 for IPv6 addresses if the first query failed;
+     *                                 only valid if family is APR_UNSPEC and hostname
+     *                                 isn't NULL; mutually exclusive with
+     *                                 APR_IPV6_ADDR_OK
+     *       APR_IPV6_ADDR_OK          first query for IPv6 addresses; only look
+     *                                 for IPv4 addresses if the first query failed;
+     *                                 only valid if family is APR_UNSPEC and hostname
+     *                                 isn't NULL and APR_HAVE_IPV6; mutually exclusive
+     *                                 with APR_IPV4_ADDR_OK
+     * </PRE>
+     * @param p The pool for the apr_sockaddr_t and associated storage.
+     * @return The new apr_sockaddr_t.
+     */
+    public static native long info(String hostname, int family,
+                                   int port, int flags, long p)
+        throws Exception;
+    /**
+     * Look up the host name from an apr_sockaddr_t.
+     * @param sa The apr_sockaddr_t.
+     * @param flags Special processing flags.
+     * @return The hostname.
+     */
+    public static native String getnameinfo(long sa, int flags);
+
+    /**
+     * Return the IP address (in numeric address string format) in
+     * an APR socket address.  APR will allocate storage for the IP address
+     * string from the pool of the apr_sockaddr_t.
+     * @param sa The socket address to reference.
+     * @return The IP address.
+     */
+    public static native String getip(long sa);
+
+    /**
+     * Given an apr_sockaddr_t and a service name, set the port for the service
+     * @param sockaddr The apr_sockaddr_t that will have its port set
+     * @param servname The name of the service you wish to use
+     * @return APR status code.
+     */
+    public static native int getservbyname(long sockaddr, String servname);
+
+    /**
+     * Return an apr_sockaddr_t from an apr_socket_t
+     * @param which Which interface do we want the apr_sockaddr_t for?
+     * @param sock The socket to use
+     * @return The returned apr_sockaddr_t.
+     */
+    public static native long get(int which, long sock)
+        throws Exception;
+
+    /**
+     * See if the IP addresses in two APR socket addresses are
+     * equivalent.  Appropriate logic is present for comparing
+     * IPv4-mapped IPv6 addresses with IPv4 addresses.
+     *
+     * @param a One of the APR socket addresses.
+     * @param b The other APR socket address.
+     * The return value will be True if the addresses
+     * are equivalent.
+     */
+    public static native boolean equal(long a, long b);
+
+}
diff --git a/java/src/org/apache/tomcat/jni/BIOCallback.java b/java/src/org/apache/tomcat/jni/BIOCallback.java
new file mode 100644
index 0000000..ee13c70
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/BIOCallback.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.tomcat.jni;
+
+/** Open SSL BIO Callback Interface
+ *
+ * @author Mladen Turk
+ */
+public interface BIOCallback {
+
+    /**
+     * Write data
+     * @param buf containing the bytes to write.
+     * @return Number of characters written.
+     */
+    public int write(byte [] buf);
+
+    /**
+     * Read data
+     * @param buf buffer to store the read bytes.
+     * @return number of bytes read.
+     */
+    public int read(byte [] buf);
+
+    /**
+     * Puts string
+     * @param data String to write
+     * @return Number of characters written
+     */
+    public int puts(String data);
+
+    /**
+     * Read string up to the len or CLRLF
+     * @param len Maximum number of characters to read
+     * @return String with up to len bytes read
+     */
+    public String gets(int len);
+
+}
diff --git a/java/src/org/apache/tomcat/jni/Buffer.java b/java/src/org/apache/tomcat/jni/Buffer.java
new file mode 100644
index 0000000..703bfca
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Buffer.java
@@ -0,0 +1,89 @@
+/*
+ *  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.tomcat.jni;
+
+import java.nio.ByteBuffer;
+
+/** Buffer
+ *
+ * @author Mladen Turk
+ */
+public class Buffer {
+
+    /**
+     * Allocate a new ByteBuffer from memory
+     * @param size The amount of memory to allocate
+     * @return The ByteBuffer with allocated memory
+     */
+    public static native ByteBuffer malloc(int size);
+
+    /**
+     * Allocate a new ByteBuffer from memory and set all of the memory to 0
+     * @param num Number of elements.
+     * @param size Length in bytes of each element.
+     * @return The ByteBuffer with allocated memory
+     */
+    public static native ByteBuffer calloc(int num, int size);
+
+    /**
+     * Allocate a new ByteBuffer from a pool
+     * @param p The pool to allocate from
+     * @param size The amount of memory to allocate
+     * @return The ByteBuffer with allocated memory
+     */
+    public static native ByteBuffer palloc(long p, int size);
+
+    /**
+     * Allocate a new ByteBuffer from a pool and set all of the memory to 0
+     * @param p The pool to allocate from
+     * @param size The amount of memory to allocate
+     * @return The ByteBuffer with allocated memory
+     */
+    public static native ByteBuffer pcalloc(long p, int size);
+
+    /**
+     * Allocate a new ByteBuffer from already allocated memory.
+     * <br>Allocated memory must be provided from call to the
+     * Stdlib.alloc or Stdlib.calloc methods.
+     * @param mem The memory to use
+     * @param size The amount of memory to use
+     * @return The ByteBuffer with attached memory
+     */
+    public static native ByteBuffer create(long mem, int size);
+
+    /**
+     * Deallocates or frees a memory block used by ByteBuffer
+     * <br><b>Warning :</b> Call this method only on ByteBuffers
+     * that were created by calling Buffer.alloc or Buffer.calloc.
+     * @param buf Previously allocated ByteBuffer to be freed.
+     */
+    public static native void free(ByteBuffer buf);
+
+    /**
+     * Returns the memory address of the ByteBuffer.
+     * @param buf Previously allocated ByteBuffer.
+     */
+    public static native long address(ByteBuffer buf);
+
+    /**
+     * Returns the allocated memory size of the ByteBuffer.
+     * @param buf Previously allocated ByteBuffer.
+     */
+    public static native long size(ByteBuffer buf);
+
+}
diff --git a/java/src/org/apache/tomcat/jni/CertificateVerifier.java b/java/src/org/apache/tomcat/jni/CertificateVerifier.java
new file mode 100644
index 0000000..1d82e2b
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/CertificateVerifier.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project 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.tomcat.jni;
+
+/**
+ * Is called during handshake and hooked into openssl via {@code SSL_CTX_set_cert_verify_callback}.
+ */
+public interface CertificateVerifier {
+
+    /**
+     * Returns {@code true} if the passed in certificate chain could be verified and so the handshake
+     * should be successful, {@code false} otherwise.
+     *
+     * @param ssl               the SSL instance
+     * @param x509              the {@code X509} certificate chain
+     * @param authAlgorithm     the auth algorithm
+     * @return verified         {@code true} if verified successful, {@code false} otherwise
+     */
+    boolean verify(long ssl, byte[][] x509, String authAlgorithm);
+}
diff --git a/java/src/org/apache/tomcat/jni/Directory.java b/java/src/org/apache/tomcat/jni/Directory.java
new file mode 100644
index 0000000..7c2de6e
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Directory.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.tomcat.jni;
+
+/** Directory
+ *
+ * @author Mladen Turk
+ */
+public class Directory {
+
+    /**
+     * Create a new directory on the file system.
+     * @param path the path for the directory to be created. (use / on all systems)
+     * @param perm Permissions for the new directory.
+     * @param pool the pool to use.
+     */
+    public static native int make(String path, int perm, long pool);
+
+    /** Creates a new directory on the file system, but behaves like
+     * 'mkdir -p'. Creates intermediate directories as required. No error
+     * will be reported if PATH already exists.
+     * @param path the path for the directory to be created. (use / on all systems)
+     * @param perm Permissions for the new directory.
+     * @param pool the pool to use.
+     */
+    public static native int makeRecursive(String path, int perm, long pool);
+
+    /**
+     * Remove directory from the file system.
+     * @param path the path for the directory to be removed. (use / on all systems)
+     * @param pool the pool to use.
+     */
+    public static native int remove(String path, long pool);
+
+    /**
+     * Find an existing directory suitable as a temporary storage location.
+     * @param pool The pool to use for any necessary allocations.
+     * @return The temp directory.
+     *
+     * This function uses an algorithm to search for a directory that an
+     * an application can use for temporary storage.  Once such a
+     * directory is found, that location is cached by the library.  Thus,
+     * callers only pay the cost of this algorithm once if that one time
+     * is successful.
+     *
+     */
+    public static native String tempGet(long pool);
+
+    /**
+     * Open the specified directory.
+     * @param dirname The full path to the directory (use / on all systems)
+     * @param pool The pool to use.
+     * @return The opened directory descriptor.
+     */
+    public static native long open(String dirname, long pool)
+        throws Error;
+
+    /**
+     * close the specified directory.
+     * @param thedir the directory descriptor to close.
+     */
+    public static native int close(long thedir);
+
+    /**
+     * Rewind the directory to the first entry.
+     * @param thedir the directory descriptor to rewind.
+     */
+    public static native int rewind(long thedir);
+
+
+    /**
+     * Read the next entry from the specified directory.
+     * @param finfo the file info structure and filled in by apr_dir_read
+     * @param wanted The desired apr_finfo_t fields, as a bit flag of APR_FINFO_ values
+     * @param thedir the directory descriptor returned from apr_dir_open
+     * No ordering is guaranteed for the entries read.
+     */
+    public static native int read(FileInfo finfo, int wanted, long thedir);
+
+}
diff --git a/java/src/org/apache/tomcat/jni/Error.java b/java/src/org/apache/tomcat/jni/Error.java
new file mode 100644
index 0000000..d543258
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Error.java
@@ -0,0 +1,96 @@
+/*
+ *  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.tomcat.jni;
+
+/** Error
+ *
+ * @author Mladen Turk
+ */
+public class Error extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * APR error type.
+     */
+    private final int error;
+
+    /**
+     * A description of the problem.
+     */
+    private final String description;
+
+    /**
+     * Construct an APRException.
+     *
+     * @param error one of the value in Error
+     * @param description error message
+     */
+    private Error(int error, String description)
+    {
+        super(error + ": " + description);
+        this.error = error;
+        this.description = description;
+    }
+
+    /**
+     * Get the APR error code of the exception.
+     *
+     * @return error of the Exception
+     */
+    public int getError()
+    {
+        return error;
+    }
+
+    /**
+     * Get the APR description of the exception.
+     *
+     * @return description of the Exception
+     */
+    public String getDescription()
+    {
+        return description;
+    }
+
+    /**
+     * Get the last platform error.
+     * @return apr_status_t the last platform error, folded into apr_status_t, on most platforms
+     * This retrieves errno, or calls a GetLastError() style function, and
+     *      folds it with APR_FROM_OS_ERROR.  Some platforms (such as OS2) have no
+     *      such mechanism, so this call may be unsupported.  Do NOT use this
+     *      call for socket errors from socket, send, recv etc!
+     */
+    public static native int osError();
+
+    /**
+     * Get the last platform socket error.
+     * @return the last socket error, folded into apr_status_t, on all platforms
+     * This retrieves errno or calls a GetLastSocketError() style function,
+     *      and folds it with APR_FROM_OS_ERROR.
+     */
+    public static native int netosError();
+
+    /**
+     * Return a human readable string describing the specified error.
+     * @param statcode The error code the get a string for.
+     * @return The error string.
+    */
+    public static native String strerror(int statcode);
+
+}
diff --git a/java/src/org/apache/tomcat/jni/File.java b/java/src/org/apache/tomcat/jni/File.java
new file mode 100644
index 0000000..1117e40
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/File.java
@@ -0,0 +1,721 @@
+/*
+ *  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.tomcat.jni;
+/* Import needed classes */
+import java.nio.ByteBuffer;
+
+/** File
+ *
+ * @author Mladen Turk
+ */
+public class File {
+
+    /** Open the file for reading */
+    public static final int APR_FOPEN_READ       = 0x00001;
+    /** Open the file for writing */
+    public static final int APR_FOPEN_WRITE      = 0x00002;
+    /** Create the file if not there */
+    public static final int APR_FOPEN_CREATE     = 0x00004;
+    /** Append to the end of the file */
+    public static final int APR_FOPEN_APPEND     = 0x00008;
+    /** Open the file and truncate to 0 length */
+    public static final int APR_FOPEN_TRUNCATE   = 0x00010;
+    /** Open the file in binary mode */
+    public static final int APR_FOPEN_BINARY     = 0x00020;
+    /** Open should fail if APR_CREATE and file exists. */
+    public static final int APR_FOPEN_EXCL       = 0x00040;
+    /** Open the file for buffered I/O */
+    public static final int APR_FOPEN_BUFFERED   = 0x00080;
+    /** Delete the file after close */
+    public static final int APR_FOPEN_DELONCLOSE = 0x00100;
+    /** Platform dependent tag to open the file for
+     * use across multiple threads
+     */
+    public static final int APR_FOPEN_XTHREAD     = 0x00200;
+    /** Platform dependent support for higher level locked read/write
+     * access to support writes across process/machines
+     */
+    public static final int APR_FOPEN_SHARELOCK   = 0x00400;
+    /** Do not register a cleanup when the file is opened */
+    public static final int APR_FOPEN_NOCLEANUP   = 0x00800;
+    /** Advisory flag that this file should support
+     * apr_socket_sendfile operation
+     */
+    public static final int APR_FOPEN_SENDFILE_ENABLED = 0x01000;
+    /** Platform dependent flag to enable large file support;
+     * <br><b>Warning :</b> The APR_LARGEFILE flag only has effect on some platforms
+     * where sizeof(apr_off_t) == 4.  Where implemented, it allows opening
+     * and writing to a file which exceeds the size which can be
+     * represented by apr_off_t (2 gigabytes).  When a file's size does
+     * exceed 2Gb, apr_file_info_get() will fail with an error on the
+     * descriptor, likewise apr_stat()/apr_lstat() will fail on the
+     * filename.  apr_dir_read() will fail with APR_INCOMPLETE on a
+     * directory entry for a large file depending on the particular
+     * APR_FINFO_* flags.  Generally, it is not recommended to use this
+     * flag.
+     */
+    public static final int APR_FOPEN_LARGEFILE      = 0x04000;
+
+    /** Set the file position */
+    public static final int APR_SET = 0;
+    /** Current */
+    public static final int APR_CUR = 1;
+    /** Go to end of file */
+    public static final int APR_END = 2;
+
+    /* flags for apr_file_attrs_set */
+
+    /** File is read-only */
+    public static final int APR_FILE_ATTR_READONLY   = 0x01;
+    /** File is executable */
+    public static final int APR_FILE_ATTR_EXECUTABLE = 0x02;
+    /** File is hidden */
+    public static final int APR_FILE_ATTR_HIDDEN     = 0x04;
+
+
+    /* File lock types/flags */
+
+    /** Shared lock. More than one process or thread can hold a shared lock
+     * at any given time. Essentially, this is a "read lock", preventing
+     * writers from establishing an exclusive lock.
+     */
+    public static final int APR_FLOCK_SHARED    = 1;
+
+    /** Exclusive lock. Only one process may hold an exclusive lock at any
+     * given time. This is analogous to a "write lock".
+     */
+    public static final int APR_FLOCK_EXCLUSIVE = 2;
+    /** mask to extract lock type */
+    public static final int APR_FLOCK_TYPEMASK  = 0x000F;
+    /** do not block while acquiring the file lock */
+    public static final int APR_FLOCK_NONBLOCK  = 0x0010;
+
+    /* apr_filetype_e values for the filetype member of the
+     * apr_file_info_t structure
+     * <br><b>Warning :</b>: Not all of the filetypes below can be determined.
+     * For example, a given platform might not correctly report
+     * a socket descriptor as APR_SOCK if that type isn't
+     * well-identified on that platform.  In such cases where
+     * a filetype exists but cannot be described by the recognized
+     * flags below, the filetype will be APR_UNKFILE.  If the
+     * filetype member is not determined, the type will be APR_NOFILE.
+     */
+
+    /** no file type determined */
+    public static final int APR_NOFILE  = 0;
+    /** a regular file */
+    public static final int APR_REG     = 1;
+    /** a directory */
+    public static final int APR_DIR     = 2;
+    /** a character device */
+    public static final int APR_CHR     = 3;
+    /** a block device */
+    public static final int APR_BLK     = 4;
+    /** a FIFO / pipe */
+    public static final int APR_PIPE    = 5;
+    /** a symbolic link */
+    public static final int APR_LNK     = 6;
+    /** a [unix domain] socket */
+    public static final int APR_SOCK    = 7;
+    /** a file of some other unknown type */
+    public static final int APR_UNKFILE = 127;
+
+
+    /*
+     * apr_file_permissions File Permissions flags
+     */
+
+    public static final int APR_FPROT_USETID     = 0x8000; /** Set user id */
+    public static final int APR_FPROT_UREAD      = 0x0400; /** Read by user */
+    public static final int APR_FPROT_UWRITE     = 0x0200; /** Write by user */
+    public static final int APR_FPROT_UEXECUTE   = 0x0100; /** Execute by user */
+
+    public static final int APR_FPROT_GSETID     = 0x4000; /** Set group id */
+    public static final int APR_FPROT_GREAD      = 0x0040; /** Read by group */
+    public static final int APR_FPROT_GWRITE     = 0x0020; /** Write by group */
+    public static final int APR_FPROT_GEXECUTE   = 0x0010; /** Execute by group */
+
+    public static final int APR_FPROT_WSTICKY    = 0x2000; /** Sticky bit */
+    public static final int APR_FPROT_WREAD      = 0x0004; /** Read by others */
+    public static final int APR_FPROT_WWRITE     = 0x0002; /** Write by others */
+    public static final int APR_FPROT_WEXECUTE   = 0x0001; /** Execute by others */
+    public static final int APR_FPROT_OS_DEFAULT = 0x0FFF; /** use OS's default permissions */
+
+
+    public static final int APR_FINFO_LINK   = 0x00000001; /** Stat the link not the file itself if it is a link */
+    public static final int APR_FINFO_MTIME  = 0x00000010; /** Modification Time */
+    public static final int APR_FINFO_CTIME  = 0x00000020; /** Creation or inode-changed time */
+    public static final int APR_FINFO_ATIME  = 0x00000040; /** Access Time */
+    public static final int APR_FINFO_SIZE   = 0x00000100; /** Size of the file */
+    public static final int APR_FINFO_CSIZE  = 0x00000200; /** Storage size consumed by the file */
+    public static final int APR_FINFO_DEV    = 0x00001000; /** Device */
+    public static final int APR_FINFO_INODE  = 0x00002000; /** Inode */
+    public static final int APR_FINFO_NLINK  = 0x00004000; /** Number of links */
+    public static final int APR_FINFO_TYPE   = 0x00008000; /** Type */
+    public static final int APR_FINFO_USER   = 0x00010000; /** User */
+    public static final int APR_FINFO_GROUP  = 0x00020000; /** Group */
+    public static final int APR_FINFO_UPROT  = 0x00100000; /** User protection bits */
+    public static final int APR_FINFO_GPROT  = 0x00200000; /** Group protection bits */
+    public static final int APR_FINFO_WPROT  = 0x00400000; /** World protection bits */
+    public static final int APR_FINFO_ICASE  = 0x01000000; /** if dev is case insensitive */
+    public static final int APR_FINFO_NAME   = 0x02000000; /** -&gt;name in proper case */
+
+    public static final int APR_FINFO_MIN    = 0x00008170; /** type, mtime, ctime, atime, size */
+    public static final int APR_FINFO_IDENT  = 0x00003000; /** dev and inode */
+    public static final int APR_FINFO_OWNER  = 0x00030000; /** user and group */
+    public static final int APR_FINFO_PROT   = 0x00700000; /**  all protections */
+    public static final int APR_FINFO_NORM   = 0x0073b170; /**  an atomic unix apr_stat() */
+    public static final int APR_FINFO_DIRENT = 0x02000000; /**  an atomic unix apr_dir_read() */
+
+
+
+    /**
+     * Open the specified file.
+     * @param fname The full path to the file (using / on all systems)
+     * @param flag Or'ed value of:
+     * <PRE>
+     * APR_FOPEN_READ              open for reading
+     * APR_FOPEN_WRITE             open for writing
+     * APR_FOPEN_CREATE            create the file if not there
+     * APR_FOPEN_APPEND            file ptr is set to end prior to all writes
+     * APR_FOPEN_TRUNCATE          set length to zero if file exists
+     * APR_FOPEN_BINARY            not a text file (This flag is ignored on
+     *                             UNIX because it has no meaning)
+     * APR_FOPEN_BUFFERED          buffer the data.  Default is non-buffered
+     * APR_FOPEN_EXCL              return error if APR_CREATE and file exists
+     * APR_FOPEN_DELONCLOSE        delete the file after closing.
+     * APR_FOPEN_XTHREAD           Platform dependent tag to open the file
+     *                             for use across multiple threads
+     * APR_FOPEN_SHARELOCK         Platform dependent support for higher
+     *                             level locked read/write access to support
+     *                             writes across process/machines
+     * APR_FOPEN_NOCLEANUP         Do not register a cleanup with the pool
+     *                             passed in on the <EM>pool</EM> argument (see below).
+     *                             The apr_os_file_t handle in apr_file_t will not
+     *                             be closed when the pool is destroyed.
+     * APR_FOPEN_SENDFILE_ENABLED  Open with appropriate platform semantics
+     *                             for sendfile operations.  Advisory only,
+     *                             apr_socket_sendfile does not check this flag.
+     * </PRE>
+     * @param perm Access permissions for file.
+     * @param pool The pool to use.
+     * If perm is APR_OS_DEFAULT and the file is being created,
+     * appropriate default permissions will be used.
+     * @return The opened file descriptor.
+     */
+    public static native long open(String fname, int flag, int perm, long pool)
+        throws Error;
+
+    /**
+     * Close the specified file.
+     * @param file The file descriptor to close.
+     */
+    public static native int close(long file);
+
+    /**
+     * Flush the file's buffer.
+     * @param thefile The file descriptor to flush
+     */
+    public static native int flush(long thefile);
+
+    /**
+     * Open a temporary file
+     * @param templ The template to use when creating a temp file.
+     * @param flags The flags to open the file with. If this is zero,
+     *              the file is opened with
+     *              APR_CREATE | APR_READ | APR_WRITE | APR_EXCL | APR_DELONCLOSE
+     * @param pool The pool to allocate the file out of.
+     * @return The apr file to use as a temporary file.
+     *
+     * This function  generates  a unique temporary file name from template.
+     * The last six characters of template must be XXXXXX and these are replaced
+     * with a string that makes the filename unique. Since it will  be  modified,
+     * template must not be a string constant, but should be declared as a character
+     * array.
+     *
+     */
+    public static native long mktemp(String templ, int flags, long pool)
+        throws Error;
+
+    /**
+     * Delete the specified file.
+     * @param path The full path to the file (using / on all systems)
+     * @param pool The pool to use.
+     * If the file is open, it won't be removed until all
+     * instances are closed.
+     */
+    public static native int remove(String path, long pool);
+
+    /**
+     * Rename the specified file.
+     * <br><b>Warning :</b> If a file exists at the new location, then it will be
+     * overwritten.  Moving files or directories across devices may not be
+     * possible.
+     * @param fromPath The full path to the original file (using / on all systems)
+     * @param toPath The full path to the new file (using / on all systems)
+     * @param pool The pool to use.
+     */
+    public static native int rename(String fromPath, String toPath, long pool);
+
+    /**
+     * Copy the specified file to another file.
+     * The new file does not need to exist, it will be created if required.
+     * <br><b>Warning :</b> If the new file already exists, its contents will be overwritten.
+     * @param fromPath The full path to the original file (using / on all systems)
+     * @param toPath The full path to the new file (using / on all systems)
+     * @param perms Access permissions for the new file if it is created.
+     *     In place of the usual or'd combination of file permissions, the
+     *     value APR_FILE_SOURCE_PERMS may be given, in which case the source
+     *     file's permissions are copied.
+     * @param pool The pool to use.
+     */
+    public static native int copy(String fromPath, String toPath, int perms, long pool);
+
+    /**
+     * Append the specified file to another file.
+     * The new file does not need to exist, it will be created if required.
+     * @param fromPath The full path to the source file (use / on all systems)
+     * @param toPath The full path to the destination file (use / on all systems)
+     * @param perms Access permissions for the destination file if it is created.
+     *     In place of the usual or'd combination of file permissions, the
+     *     value APR_FILE_SOURCE_PERMS may be given, in which case the source
+     *     file's permissions are copied.
+     * @param pool The pool to use.
+     */
+    public static native int append(String fromPath, String toPath, int perms, long pool);
+
+    /**
+     * Write the string into the specified file.
+     * @param str The string to write. Must be NUL terminated!
+     * @param thefile The file descriptor to write to
+     */
+    public static native int puts(byte [] str, long thefile);
+
+    /**
+     * Move the read/write file offset to a specified byte within a file.
+     * @param thefile The file descriptor
+     * @param where How to move the pointer, one of:
+     * <PRE>
+     * APR_SET  --  set the offset to offset
+     * APR_CUR  --  add the offset to the current position
+     * APR_END  --  add the offset to the current file size
+     * </PRE>
+     * @param offset The offset to move the pointer to.
+     * @return Offset the pointer was actually moved to.
+     */
+    public static native long seek(long thefile, int where, long offset)
+        throws Error;
+
+    /**
+     * Write a character into the specified file.
+     * @param ch The character to write.
+     * @param thefile The file descriptor to write to
+     */
+    public static native int putc(byte ch, long thefile);
+
+    /**
+     * Put a character back onto a specified stream.
+     * @param ch The character to write.
+     * @param thefile The file descriptor to write to
+     */
+    public static native int ungetc(byte ch, long thefile);
+
+    /**
+     * Write data to the specified file.
+     *
+     * Write will write up to the specified number of
+     * bytes, but never more.  If the OS cannot write that many bytes, it
+     * will write as many as it can.  The third argument is modified to
+     * reflect the * number of bytes written.
+     *
+     * It is possible for both bytes to be written and an error to
+     * be returned.  APR_EINTR is never returned.
+     * @param thefile The file descriptor to write to.
+     * @param buf The buffer which contains the data.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to write; (-1) for full array.
+     * @return The number of bytes written.
+     */
+    public static native int write(long thefile, byte[] buf, int offset, int nbytes);
+
+    /**
+     * Write data to the specified file.
+     *
+     * Write will write up to the specified number of
+     * bytes, but never more.  If the OS cannot write that many bytes, it
+     * will write as many as it can.  The third argument is modified to
+     * reflect the * number of bytes written.
+     *
+     * It is possible for both bytes to be written and an error to
+     * be returned.  APR_EINTR is never returned.
+     * @param thefile The file descriptor to write to.
+     * @param buf The direct Byte buffer which contains the data.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to write
+     * @return The number of bytes written.
+     */
+    public static native int writeb(long thefile, ByteBuffer buf, int offset, int nbytes);
+
+    /**
+     * Write data to the specified file, ensuring that all of the data is
+     * written before returning.
+     *
+     * Write will write up to the specified number of
+     * bytes, but never more.  If the OS cannot write that many bytes, the
+     * process/thread will block until they can be written. Exceptional
+     * error such as "out of space" or "pipe closed" will terminate with
+     * an error.
+     *
+     * It is possible for both bytes to be written and an error to
+     * be returned.  And if *bytes_written is less than nbytes, an
+     * accompanying error is _always_ returned.
+     *
+     * APR_EINTR is never returned.
+     * @param thefile The file descriptor to write to.
+     * @param buf The buffer which contains the data.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to write; (-1) for full array.
+     * @return The number of bytes written.
+     */
+    public static native int writeFull(long thefile, byte[] buf, int offset, int nbytes);
+
+    /**
+     * Write data to the specified file, ensuring that all of the data is
+     * written before returning.
+     *
+     * Write will write up to the specified number of
+     * bytes, but never more.  If the OS cannot write that many bytes, the
+     * process/thread will block until they can be written. Exceptional
+     * error such as "out of space" or "pipe closed" will terminate with
+     * an error.
+     *
+     * It is possible for both bytes to be written and an error to
+     * be returned.  And if *bytes_written is less than nbytes, an
+     * accompanying error is _always_ returned.
+     *
+     * APR_EINTR is never returned.
+     * @param thefile The file descriptor to write to.
+     * @param buf The direct ByteBuffer which contains the data.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to write.
+     * @return The number of bytes written.
+     */
+    public static native int writeFullb(long thefile, ByteBuffer buf, int offset, int nbytes);
+
+    /**
+     * Write data from array of byte arrays to the specified file.
+     *
+     * It is possible for both bytes to be written and an error to
+     * be returned.  APR_EINTR is never returned.
+     *
+     * apr_file_writev is available even if the underlying
+     * operating system doesn't provide writev().
+     * @param thefile The file descriptor to write to.
+     * @param vec The array from which to get the data to write to the file.
+     * @return The number of bytes written.
+     */
+    public static native int writev(long thefile, byte[][] vec);
+
+    /**
+     * Write data from array of byte arrays to the specified file,
+     * ensuring that all of the data is written before returning.
+     *
+     * writevFull is available even if the underlying
+     * operating system doesn't provide writev().
+     * @param thefile The file descriptor to write to.
+     * @param vec The array from which to get the data to write to the file.
+     * @return The number of bytes written.
+     */
+    public static native int writevFull(long thefile, byte[][] vec);
+
+    /**
+     * Read data from the specified file.
+     *
+     * apr_file_read will read up to the specified number of
+     * bytes, but never more.  If there isn't enough data to fill that
+     * number of bytes, all of the available data is read.  The third
+     * argument is modified to reflect the number of bytes read.  If a
+     * char was put back into the stream via ungetc, it will be the first
+     * character returned.
+     *
+     * It is not possible for both bytes to be read and an APR_EOF
+     * or other error to be returned.  APR_EINTR is never returned.
+     * @param thefile The file descriptor to read from.
+     * @param buf The buffer to store the data to.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @return the number of bytes read.
+     */
+    public static native int read(long thefile, byte[] buf,  int offset, int nbytes);
+
+    /**
+     * Read data from the specified file.
+     *
+     * apr_file_read will read up to the specified number of
+     * bytes, but never more.  If there isn't enough data to fill that
+     * number of bytes, all of the available data is read.  The third
+     * argument is modified to reflect the number of bytes read.  If a
+     * char was put back into the stream via ungetc, it will be the first
+     * character returned.
+     *
+     * It is not possible for both bytes to be read and an APR_EOF
+     * or other error to be returned.  APR_EINTR is never returned.
+     * @param thefile The file descriptor to read from.
+     * @param buf The direct Byte buffer to store the data to.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to read.
+     * @return the number of bytes read.
+     */
+    public static native int readb(long thefile, ByteBuffer buf,  int offset, int nbytes);
+
+    /**
+     * Read data from the specified file, ensuring that the buffer is filled
+     * before returning.
+     *
+     * Read will read up to the specified number of
+     * bytes, but never more.  If there isn't enough data to fill that
+     * number of bytes, then the process/thread will block until it is
+     * available or EOF is reached.  If a char was put back into the
+     * stream via ungetc, it will be the first character returned.
+     *
+     * It is possible for both bytes to be read and an error to be
+     * returned.  And if *bytes_read is less than nbytes, an accompanying
+     * error is _always_ returned.
+     *
+     * APR_EINTR is never returned.
+     * @param thefile The file descriptor to read from.
+     * @param buf The buffer to store the data to.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @return the number of bytes read.
+     */
+    public static native int readFull(long thefile, byte[] buf,  int offset, int nbytes);
+
+    /**
+     * Read data from the specified file, ensuring that the buffer is filled
+     * before returning.
+     *
+     * Read will read up to the specified number of
+     * bytes, but never more.  If there isn't enough data to fill that
+     * number of bytes, then the process/thread will block until it is
+     * available or EOF is reached.  If a char was put back into the
+     * stream via ungetc, it will be the first character returned.
+     *
+     * It is possible for both bytes to be read and an error to be
+     * returned.  And if *bytes_read is less than nbytes, an accompanying
+     * error is _always_ returned.
+     *
+     * APR_EINTR is never returned.
+     * @param thefile The file descriptor to read from.
+     * @param buf The direct ByteBuffer to store the data to.
+     * @param offset Start offset in buf
+     * @param nbytes The number of bytes to read.
+     * @return the number of bytes read.
+     */
+    public static native int readFullb(long thefile, ByteBuffer buf,  int offset, int nbytes);
+
+    /**
+     * Read a string from the specified file.
+     * The buffer will be NUL-terminated if any characters are stored.
+     * @param buf The buffer to store the string in.
+     * @param offset Start offset in buf
+     * @param thefile The file descriptor to read from
+     */
+    public static native int gets(byte[] buf,  int offset, long thefile);
+
+
+    /**
+     * Read a character from the specified file.
+     * @param thefile The file descriptor to read from
+     * @return The read character
+     */
+    public static native int getc(long thefile)
+        throws Error;
+
+    /**
+     * Are we at the end of the file
+     * @param fptr The apr file we are testing.
+     * @return Returns APR_EOF if we are at the end of file, APR_SUCCESS otherwise.
+     */
+    public static native int eof(long fptr);
+
+    /**
+     * return the file name of the current file.
+     * @param thefile The currently open file.
+     */
+    public static native String nameGet(long thefile);
+
+    /**
+     * Set the specified file's permission bits.
+     * <br><b>Warning :</b> Some platforms may not be able to apply all of the
+     * available permission bits; APR_INCOMPLETE will be returned if some
+     * permissions are specified which could not be set.
+     * <br><b>Warning :</b> Platforms which do not implement this feature will return
+     * APR_ENOTIMPL.
+     * @param fname The file (name) to apply the permissions to.
+     * @param perms The permission bits to apply to the file.
+     *
+     */
+    public static native int permsSet(String fname, int perms);
+
+    /**
+     * Set attributes of the specified file.
+     * This function should be used in preference to explicit manipulation
+     *      of the file permissions, because the operations to provide these
+     *      attributes are platform specific and may involve more than simply
+     *      setting permission bits.
+     * <br><b>Warning :</b> Platforms which do not implement this feature will return
+     *      APR_ENOTIMPL.
+     * @param fname The full path to the file (using / on all systems)
+     * @param attributes Or'd combination of
+     * <PRE>
+     *            APR_FILE_ATTR_READONLY   - make the file readonly
+     *            APR_FILE_ATTR_EXECUTABLE - make the file executable
+     *            APR_FILE_ATTR_HIDDEN     - make the file hidden
+     * </PRE>
+     * @param mask Mask of valid bits in attributes.
+     * @param pool the pool to use.
+     */
+    public static native int  attrsSet(String fname, int attributes, int mask, long pool);
+
+    /**
+     * Set the mtime of the specified file.
+     * <br><b>Warning :</b> Platforms which do not implement this feature will return
+     *      APR_ENOTIMPL.
+     * @param fname The full path to the file (using / on all systems)
+     * @param mtime The mtime to apply to the file in microseconds
+     * @param pool The pool to use.
+     */
+    public static native int  mtimeSet(String fname, long mtime, long pool);
+
+    /**
+     * Establish a lock on the specified, open file. The lock may be advisory
+     * or mandatory, at the discretion of the platform. The lock applies to
+     * the file as a whole, rather than a specific range. Locks are established
+     * on a per-thread/process basis; a second lock by the same thread will not
+     * block.
+     * @param thefile The file to lock.
+     * @param type The type of lock to establish on the file.
+     */
+    public static native int lock(long thefile, int type);
+
+    /**
+     * Remove any outstanding locks on the file.
+     * @param thefile The file to unlock.
+     */
+    public static native int unlock(long thefile);
+
+    /**
+     * Retrieve the flags that were passed into apr_file_open()
+     * when the file was opened.
+     * @param file The file to retrieve flags.
+     * @return the flags
+     */
+    public static native int flagsGet(long file);
+
+    /**
+     * Truncate the file's length to the specified offset
+     * @param fp The file to truncate
+     * @param offset The offset to truncate to.
+     */
+    public static native int trunc(long fp, long offset);
+
+    /**
+     * Create an anonymous pipe.
+     * @param io io[0] The file descriptors to use as input to the pipe.
+     *           io[1] The file descriptor to use as output from the pipe.
+     * @param pool The pool to operate on.
+     */
+    public static native int pipeCreate(long [] io, long pool);
+
+    /**
+     * Get the timeout value for a pipe or manipulate the blocking state.
+     * @param thepipe The pipe we are getting a timeout for.
+     * @return The current timeout value in microseconds.
+     */
+    public static native long pipeTimeoutGet(long thepipe)
+        throws Error;
+
+    /**
+     * Set the timeout value for a pipe or manipulate the blocking state.
+     * @param thepipe The pipe we are setting a timeout on.
+     * @param timeout The timeout value in microseconds.  Values &lt; 0 mean
+     *        wait forever, 0 means do not wait at all.
+     */
+    public static native int pipeTimeoutSet(long thepipe, long timeout);
+
+    /**
+     * Duplicate the specified file descriptor.
+     * @param newFile The file to duplicate.
+     * newFile must point to a valid apr_file_t, or point to NULL.
+     * @param oldFile The file to duplicate.
+     * @param pool The pool to use for the new file.
+     * @return Duplicated file structure.
+     */
+    public static native long dup(long newFile, long oldFile, long pool)
+        throws Error;
+
+    /**
+     * Duplicate the specified file descriptor and close the original.
+     * @param newFile The old file that is to be closed and reused.
+     * newFile MUST point at a valid apr_file_t. It cannot be NULL.
+     * @param oldFile The file to duplicate.
+     * @param pool The pool to use for the new file.
+     * @return Status code.
+     */
+    public static native int dup2(long newFile, long oldFile, long pool);
+
+    /**
+     * Get the specified file's stats.  The file is specified by filename,
+     * instead of using a pre-opened file.
+     * @param finfo Where to store the information about the file, which is
+     * never touched if the call fails.
+     * @param fname The name of the file to stat.
+     * @param wanted The desired apr_finfo_t fields, as a bit flag of APR_FINFO_ values
+     * @param pool the pool to use to allocate the new file.
+     */
+    public static native int stat(FileInfo finfo, String fname, int wanted, long pool);
+
+    /**
+     * Get the specified file's stats.  The file is specified by filename,
+     * instead of using a pre-opened file.
+     * @param fname The name of the file to stat.
+     * @param wanted The desired apr_finfo_t fields, as a bit flag of APR_FINFO_ values
+     * @param pool the pool to use to allocate the new file.
+     * @return FileInfo object.
+     */
+    public static native FileInfo getStat(String fname, int wanted, long pool);
+
+    /**
+     * Get the specified file's stats.
+     * @param finfo Where to store the information about the file.
+     * @param wanted The desired apr_finfo_t fields, as a bit flag of APR_FINFO_ values
+     * @param thefile The file to get information about.
+     */
+    public static native int infoGet(FileInfo finfo, int wanted, long thefile);
+
+
+    /**
+     * Get the specified file's stats.
+     * @param wanted The desired apr_finfo_t fields, as a bit flag of APR_FINFO_ values
+     * @param thefile The file to get information about.
+     * @return FileInfo object.
+     */
+    public static native FileInfo getInfo(int wanted, long thefile);
+
+}
diff --git a/java/src/org/apache/tomcat/jni/FileInfo.java b/java/src/org/apache/tomcat/jni/FileInfo.java
new file mode 100644
index 0000000..9fb6bd3
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/FileInfo.java
@@ -0,0 +1,65 @@
+/*
+ *  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.tomcat.jni;
+
+/** Fileinfo
+ *
+ * @author Mladen Turk
+ */
+public class FileInfo {
+
+    /** Allocates memory and closes lingering handles in the specified pool */
+    public long pool;
+    /** The bitmask describing valid fields of this apr_finfo_t structure
+     *  including all available 'wanted' fields and potentially more */
+    public int valid;
+    /** The access permissions of the file.  Mimics Unix access rights. */
+    public int protection;
+    /** The type of file.  One of APR_REG, APR_DIR, APR_CHR, APR_BLK, APR_PIPE,
+     * APR_LNK or APR_SOCK.  If the type is undetermined, the value is APR_NOFILE.
+     * If the type cannot be determined, the value is APR_UNKFILE.
+     */
+    public int filetype;
+    /** The user id that owns the file */
+    public int user;
+    /** The group id that owns the file */
+    public int group;
+    /** The inode of the file. */
+    public int inode;
+    /** The id of the device the file is on. */
+    public int device;
+    /** The number of hard links to the file. */
+    public int nlink;
+    /** The size of the file */
+    public long size;
+    /** The storage size consumed by the file */
+    public long csize;
+    /** The time the file was last accessed */
+    public long atime;
+    /** The time the file was last modified */
+    public long mtime;
+    /** The time the file was created, or the inode was last changed */
+    public long ctime;
+    /** The pathname of the file (possibly unrooted) */
+    public String fname;
+    /** The file's name (no path) in filesystem case */
+    public String name;
+    /** The file's handle, if accessed (can be submitted to apr_duphandle) */
+    public long filehand;
+
+}
diff --git a/java/src/org/apache/tomcat/jni/Global.java b/java/src/org/apache/tomcat/jni/Global.java
new file mode 100644
index 0000000..60a4e8f
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Global.java
@@ -0,0 +1,94 @@
+/*
+ *  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.tomcat.jni;
+
+/** Global
+ *
+ * @author Mladen Turk
+ */
+public class Global {
+
+    /**
+     * Create and initialize a mutex that can be used to synchronize both
+     * processes and threads. Note: There is considerable overhead in using
+     * this API if only cross-process or cross-thread mutual exclusion is
+     * required. See apr_proc_mutex.h and apr_thread_mutex.h for more
+     * specialized lock routines.
+     * <br><b>Warning :</b> Check APR_HAS_foo_SERIALIZE defines to see if the platform supports
+     *          APR_LOCK_foo.  Only APR_LOCK_DEFAULT is portable.
+     * @param fname A file name to use if the lock mechanism requires one.  This
+     *        argument should always be provided.  The lock code itself will
+     *        determine if it should be used.
+     * @param mech The mechanism to use for the interprocess lock, if any; one of
+     * <PRE>
+     *            APR_LOCK_FCNTL
+     *            APR_LOCK_FLOCK
+     *            APR_LOCK_SYSVSEM
+     *            APR_LOCK_POSIXSEM
+     *            APR_LOCK_PROC_PTHREAD
+     *            APR_LOCK_DEFAULT     pick the default mechanism for the platform
+     * </PRE>
+     * @param pool the pool from which to allocate the mutex.
+     * @return Newly created mutex.
+     */
+    public static native long create(String fname, int mech, long pool)
+        throws Error;
+
+    /**
+     * Re-open a mutex in a child process.
+     * @param fname A file name to use if the mutex mechanism requires one.  This
+     *              argument should always be provided.  The mutex code itself will
+     *              determine if it should be used.  This filename should be the
+     *              same one that was passed to apr_proc_mutex_create().
+     * @param pool The pool to operate on.
+     * This function must be called to maintain portability, even
+     *         if the underlying lock mechanism does not require it.
+     * @return Newly opened mutex.
+     */
+    public static native long childInit(String fname, long pool)
+        throws Error;
+
+    /**
+     * Acquire the lock for the given mutex. If the mutex is already locked,
+     * the current thread will be put to sleep until the lock becomes available.
+     * @param mutex the mutex on which to acquire the lock.
+     */
+    public static native int lock(long mutex);
+
+    /**
+     * Attempt to acquire the lock for the given mutex. If the mutex has already
+     * been acquired, the call returns immediately with APR_EBUSY. Note: it
+     * is important that the APR_STATUS_IS_EBUSY(s) macro be used to determine
+     * if the return value was APR_EBUSY, for portability reasons.
+     * @param mutex the mutex on which to attempt the lock acquiring.
+     */
+    public static native int trylock(long mutex);
+
+    /**
+     * Release the lock for the given mutex.
+     * @param mutex the mutex from which to release the lock.
+     */
+    public static native int unlock(long mutex);
+
+    /**
+     * Destroy the mutex and free the memory associated with the lock.
+     * @param mutex the mutex to destroy.
+     */
+    public static native int destroy(long mutex);
+
+}
diff --git a/java/src/org/apache/tomcat/jni/Library.java b/java/src/org/apache/tomcat/jni/Library.java
new file mode 100644
index 0000000..990832c
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Library.java
@@ -0,0 +1,224 @@
+/*
+ *  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.tomcat.jni;
+
+import java.io.File;
+
+/** Library
+ *
+ * @author Mladen Turk
+ */
+public final class Library {
+
+    /* Default library names */
+    private static final String [] NAMES = {"netty-tcnative", "libnetty-tcnative", "netty-tcnative-1", "libnetty-tcnative-1"};
+    /*
+     * A handle to the unique Library singleton instance.
+     */
+    private static Library _instance = null;
+
+    private Library() throws Exception {
+        boolean loaded = false;
+        String path = System.getProperty("java.library.path");
+        String [] paths = path.split(File.pathSeparator);
+        StringBuilder err = new StringBuilder();
+        for (int i = 0; i < NAMES.length; i++) {
+            try {
+                System.loadLibrary(NAMES[i]);
+                loaded = true;
+            } catch (ThreadDeath | VirtualMachineError t) {
+                throw t;
+            } catch (Throwable t) {
+                String name = System.mapLibraryName(NAMES[i]);
+                for (int j = 0; j < paths.length; j++) {
+                    java.io.File fd = new java.io.File(paths[j] , name);
+                    if (fd.exists()) {
+                        // File exists but failed to load
+                        throw t;
+                    }
+                }
+                if (i > 0) {
+                    err.append(", ");
+                }
+                err.append(t.getMessage());
+            }
+            if (loaded) {
+                break;
+            }
+        }
+        if (!loaded) {
+            StringBuilder names = new StringBuilder();
+            for (String name : NAMES) {
+                names.append(name);
+                names.append(", ");
+            }
+            throw new LibraryNotFoundError(names.substring(0, names.length() -2), err.toString());
+        }
+    }
+
+    private Library(String libraryName)
+    {
+        if (!"provided".equals(libraryName)) {
+            System.loadLibrary(libraryName);
+        }
+    }
+
+    /* create global TCN's APR pool
+     * This has to be the first call to TCN library.
+     */
+    private static native boolean initialize();
+    /* destroy global TCN's APR pool
+     * This has to be the last call to TCN library.
+     */
+    public static native void terminate();
+    /* Internal function for loading APR Features */
+    private static native boolean has(int what);
+    /* Internal function for loading APR Features */
+    private static native int version(int what);
+    /* Internal function for loading APR sizes */
+    private static native int size(int what);
+
+    /* TCN_MAJOR_VERSION */
+    public static int TCN_MAJOR_VERSION  = 0;
+    /* TCN_MINOR_VERSION */
+    public static int TCN_MINOR_VERSION  = 0;
+    /* TCN_PATCH_VERSION */
+    public static int TCN_PATCH_VERSION  = 0;
+    /* TCN_IS_DEV_VERSION */
+    public static int TCN_IS_DEV_VERSION = 0;
+    /* APR_MAJOR_VERSION */
+    public static int APR_MAJOR_VERSION  = 0;
+    /* APR_MINOR_VERSION */
+    public static int APR_MINOR_VERSION  = 0;
+    /* APR_PATCH_VERSION */
+    public static int APR_PATCH_VERSION  = 0;
+    /* APR_IS_DEV_VERSION */
+    public static int APR_IS_DEV_VERSION = 0;
+
+    /* TCN_VERSION_STRING */
+    public static native String versionString();
+    /* APR_VERSION_STRING */
+    public static native String aprVersionString();
+
+    /*  APR Feature Macros */
+    public static boolean APR_HAVE_IPV6           = false;
+    public static boolean APR_HAS_SHARED_MEMORY   = false;
+    public static boolean APR_HAS_THREADS         = false;
+    public static boolean APR_HAS_SENDFILE        = false;
+    public static boolean APR_HAS_MMAP            = false;
+    public static boolean APR_HAS_FORK            = false;
+    public static boolean APR_HAS_RANDOM          = false;
+    public static boolean APR_HAS_OTHER_CHILD     = false;
+    public static boolean APR_HAS_DSO             = false;
+    public static boolean APR_HAS_SO_ACCEPTFILTER = false;
+    public static boolean APR_HAS_UNICODE_FS      = false;
+    public static boolean APR_HAS_PROC_INVOKED    = false;
+    public static boolean APR_HAS_USER            = false;
+    public static boolean APR_HAS_LARGE_FILES     = false;
+    public static boolean APR_HAS_XTHREAD_FILES   = false;
+    public static boolean APR_HAS_OS_UUID         = false;
+    /* Are we big endian? */
+    public static boolean APR_IS_BIGENDIAN        = false;
+    /* APR sets APR_FILES_AS_SOCKETS to 1 on systems where it is possible
+     * to poll on files/pipes.
+     */
+    public static boolean APR_FILES_AS_SOCKETS    = false;
+    /* This macro indicates whether or not EBCDIC is the native character set.
+     */
+    public static boolean APR_CHARSET_EBCDIC      = false;
+    /* Is the TCP_NODELAY socket option inherited from listening sockets?
+     */
+    public static boolean APR_TCP_NODELAY_INHERITED = false;
+    /* Is the O_NONBLOCK flag inherited from listening sockets?
+     */
+    public static boolean APR_O_NONBLOCK_INHERITED  = false;
+
+
+    public static int APR_SIZEOF_VOIDP;
+    public static int APR_PATH_MAX;
+    public static int APRMAXHOSTLEN;
+    public static int APR_MAX_IOVEC_SIZE;
+    public static int APR_MAX_SECS_TO_LINGER;
+    public static int APR_MMAP_THRESHOLD;
+    public static int APR_MMAP_LIMIT;
+
+    /* return global TCN's APR pool */
+    public static native long globalPool();
+
+    /**
+     * Setup any APR internal data structures.  This MUST be the first function
+     * called for any APR library.
+     * @param libraryName the name of the library to load
+     */
+    public static boolean initialize(String libraryName)
+        throws Exception
+    {
+        if (_instance == null) {
+            if (libraryName == null)
+                _instance = new Library();
+            else
+                _instance = new Library(libraryName);
+            TCN_MAJOR_VERSION  = version(0x01);
+            TCN_MINOR_VERSION  = version(0x02);
+            TCN_PATCH_VERSION  = version(0x03);
+            TCN_IS_DEV_VERSION = version(0x04);
+            APR_MAJOR_VERSION  = version(0x11);
+            APR_MINOR_VERSION  = version(0x12);
+            APR_PATCH_VERSION  = version(0x13);
+            APR_IS_DEV_VERSION = version(0x14);
+
+            APR_SIZEOF_VOIDP        = size(1);
+            APR_PATH_MAX            = size(2);
+            APRMAXHOSTLEN           = size(3);
+            APR_MAX_IOVEC_SIZE      = size(4);
+            APR_MAX_SECS_TO_LINGER  = size(5);
+            APR_MMAP_THRESHOLD      = size(6);
+            APR_MMAP_LIMIT          = size(7);
+
+            APR_HAVE_IPV6           = has(0);
+            APR_HAS_SHARED_MEMORY   = has(1);
+            APR_HAS_THREADS         = has(2);
+            APR_HAS_SENDFILE        = has(3);
+            APR_HAS_MMAP            = has(4);
+            APR_HAS_FORK            = has(5);
+            APR_HAS_RANDOM          = has(6);
+            APR_HAS_OTHER_CHILD     = has(7);
+            APR_HAS_DSO             = has(8);
+            APR_HAS_SO_ACCEPTFILTER = has(9);
+            APR_HAS_UNICODE_FS      = has(10);
+            APR_HAS_PROC_INVOKED    = has(11);
+            APR_HAS_USER            = has(12);
+            APR_HAS_LARGE_FILES     = has(13);
+            APR_HAS_XTHREAD_FILES   = has(14);
+            APR_HAS_OS_UUID         = has(15);
+            APR_IS_BIGENDIAN        = has(16);
+            APR_FILES_AS_SOCKETS    = has(17);
+            APR_CHARSET_EBCDIC      = has(18);
+            APR_TCP_NODELAY_INHERITED = has(19);
+            APR_O_NONBLOCK_INHERITED  = has(20);
+            if (APR_MAJOR_VERSION < 1) {
+                throw new UnsatisfiedLinkError("Unsupported APR Version (" +
+                                               aprVersionString() + ")");
+            }
+            if (!APR_HAS_THREADS) {
+                throw new UnsatisfiedLinkError("Missing APR_HAS_THREADS");
+            }
+        }
+        return initialize();
+    }
+}
diff --git a/java/src/org/apache/tomcat/jni/LibraryNotFoundError.java b/java/src/org/apache/tomcat/jni/LibraryNotFoundError.java
new file mode 100644
index 0000000..ab8a030
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/LibraryNotFoundError.java
@@ -0,0 +1,40 @@
+/*
+ * 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.tomcat.jni;
+
+public class LibraryNotFoundError extends UnsatisfiedLinkError {
+
+    private static final long serialVersionUID = 1L;
+
+    private final String libraryNames;
+
+    /**
+     *
+     * @param libraryNames A list of the file names of the native libraries that
+     *                     failed to load
+     * @param errors A list of the error messages received when trying to load
+     *               each of the libraries
+     */
+    public LibraryNotFoundError(String libraryNames, String errors){
+        super(errors);
+        this.libraryNames = libraryNames;
+    }
+
+    public String getLibraryNames(){
+        return libraryNames;
+    }
+}
diff --git a/java/src/org/apache/tomcat/jni/Local.java b/java/src/org/apache/tomcat/jni/Local.java
new file mode 100644
index 0000000..256af62
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Local.java
@@ -0,0 +1,73 @@
+/*
+ *  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.tomcat.jni;
+
+/** Local socket
+ *
+ * @author Mladen Turk
+ */
+public class Local {
+
+    /**
+     * Create a socket.
+     * @param path The address of the new socket.
+     * @param cont The parent pool to use
+     * @return The new socket that has been set up.
+     */
+    public static native long create(String path, long cont)
+        throws Exception;
+
+    /**
+     * Bind the socket to its associated port
+     * @param sock The socket to bind
+     * @param sa The socket address to bind to
+     * This may be where we will find out if there is any other process
+     *      using the selected port.
+     */
+    public static native int bind(long sock, long sa);
+
+    /**
+     * Listen to a bound socket for connections.
+     * @param sock The socket to listen on
+     * @param backlog The number of outstanding connections allowed in the sockets
+     *                listen queue.  If this value is less than zero, for NT pipes
+     *                the number of instances is unlimited.
+     *
+     */
+    public static native int listen(long sock, int backlog);
+
+    /**
+     * Accept a new connection request
+     * @param sock The socket we are listening on.
+     * @return  A copy of the socket that is connected to the socket that
+     *          made the connection request.  This is the socket which should
+     *          be used for all future communication.
+     */
+    public static native long accept(long sock)
+        throws Exception;
+
+    /**
+     * Issue a connection request to a socket either on the same machine
+     * or a different one.
+     * @param sock The socket we wish to use for our side of the connection
+     * @param sa The address of the machine we wish to connect to.
+     *           Unused for NT Pipes.
+     */
+    public static native int connect(long sock, long sa);
+
+}
diff --git a/java/src/org/apache/tomcat/jni/Lock.java b/java/src/org/apache/tomcat/jni/Lock.java
new file mode 100644
index 0000000..0c8aa05
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Lock.java
@@ -0,0 +1,121 @@
+/*
+ *  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.tomcat.jni;
+
+/** Lock
+ *
+ * @author Mladen Turk
+ */
+public class Lock {
+
+    /**
+     * Enumerated potential types for APR process locking methods
+     * <br><b>Warning :</b> Check APR_HAS_foo_SERIALIZE defines to see if the platform supports
+     *          APR_LOCK_foo.  Only APR_LOCK_DEFAULT is portable.
+     */
+
+    public static final int APR_LOCK_FCNTL        = 0; /** fcntl() */
+    public static final int APR_LOCK_FLOCK        = 1; /** flock() */
+    public static final int APR_LOCK_SYSVSEM      = 2; /** System V Semaphores */
+    public static final int APR_LOCK_PROC_PTHREAD = 3; /** POSIX pthread process-based locking */
+    public static final int APR_LOCK_POSIXSEM     = 4; /** POSIX semaphore process-based locking */
+    public static final int APR_LOCK_DEFAULT      = 5; /** Use the default process lock */
+
+    /**
+     * Create and initialize a mutex that can be used to synchronize processes.
+     * <br><b>Warning :</b> Check APR_HAS_foo_SERIALIZE defines to see if the platform supports
+     *          APR_LOCK_foo.  Only APR_LOCK_DEFAULT is portable.
+     * @param fname A file name to use if the lock mechanism requires one.  This
+     *        argument should always be provided.  The lock code itself will
+     *        determine if it should be used.
+     * @param mech The mechanism to use for the interprocess lock, if any; one of
+     * <PRE>
+     *            APR_LOCK_FCNTL
+     *            APR_LOCK_FLOCK
+     *            APR_LOCK_SYSVSEM
+     *            APR_LOCK_POSIXSEM
+     *            APR_LOCK_PROC_PTHREAD
+     *            APR_LOCK_DEFAULT     pick the default mechanism for the platform
+     * </PRE>
+     * @param pool the pool from which to allocate the mutex.
+     * @return Newly created mutex.
+     */
+    public static native long create(String fname, int mech, long pool)
+        throws Error;
+
+    /**
+     * Re-open a mutex in a child process.
+     * This function must be called to maintain portability, even
+     * if the underlying lock mechanism does not require it.
+     * @param fname A file name to use if the mutex mechanism requires one.  This
+     *              argument should always be provided.  The mutex code itself will
+     *              determine if it should be used.  This filename should be the
+     *              same one that was passed to apr_proc_mutex_create().
+     * @param pool The pool to operate on.
+     * @return Newly opened mutex.
+     */
+    public static native long childInit(String fname, long pool)
+        throws Error;
+
+    /**
+     * Acquire the lock for the given mutex. If the mutex is already locked,
+     * the current thread will be put to sleep until the lock becomes available.
+     * @param mutex the mutex on which to acquire the lock.
+     */
+    public static native int lock(long mutex);
+
+    /**
+     * Attempt to acquire the lock for the given mutex. If the mutex has already
+     * been acquired, the call returns immediately with APR_EBUSY. Note: it
+     * is important that the APR_STATUS_IS_EBUSY(s) macro be used to determine
+     * if the return value was APR_EBUSY, for portability reasons.
+     * @param mutex the mutex on which to attempt the lock acquiring.
+     */
+    public static native int trylock(long mutex);
+
+    /**
+     * Release the lock for the given mutex.
+     * @param mutex the mutex from which to release the lock.
+     */
+    public static native int unlock(long mutex);
+
+    /**
+     * Destroy the mutex and free the memory associated with the lock.
+     * @param mutex the mutex to destroy.
+     */
+    public static native int destroy(long mutex);
+
+    /**
+     * Return the name of the lockfile for the mutex, or NULL
+     * if the mutex doesn't use a lock file
+     */
+    public static native String lockfile(long mutex);
+
+    /**
+     * Display the name of the mutex, as it relates to the actual method used.
+     * This matches the valid options for Apache's AcceptMutex directive
+     * @param mutex the name of the mutex
+     */
+    public static native String name(long mutex);
+
+    /**
+     * Display the name of the default mutex: APR_LOCK_DEFAULT
+     */
+    public static native String defname();
+
+}
diff --git a/java/src/org/apache/tomcat/jni/Mmap.java b/java/src/org/apache/tomcat/jni/Mmap.java
new file mode 100644
index 0000000..92d7687
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Mmap.java
@@ -0,0 +1,71 @@
+/*
+ *  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.tomcat.jni;
+
+/** Mmap
+ *
+ * @author Mladen Turk
+ */
+public class Mmap {
+    /** MMap opened for reading */
+    public static final int APR_MMAP_READ  = 1;
+    /** MMap opened for writing */
+    public static final int APR_MMAP_WRITE = 2;
+
+
+    /**
+     * Create a new mmap'ed file out of an existing APR file.
+     * @param file The file turn into an mmap.
+     * @param offset The offset into the file to start the data pointer at.
+     * @param size The size of the file
+     * @param flag bit-wise or of:
+     * <PRE>
+     * APR_MMAP_READ       MMap opened for reading
+     * APR_MMAP_WRITE      MMap opened for writing
+     * </PRE>
+     * @param pool The pool to use when creating the mmap.
+     * @return The newly created mmap'ed file.
+     */
+    public static native long create(long file, long offset, long size, int flag, long pool)
+        throws Error;
+
+    /**
+     * Duplicate the specified MMAP.
+     * @param mmap The mmap to duplicate.
+     * @param pool The pool to use for new_mmap.
+     * @return Duplicated mmap'ed file.
+     */
+    public static native long dup(long mmap, long pool)
+        throws Error;
+
+    /**
+     * Remove a mmap'ed.
+     * @param mm The mmap'ed file.
+     */
+    public static native int delete(long mm);
+
+    /**
+     * Move the pointer into the mmap'ed file to the specified offset.
+     * @param mm The mmap'ed file.
+     * @param offset The offset to move to.
+     * @return The pointer to the offset specified.
+     */
+    public static native long offset(long mm, long offset)
+        throws Error;
+
+}
diff --git a/java/src/org/apache/tomcat/jni/Multicast.java b/java/src/org/apache/tomcat/jni/Multicast.java
new file mode 100644
index 0000000..edbb300
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Multicast.java
@@ -0,0 +1,76 @@
+/*
+ *  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.tomcat.jni;
+
+/** Multicast
+ *
+ * @author Mladen Turk
+ */
+public class Multicast {
+
+    /**
+     * Join a Multicast Group
+     * @param sock The socket to join a multicast group
+     * @param join The address of the multicast group to join
+     * @param iface Address of the interface to use.  If NULL is passed, the
+     *              default multicast interface will be used. (OS Dependent)
+     * @param source Source Address to accept transmissions from (non-NULL
+     *               implies Source-Specific Multicast)
+     */
+    public static native int join(long sock, long join,
+                                  long iface, long source);
+
+    /**
+     * Leave a Multicast Group.  All arguments must be the same as
+     * apr_mcast_join.
+     * @param sock The socket to leave a multicast group
+     * @param addr The address of the multicast group to leave
+     * @param iface Address of the interface to use.  If NULL is passed, the
+     *              default multicast interface will be used. (OS Dependent)
+     * @param source Source Address to accept transmissions from (non-NULL
+     *               implies Source-Specific Multicast)
+     */
+    public static native int leave(long sock, long addr,
+                                   long iface, long source);
+
+    /**
+     * Set the Multicast Time to Live (ttl) for a multicast transmission.
+     * @param sock The socket to set the multicast ttl
+     * @param ttl Time to live to Assign. 0-255, default=1
+     * <br><b>Remark :</b> If the TTL is 0, packets will only be seen
+     * by sockets on the local machine,
+     * and only when multicast loopback is enabled.
+     */
+    public static native int hops(long sock, int ttl);
+
+    /**
+     * Toggle IP Multicast Loopback
+     * @param sock The socket to set multicast loopback
+     * @param opt false=disable, true=enable
+     */
+    public static native int loopback(long sock, boolean opt);
+
+
+    /**
+     * Set the Interface to be used for outgoing Multicast Transmissions.
+     * @param sock The socket to set the multicast interface on
+     * @param iface Address of the interface to use for Multicast
+     */
+    public static native int ointerface(long sock, long iface);
+
+}
diff --git a/java/src/org/apache/tomcat/jni/OS.java b/java/src/org/apache/tomcat/jni/OS.java
new file mode 100644
index 0000000..e7791af
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/OS.java
@@ -0,0 +1,128 @@
+/*
+ *  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.tomcat.jni;
+
+/** OS
+ *
+ * @author Mladen Turk
+ */
+public class OS {
+
+    /* OS Enums */
+    private static final int UNIX      = 1;
+    private static final int NETWARE   = 2;
+    private static final int WIN32     = 3;
+    private static final int WIN64     = 4;
+    private static final int LINUX     = 5;
+    private static final int SOLARIS   = 6;
+    private static final int BSD       = 7;
+    private static final int MACOSX    = 8;
+
+    public static final int LOG_EMERG  = 1;
+    public static final int LOG_ERROR  = 2;
+    public static final int LOG_NOTICE = 3;
+    public static final int LOG_WARN   = 4;
+    public static final int LOG_INFO   = 5;
+    public static final int LOG_DEBUG  = 6;
+
+    /**
+     * Check for OS type.
+     * @param type OS type to test.
+     */
+    private static native boolean is(int type);
+
+    public static final boolean IS_UNIX    = is(UNIX);
+    public static final boolean IS_NETWARE = is(NETWARE);
+    public static final boolean IS_WIN32   = is(WIN32);
+    public static final boolean IS_WIN64   = is(WIN64);
+    public static final boolean IS_LINUX   = is(LINUX);
+    public static final boolean IS_SOLARIS = is(SOLARIS);
+    public static final boolean IS_BSD     = is(BSD);
+    public static final boolean IS_MACOSX  = is(MACOSX);
+
+    /**
+     * Get the name of the system default character set.
+     * @param pool the pool to allocate the name from, if needed
+     */
+    public static native String defaultEncoding(long pool);
+
+    /**
+     * Get the name of the current locale character set.
+     * Defers to apr_os_default_encoding if the current locale's
+     * data can't be retrieved on this system.
+     * @param pool the pool to allocate the name from, if needed
+     */
+    public static native String localeEncoding(long pool);
+
+    /**
+     * Generate random bytes.
+     * @param buf Buffer to fill with random bytes
+     * @param len Length of buffer in bytes
+     */
+    public static native int random(byte [] buf, int len);
+
+    /**
+     * Gather system info.
+     * <PRE>
+     * On exit the inf array will be filled with:
+     * inf[0]  - Total usable main memory size
+     * inf[1]  - Available memory size
+     * inf[2]  - Total page file/swap space size
+     * inf[3]  - Page file/swap space still available
+     * inf[4]  - Amount of shared memory
+     * inf[5]  - Memory used by buffers
+     * inf[6]  - Memory Load
+     *
+     * inf[7]  - Idle Time in microseconds
+     * inf[8]  - Kernel Time in microseconds
+     * inf[9]  - User Time in microseconds
+     *
+     * inf[10] - Process creation time (apr_time_t)
+     * inf[11] - Process Kernel Time in microseconds
+     * inf[12] - Process User Time in microseconds
+     *
+     * inf[13] - Current working set size.
+     * inf[14] - Peak working set size.
+     * inf[15] - Number of page faults.
+     * </PRE>
+     * @param inf array that will be filled with system information.
+     *            Array length must be at least 16.
+     */
+    public static native int info(long [] inf);
+
+    /**
+     * Expand environment variables.
+     * @param str String to expand
+     * @return Expanded string with replaced environment variables.
+     */
+    public static native String expand(String str);
+
+    /**
+     * Initialize system logging.
+     * @param domain String that will be prepended to every message
+     */
+    public static native void sysloginit(String domain);
+
+    /**
+     * Log message.
+     * @param level Log message severity. See LOG_XXX enums.
+     * @param message Message to log
+     */
+    public static native void syslog(int level, String message);
+
+}
diff --git a/java/src/org/apache/tomcat/jni/PasswordCallback.java b/java/src/org/apache/tomcat/jni/PasswordCallback.java
new file mode 100644
index 0000000..13f5d9a
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/PasswordCallback.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.tomcat.jni;
+
+/** PasswordCallback Interface
+ *
+ * @author Mladen Turk
+ */
+public interface PasswordCallback {
+
+    /**
+     * Called when the password is required
+     * @param prompt Password prompt
+     * @return Valid password or null
+     */
+    public String callback(String prompt);
+}
diff --git a/java/src/org/apache/tomcat/jni/Poll.java b/java/src/org/apache/tomcat/jni/Poll.java
new file mode 100644
index 0000000..3d23945
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Poll.java
@@ -0,0 +1,183 @@
+/*
+ *  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.tomcat.jni;
+
+/** Poll
+ *
+ * @author Mladen Turk
+ */
+public class Poll {
+
+    /**
+     * Poll return values
+     */
+    /** Can read without blocking */
+    public static final int APR_POLLIN   = 0x001;
+    /** Priority data available */
+    public static final int APR_POLLPRI  = 0x002;
+    /** Can write without blocking */
+    public static final int APR_POLLOUT  = 0x004;
+    /** Pending error */
+    public static final int APR_POLLERR  = 0x010;
+    /** Hangup occurred */
+    public static final int APR_POLLHUP  = 0x020;
+    /** Descriptor invalid */
+    public static final int APR_POLLNVAL = 0x040;
+
+    /**
+     * Pollset Flags
+     */
+    /** Adding or Removing a Descriptor is thread safe */
+    public static final int APR_POLLSET_THREADSAFE = 0x001;
+
+
+    /** Used in apr_pollfd_t to determine what the apr_descriptor is
+     * apr_datatype_e enum
+     */
+    public static final int APR_NO_DESC       = 0; /** nothing here */
+    public static final int APR_POLL_SOCKET   = 1; /** descriptor refers to a socket */
+    public static final int APR_POLL_FILE     = 2; /** descriptor refers to a file */
+    public static final int APR_POLL_LASTDESC = 3; /** descriptor is the last one in the list */
+
+    /**
+     * Setup a pollset object.
+     * If flags equals APR_POLLSET_THREADSAFE, then a pollset is
+     * created on which it is safe to make concurrent calls to
+     * apr_pollset_add(), apr_pollset_remove() and apr_pollset_poll() from
+     * separate threads.  This feature is only supported on some
+     * platforms; the apr_pollset_create() call will fail with
+     * APR_ENOTIMPL on platforms where it is not supported.
+     * @param size The maximum number of descriptors that this pollset can hold
+     * @param p The pool from which to allocate the pollset
+     * @param flags Optional flags to modify the operation of the pollset.
+     * @param ttl Maximum time to live for a particular socket.
+     * @return  The pointer in which to return the newly created object
+     */
+    public static native long create(int size, long p, int flags, long ttl)
+        throws Error;
+    /**
+     * Destroy a pollset object
+     * @param pollset The pollset to destroy
+     */
+    public static native int destroy(long pollset);
+
+    /**
+     * Add a socket to a pollset with the default timeout.
+     * @param pollset The pollset to which to add the socket
+     * @param sock The sockets to add
+     * @param reqevents requested events
+     */
+    public static native int add(long pollset, long sock,
+                                 int reqevents);
+
+    /**
+     * Add a socket to a pollset with a specific timeout.
+     * @param pollset The pollset to which to add the socket
+     * @param sock The sockets to add
+     * @param reqevents requested events
+     * @param timeout requested timeout in microseconds (-1 for infinite)
+     */
+    public static native int addWithTimeout(long pollset, long sock,
+                                            int reqevents, long timeout);
+
+    /**
+     * Remove a descriptor from a pollset
+     * @param pollset The pollset from which to remove the descriptor
+     * @param sock The socket to remove
+     */
+    public static native int remove(long pollset, long sock);
+
+    /**
+     * Block for activity on the descriptor(s) in a pollset
+     * @param pollset The pollset to use
+     * @param timeout Timeout in microseconds
+     * @param descriptors Array of signaled descriptors (output parameter)
+     *        The descriptor array must be two times the size of pollset.
+     *        and are populated as follows:
+     * <PRE>
+     * descriptors[2n + 0] -&gt; returned events
+     * descriptors[2n + 1] -&gt; socket
+     * </PRE>
+     * @param remove Remove signaled descriptors from pollset
+     * @return Number of signaled descriptors (output parameter)
+     *         or negative APR error code.
+     */
+    public static native int poll(long pollset, long timeout,
+                                  long [] descriptors, boolean remove);
+
+    /**
+     * Maintain on the descriptor(s) in a pollset
+     * @param pollset The pollset to use
+     * @param descriptors Array of signaled descriptors (output parameter)
+     *        The descriptor array must be the size of pollset.
+     *        and are populated as follows:
+     * <PRE>
+     * descriptors[n] -&gt; socket
+     * </PRE>
+     * @param remove Remove signaled descriptors from pollset
+     * @return Number of signaled descriptors (output parameter)
+     *         or negative APR error code.
+     */
+    public static native int maintain(long pollset, long [] descriptors,
+                                      boolean remove);
+
+    /**
+     * Set the socket time to live.
+     * @param pollset The pollset to use
+     * @param ttl Timeout in microseconds
+     */
+    public static native void setTtl(long pollset, long ttl);
+
+    /**
+     * Get the socket time to live.
+     * @param pollset The pollset to use
+     * @return Timeout in microseconds
+     */
+    public static native long getTtl(long pollset);
+
+    /**
+     * Return all descriptor(s) in a pollset
+     * @param pollset The pollset to use
+     * @param descriptors Array of descriptors (output parameter)
+     *        The descriptor array must be two times the size of pollset.
+     *        and are populated as follows:
+     * <PRE>
+     * descriptors[2n + 0] -&gt; returned events
+     * descriptors[2n + 1] -&gt; socket
+     * </PRE>
+     * @return Number of descriptors (output parameter) in the Poll
+     *         or negative APR error code.
+     */
+    public static native int pollset(long pollset, long [] descriptors);
+
+    /**
+     * Make poll() return.
+     *
+     * @param   pollset
+     * @return  Negative APR error code
+     */
+    public static native int interrupt(long pollset);
+
+    /**
+     * Check if interrupt() is allowed.
+     *
+     * @param pollset
+     * @return  <code>true</code> if {@link #interrupt(long)} is allowed, else
+     *          <code>false</code>
+     */
+    public static native boolean wakeable(long pollset);
+}
diff --git a/java/src/org/apache/tomcat/jni/Pool.java b/java/src/org/apache/tomcat/jni/Pool.java
new file mode 100644
index 0000000..617b1b7
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Pool.java
@@ -0,0 +1,162 @@
+/*
+ *  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.tomcat.jni;
+
+import java.nio.ByteBuffer;
+
+/** Pool
+ *
+ * @author Mladen Turk
+ */
+public class Pool {
+
+    /**
+     * Create a new pool.
+     * @param parent The parent pool.  If this is 0, the new pool is a root
+     * pool.  If it is non-zero, the new pool will inherit all
+     * of its parent pool's attributes, except the apr_pool_t will
+     * be a sub-pool.
+     * @return The pool we have just created.
+    */
+    public static native long create(long parent);
+
+    /**
+     * Clear all memory in the pool and run all the cleanups. This also destroys all
+     * subpools.
+     * @param pool The pool to clear
+     * This does not actually free the memory, it just allows the pool
+     *         to re-use this memory for the next allocation.
+     */
+    public static native void clear(long pool);
+
+    /**
+     * Destroy the pool. This takes similar action as apr_pool_clear() and then
+     * frees all the memory.
+     * This will actually free the memory
+     * @param pool The pool to destroy
+     */
+    public static native void destroy(long pool);
+
+    /**
+     * Get the parent pool of the specified pool.
+     * @param pool The pool for retrieving the parent pool.
+     * @return The parent of the given pool.
+     */
+    public static native long parentGet(long pool);
+
+    /**
+     * Determine if pool a is an ancestor of pool b
+     * @param a The pool to search
+     * @param b The pool to search for
+     * @return True if a is an ancestor of b, NULL is considered an ancestor
+     * of all pools.
+     */
+    public static native boolean isAncestor(long a, long b);
+
+
+    /*
+     * Cleanup
+     *
+     * Cleanups are performed in the reverse order they were registered.  That is:
+     * Last In, First Out.  A cleanup function can safely allocate memory from
+     * the pool that is being cleaned up. It can also safely register additional
+     * cleanups which will be run LIFO, directly after the current cleanup
+     * terminates.  Cleanups have to take caution in calling functions that
+     * create subpools. Subpools, created during cleanup will NOT automatically
+     * be cleaned up.  In other words, cleanups are to clean up after themselves.
+     */
+
+    /**
+     * Register a function to be called when a pool is cleared or destroyed
+     * @param pool The pool register the cleanup with
+     * @param o The object to call when the pool is cleared
+     *                      or destroyed
+     * @return The cleanup handler.
+     */
+    public static native long cleanupRegister(long pool, Object o);
+
+    /**
+     * Remove a previously registered cleanup function
+     * @param pool The pool remove the cleanup from
+     * @param data The cleanup handler to remove from cleanup
+     */
+    public static native void cleanupKill(long pool, long data);
+
+    /**
+     * Register a process to be killed when a pool dies.
+     * @param a The pool to use to define the processes lifetime
+     * @param proc The process to register
+     * @param how How to kill the process, one of:
+     * <PRE>
+     * APR_KILL_NEVER         -- process is never sent any signals
+     * APR_KILL_ALWAYS        -- process is sent SIGKILL on apr_pool_t cleanup
+     * APR_KILL_AFTER_TIMEOUT -- SIGTERM, wait 3 seconds, SIGKILL
+     * APR_JUST_WAIT          -- wait forever for the process to complete
+     * APR_KILL_ONLY_ONCE     -- send SIGTERM and then wait
+     * </PRE>
+     */
+    public static native void noteSubprocess(long a, long proc, int how);
+
+    /**
+     * Allocate a block of memory from a pool
+     * @param p The pool to allocate from
+     * @param size The amount of memory to allocate
+     * @return The ByteBuffer with allocated memory
+     */
+    public static native ByteBuffer alloc(long p, int size);
+
+    /**
+     * Allocate a block of memory from a pool and set all of the memory to 0
+     * @param p The pool to allocate from
+     * @param size The amount of memory to allocate
+     * @return The ByteBuffer with allocated memory
+     */
+    public static native ByteBuffer calloc(long p, int size);
+
+    /*
+     * User data management
+     */
+
+    /**
+     * Set the data associated with the current pool
+     * @param data The user data associated with the pool.
+     * @param key The key to use for association
+     * @param pool The current pool
+     * <br><b>Warning :</b>
+     * The data to be attached to the pool should have a life span
+     * at least as long as the pool it is being attached to.
+     * Object attached to the pool will be globally referenced
+     * until the pool is cleared or dataSet is called with the null data.
+     * @return APR Status code.
+     */
+     public static native int dataSet(long pool, String key, Object data);
+
+    /**
+     * Return the data associated with the current pool.
+     * @param key The key for the data to retrieve
+     * @param pool The current pool.
+     */
+     public static native Object dataGet(long pool, String key);
+
+    /**
+     * Run all of the child_cleanups, so that any unnecessary files are
+     * closed because we are about to exec a new program
+     */
+    public static native void cleanupForExec();
+
+}
diff --git a/java/src/org/apache/tomcat/jni/PoolCallback.java b/java/src/org/apache/tomcat/jni/PoolCallback.java
new file mode 100644
index 0000000..5075a3e
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/PoolCallback.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.tomcat.jni;
+
+/** PoolCallback Interface
+ *
+ * @author Mladen Turk
+ */
+public interface PoolCallback {
+
+    /**
+     * Called when the pool is destroyed or cleared
+     * @return Function must return APR_SUCCESS
+     */
+    public int callback();
+}
diff --git a/java/src/org/apache/tomcat/jni/Proc.java b/java/src/org/apache/tomcat/jni/Proc.java
new file mode 100644
index 0000000..c4c3c43
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Proc.java
@@ -0,0 +1,208 @@
+/*
+ *  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.tomcat.jni;
+
+/** Proc
+ *
+ * @author Mladen Turk
+ */
+public class Proc {
+
+    /*
+     * apr_cmdtype_e enum
+     */
+    public static final int APR_SHELLCM      = 0; /** use the shell to invoke the program */
+    public static final int APR_PROGRAM      = 1; /** invoke the program directly, no copied env */
+    public static final int APR_PROGRAM_ENV  = 2; /** invoke the program, replicating our environment */
+    public static final int APR_PROGRAM_PATH = 3; /** find program on PATH, use our environment */
+    public static final int APR_SHELLCMD_ENV = 4; /** use the shell to invoke the program,
+                                                   *   replicating our environment
+                                                   */
+
+    /*
+     * apr_wait_how_e enum
+     */
+    public static final int APR_WAIT   = 0; /** wait for the specified process to finish */
+    public static final int APR_NOWAIT = 1; /** do not wait -- just see if it has finished */
+
+    /*
+     * apr_exit_why_e enum
+     */
+    public static final int APR_PROC_EXIT        = 1; /** process exited normally */
+    public static final int APR_PROC_SIGNAL      = 2; /** process exited due to a signal */
+    public static final int APR_PROC_SIGNAL_CORE = 4; /** process exited and dumped a core file */
+
+    public static final int APR_NO_PIPE       = 0;
+    public static final int APR_FULL_BLOCK    = 1;
+    public static final int APR_FULL_NONBLOCK = 2;
+    public static final int APR_PARENT_BLOCK  = 3;
+    public static final int APR_CHILD_BLOCK   = 4;
+
+    public static final int APR_LIMIT_CPU     = 0;
+    public static final int APR_LIMIT_MEM     = 1;
+    public static final int APR_LIMIT_NPROC   = 2;
+    public static final int APR_LIMIT_NOFILE  = 3;
+
+
+    /** child has died, caller must call unregister still */
+    public static final int APR_OC_REASON_DEATH      = 0;
+    /** write_fd is unwritable */
+    public static final int APR_OC_REASON_UNWRITABLE = 1;
+    /** a restart is occurring, perform any necessary cleanup (including
+     * sending a special signal to child)
+     */
+    public static final int APR_OC_REASON_RESTART    = 2;
+    /** unregister has been called, do whatever is necessary (including
+     * kill the child)
+     */
+    public static final int APR_OC_REASON_UNREGISTER = 3;
+    /** somehow the child exited without us knowing ... buggy os? */
+    public static final int APR_OC_REASON_LOST       = 4;
+    /** a health check is occurring, for most maintenance functions
+     * this is a no-op.
+     */
+    public static final int APR_OC_REASON_RUNNING    = 5;
+
+    /* apr_kill_conditions_e enumeration */
+    /** process is never sent any signals */
+    public static final int APR_KILL_NEVER         = 0;
+    /** process is sent SIGKILL on apr_pool_t cleanup */
+    public static final int APR_KILL_ALWAYS        = 1;
+    /** SIGTERM, wait 3 seconds, SIGKILL */
+    public static final int APR_KILL_AFTER_TIMEOUT = 2;
+    /** wait forever for the process to complete */
+    public static final int APR_JUST_WAIT          = 3;
+    /** send SIGTERM and then wait */
+    public static final int APR_KILL_ONLY_ONCE     = 4;
+
+    public static final int APR_PROC_DETACH_FOREGROUND = 0; /** Do not detach */
+    public static final int APR_PROC_DETACH_DAEMONIZE  = 1; /** Detach */
+
+    /* Maximum number of arguments for create process call */
+    public static final int MAX_ARGS_SIZE          = 1024;
+    /* Maximum number of environment variables for create process call */
+    public static final int MAX_ENV_SIZE           = 1024;
+
+    /**
+     * Allocate apr_proc_t structure from pool
+     * This is not an apr function.
+     * @param cont The pool to use.
+     */
+    public static native long alloc(long cont);
+
+    /**
+     * This is currently the only non-portable call in APR.  This executes
+     * a standard unix fork.
+     * @param proc The resulting process handle.
+     * @param cont The pool to use.
+     * @return APR_INCHILD for the child, and APR_INPARENT for the parent
+     * or an error.
+     */
+    public static native int fork(long [] proc, long cont);
+
+    /**
+     * Create a new process and execute a new program within that process.
+     * This function returns without waiting for the new process to terminate;
+     * use apr_proc_wait for that.
+     * @param progname The program to run
+     * @param args The arguments to pass to the new program.  The first
+     *             one should be the program name.
+     * @param env The new environment table for the new process.  This
+     *            should be a list of NULL-terminated strings. This argument
+     *            is ignored for APR_PROGRAM_ENV, APR_PROGRAM_PATH, and
+     *            APR_SHELLCMD_ENV types of commands.
+     * @param attr The procattr we should use to determine how to create the new
+     * process
+     * @param pool The pool to use.
+     * @return The resulting process handle.
+     */
+    public static native int create(long proc, String progname,
+                                    String [] args, String [] env,
+                                    long attr, long pool);
+
+    /**
+     * Wait for a child process to die
+     * @param proc The process handle that corresponds to the desired child process
+     * @param exit exit[0] The returned exit status of the child, if a child process
+     *                dies, or the signal that caused the child to die.
+     *                On platforms that don't support obtaining this information,
+     *                the status parameter will be returned as APR_ENOTIMPL.
+     * exit[1] Why the child died, the bitwise or of:
+     * <PRE>
+     * APR_PROC_EXIT         -- process terminated normally
+     * APR_PROC_SIGNAL       -- process was killed by a signal
+     * APR_PROC_SIGNAL_CORE  -- process was killed by a signal, and
+     *                          generated a core dump.
+     * </PRE>
+     * @param waithow How should we wait.  One of:
+     * <PRE>
+     * APR_WAIT   -- block until the child process dies.
+     * APR_NOWAIT -- return immediately regardless of if the
+     *               child is dead or not.
+     * </PRE>
+     * @return The childs status is in the return code to this process.  It is one of:
+     * <PRE>
+     * APR_CHILD_DONE     -- child is no longer running.
+     * APR_CHILD_NOTDONE  -- child is still running.
+     * </PRE>
+     */
+    public static native int wait(long proc, int [] exit, int waithow);
+
+    /**
+     * Wait for any current child process to die and return information
+     * about that child.
+     * @param proc Pointer to NULL on entry, will be filled out with child's
+     *             information
+     * @param exit exit[0] The returned exit status of the child, if a child process
+     *                dies, or the signal that caused the child to die.
+     *                On platforms that don't support obtaining this information,
+     *                the status parameter will be returned as APR_ENOTIMPL.
+     * exit[1] Why the child died, the bitwise or of:
+     * <PRE>
+     * APR_PROC_EXIT         -- process terminated normally
+     * APR_PROC_SIGNAL       -- process was killed by a signal
+     * APR_PROC_SIGNAL_CORE  -- process was killed by a signal, and
+     *                          generated a core dump.
+     * </PRE>
+     * @param waithow How should we wait.  One of:
+     * <PRE>
+     * APR_WAIT   -- block until the child process dies.
+     * APR_NOWAIT -- return immediately regardless of if the
+     *               child is dead or not.
+     * </PRE>
+     * @param pool Pool to allocate child information out of.
+     */
+    public static native int waitAllProcs(long proc, int [] exit,
+                                          int waithow, long pool);
+
+     /**
+     * Detach the process from the controlling terminal.
+     * @param daemonize set to non-zero if the process should daemonize
+     *                  and become a background process, else it will
+     *                  stay in the foreground.
+     */
+    public static native int detach(int daemonize);
+
+    /**
+     * Terminate a process.
+     * @param proc The process to terminate.
+     * @param sig How to kill the process.
+     */
+    public static native int kill(long proc, int sig);
+
+}
diff --git a/java/src/org/apache/tomcat/jni/ProcErrorCallback.java b/java/src/org/apache/tomcat/jni/ProcErrorCallback.java
new file mode 100644
index 0000000..60f53c8
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/ProcErrorCallback.java
@@ -0,0 +1,36 @@
+/*
+ *  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.tomcat.jni;
+
+/** ProcErrorCallback Interface
+ *
+ * @author Mladen Turk
+ */
+public interface ProcErrorCallback {
+
+    /**
+     * Called in the child process if APR encounters an error
+     * in the child prior to running the specified program.
+     * @param pool Pool associated with the apr_proc_t.  If your child
+     *             error function needs user data, associate it with this
+     *             pool.
+     * @param err APR error code describing the error
+     * @param description Text description of type of processing which failed
+     */
+    public void callback(long pool, int err, String description);
+}
diff --git a/java/src/org/apache/tomcat/jni/Procattr.java b/java/src/org/apache/tomcat/jni/Procattr.java
new file mode 100644
index 0000000..7f6869c
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Procattr.java
@@ -0,0 +1,170 @@
+/*
+ *  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.tomcat.jni;
+
+/** Procattr
+ *
+ * @author Mladen Turk
+ */
+public class Procattr {
+
+    /**
+     * Create and initialize a new procattr variable
+     * @param cont The pool to use
+     * @return The newly created procattr.
+     */
+    public static native long create(long cont)
+        throws Error;
+
+    /**
+     * Determine if any of stdin, stdout, or stderr should be linked to pipes
+     * when starting a child process.
+     * @param attr The procattr we care about.
+     * @param in Should stdin be a pipe back to the parent?
+     * @param out Should stdout be a pipe back to the parent?
+     * @param err Should stderr be a pipe back to the parent?
+     */
+    public static native int ioSet(long attr, int in, int out, int err);
+    /**
+     * Set the child_in and/or parent_in values to existing apr_file_t values.
+     * <br>
+     * This is NOT a required initializer function. This is
+     * useful if you have already opened a pipe (or multiple files)
+     * that you wish to use, perhaps persistently across multiple
+     * process invocations - such as a log file. You can save some
+     * extra function calls by not creating your own pipe since this
+     * creates one in the process space for you.
+     * @param attr The procattr we care about.
+     * @param in apr_file_t value to use as child_in. Must be a valid file.
+     * @param parent apr_file_t value to use as parent_in. Must be a valid file.
+     */
+    public static native int childInSet(long attr, long in, long parent);
+
+    /**
+     * Set the child_out and parent_out values to existing apr_file_t values.
+     * <br>
+     * This is NOT a required initializer function. This is
+     * useful if you have already opened a pipe (or multiple files)
+     * that you wish to use, perhaps persistently across multiple
+     * process invocations - such as a log file.
+     * @param attr The procattr we care about.
+     * @param out apr_file_t value to use as child_out. Must be a valid file.
+     * @param parent apr_file_t value to use as parent_out. Must be a valid file.
+     */
+    public static native int childOutSet(long attr, long out, long parent);
+
+    /**
+     * Set the child_err and parent_err values to existing apr_file_t values.
+     * <br>
+     * This is NOT a required initializer function. This is
+     * useful if you have already opened a pipe (or multiple files)
+     * that you wish to use, perhaps persistently across multiple
+     * process invocations - such as a log file.
+     * @param attr The procattr we care about.
+     * @param err apr_file_t value to use as child_err. Must be a valid file.
+     * @param parent apr_file_t value to use as parent_err. Must be a valid file.
+     */
+    public static native int childErrSet(long attr, long err, long parent);
+
+    /**
+     * Set which directory the child process should start executing in.
+     * @param attr The procattr we care about.
+     * @param dir Which dir to start in.  By default, this is the same dir as
+     *            the parent currently resides in, when the createprocess call
+     *            is made.
+     */
+    public static native int dirSet(long attr, String dir);
+
+    /**
+     * Set what type of command the child process will call.
+     * @param attr The procattr we care about.
+     * @param cmd The type of command.  One of:
+     * <PRE>
+     * APR_SHELLCMD     --  Anything that the shell can handle
+     * APR_PROGRAM      --  Executable program   (default)
+     * APR_PROGRAM_ENV  --  Executable program, copy environment
+     * APR_PROGRAM_PATH --  Executable program on PATH, copy env
+     * </PRE>
+     */
+    public static native int cmdtypeSet(long attr, int cmd);
+
+    /**
+     * Determine if the child should start in detached state.
+     * @param attr The procattr we care about.
+     * @param detach Should the child start in detached state?  Default is no.
+     */
+    public static native int detachSet(long attr, int detach);
+
+    /**
+     * Specify that apr_proc_create() should do whatever it can to report
+     * failures to the caller of apr_proc_create(), rather than find out in
+     * the child.
+     * @param attr The procattr describing the child process to be created.
+     * @param chk Flag to indicate whether or not extra work should be done
+     *            to try to report failures to the caller.
+     * <br>
+     * This flag only affects apr_proc_create() on platforms where
+     * fork() is used.  This leads to extra overhead in the calling
+     * process, but that may help the application handle such
+     * errors more gracefully.
+     */
+    public static native int errorCheckSet(long attr, int chk);
+
+    /**
+     * Determine if the child should start in its own address space or using the
+     * current one from its parent
+     * @param attr The procattr we care about.
+     * @param addrspace Should the child start in its own address space?  Default
+     * is no on NetWare and yes on other platforms.
+     */
+    public static native int addrspaceSet(long attr, int addrspace);
+
+    /**
+     * Specify an error function to be called in the child process if APR
+     * encounters an error in the child prior to running the specified program.
+     * @param attr The procattr describing the child process to be created.
+     * @param pool The the pool to use.
+     * @param o The Object to call in the child process.
+     * <br>
+     * At the present time, it will only be called from apr_proc_create()
+     * on platforms where fork() is used.  It will never be called on other
+     * platforms, on those platforms apr_proc_create() will return the error
+     * in the parent process rather than invoke the callback in the now-forked
+     * child process.
+     */
+    public static native void errfnSet(long attr, long pool, Object o);
+
+    /**
+     * Set the username used for running process
+     * @param attr The procattr we care about.
+     * @param username The username used
+     * @param password User password if needed. Password is needed on WIN32
+     *                 or any other platform having
+     *                 APR_PROCATTR_USER_SET_REQUIRES_PASSWORD set.
+     */
+    public static native int userSet(long attr, String username, String password);
+
+    /**
+     * Set the group used for running process
+     * @param attr The procattr we care about.
+     * @param groupname The group name  used
+     */
+    public static native int groupSet(long attr, String groupname);
+
+
+}
diff --git a/java/src/org/apache/tomcat/jni/Registry.java b/java/src/org/apache/tomcat/jni/Registry.java
new file mode 100644
index 0000000..9c493e7
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Registry.java
@@ -0,0 +1,233 @@
+/*
+ *  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.tomcat.jni;
+
+/** Windows Registy support
+ *
+ * @author Mladen Turk
+ */
+public class Registry {
+
+    /* Registry Enums */
+    public static final int HKEY_CLASSES_ROOT       = 1;
+    public static final int HKEY_CURRENT_CONFIG     = 2;
+    public static final int HKEY_CURRENT_USER       = 3;
+    public static final int HKEY_LOCAL_MACHINE      = 4;
+    public static final int HKEY_USERS              = 5;
+
+    public static final int KEY_ALL_ACCESS          = 0x0001;
+    public static final int KEY_CREATE_LINK         = 0x0002;
+    public static final int KEY_CREATE_SUB_KEY      = 0x0004;
+    public static final int KEY_ENUMERATE_SUB_KEYS  = 0x0008;
+    public static final int KEY_EXECUTE             = 0x0010;
+    public static final int KEY_NOTIFY              = 0x0020;
+    public static final int KEY_QUERY_VALUE         = 0x0040;
+    public static final int KEY_READ                = 0x0080;
+    public static final int KEY_SET_VALUE           = 0x0100;
+    public static final int KEY_WOW64_64KEY         = 0x0200;
+    public static final int KEY_WOW64_32KEY         = 0x0400;
+    public static final int KEY_WRITE               = 0x0800;
+
+    public static final int REG_BINARY              = 1;
+    public static final int REG_DWORD               = 2;
+    public static final int REG_EXPAND_SZ           = 3;
+    public static final int REG_MULTI_SZ            = 4;
+    public static final int REG_QWORD               = 5;
+    public static final int REG_SZ                  = 6;
+
+     /**
+     * Create or open a Registry Key.
+     * @param name Registry Subkey to open
+     * @param root Root key, one of HKEY_*
+     * @param sam Access mask that specifies the access rights for the key.
+     * @param pool Pool used for native memory allocation
+     * @return Opened Registry key
+     */
+    public static native long create(int root, String name, int sam, long pool)
+        throws Error;
+
+     /**
+     * Opens the specified Registry Key.
+     * @param name Registry Subkey to open
+     * @param root Root key, one of HKEY_*
+     * @param sam Access mask that specifies the access rights for the key.
+     * @param pool Pool used for native memory allocation
+     * @return Opened Registry key
+     */
+    public static native long open(int root, String name, int sam, long pool)
+        throws Error;
+
+    /**
+     * Close the specified Registry key.
+     * @param key The Registry key descriptor to close.
+     */
+    public static native int close(long key);
+
+    /**
+     * Get the Registry key type.
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Value type or negative error value
+     */
+    public static native int getType(long key, String name);
+
+    /**
+     * Get the Registry value for REG_DWORD
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Registry key value
+     */
+    public static native int getValueI(long key, String name)
+        throws Error;
+
+    /**
+     * Get the Registry value for REG_QWORD or REG_DWORD
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Registry key value
+     */
+    public static native long getValueJ(long key, String name)
+        throws Error;
+
+    /**
+     * Get the Registry key length.
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Value size or negative error value
+     */
+    public static native int getSize(long key, String name);
+
+    /**
+     * Get the Registry value for REG_SZ or REG_EXPAND_SZ
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Registry key value
+     */
+    public static native String getValueS(long key, String name)
+        throws Error;
+
+    /**
+     * Get the Registry value for REG_MULTI_SZ
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Registry key value
+     */
+    public static native String[] getValueA(long key, String name)
+        throws Error;
+
+    /**
+     * Get the Registry value for REG_BINARY
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to query
+     * @return Registry key value
+     */
+    public static native byte[] getValueB(long key, String name)
+        throws Error;
+
+
+    /**
+     * Set the Registry value for REG_DWORD
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to set
+     * @param val The the value to set
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int setValueI(long key, String name, int val);
+
+    /**
+     * Set the Registry value for REG_QWORD
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to set
+     * @param val The the value to set
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int setValueJ(long key, String name, long val);
+
+    /**
+     * Set the Registry value for REG_SZ
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to set
+     * @param val The the value to set
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int setValueS(long key, String name, String val);
+
+    /**
+     * Set the Registry value for REG_EXPAND_SZ
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to set
+     * @param val The the value to set
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int setValueE(long key, String name, String val);
+
+     /**
+     * Set the Registry value for REG_MULTI_SZ
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to set
+     * @param val The the value to set
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int setValueA(long key, String name, String[] val);
+
+     /**
+     * Set the Registry value for REG_BINARY
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to set
+     * @param val The the value to set
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int setValueB(long key, String name, byte[] val);
+
+    /**
+     * Enumerate the Registry subkeys
+     * @param key The Registry key descriptor to use.
+     * @return Array of all subkey names
+     */
+    public static native String[] enumKeys(long key)
+        throws Error;
+
+    /**
+     * Enumerate the Registry values
+     * @param key The Registry key descriptor to use.
+     * @return Array of all value names
+     */
+    public static native String[] enumValues(long key)
+        throws Error;
+
+     /**
+     * Delete the Registry value
+     * @param key The Registry key descriptor to use.
+     * @param name The name of the value to delete
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int deleteValue(long key, String name);
+
+     /**
+     * Delete the Registry subkey
+     * @param root Root key, one of HKEY_*
+     * @param name Subkey to delete
+     * @param onlyIfEmpty If true will not delete a key if
+     *                    it contains any subkeys or values
+     * @return If the function succeeds, the return value is 0
+     */
+    public static native int deleteKey(int root, String name,
+                                       boolean onlyIfEmpty);
+
+
+}
diff --git a/java/src/org/apache/tomcat/jni/SSL.java b/java/src/org/apache/tomcat/jni/SSL.java
new file mode 100644
index 0000000..0e381e8
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/SSL.java
@@ -0,0 +1,738 @@
+/*
+ *  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.tomcat.jni;
+
+/** SSL
+ *
+ * @author Mladen Turk
+ */
+public final class SSL {
+
+    /*
+     * Type definitions mostly from mod_ssl
+     */
+    public static final int UNSET            = -1;
+    /*
+     * Define the certificate algorithm types
+     */
+    public static final int SSL_ALGO_UNKNOWN = 0;
+    public static final int SSL_ALGO_RSA     = (1<<0);
+    public static final int SSL_ALGO_DSA     = (1<<1);
+    public static final int SSL_ALGO_ALL     = (SSL_ALGO_RSA|SSL_ALGO_DSA);
+
+    public static final int SSL_AIDX_RSA     = 0;
+    public static final int SSL_AIDX_DSA     = 1;
+    public static final int SSL_AIDX_MAX     = 2;
+    /*
+     * Define IDs for the temporary RSA keys and DH params
+     */
+
+    public static final int SSL_TMP_KEY_RSA_512  = 0;
+    public static final int SSL_TMP_KEY_RSA_1024 = 1;
+    public static final int SSL_TMP_KEY_RSA_2048 = 2;
+    public static final int SSL_TMP_KEY_RSA_4096 = 3;
+    public static final int SSL_TMP_KEY_DH_512   = 4;
+    public static final int SSL_TMP_KEY_DH_1024  = 5;
+    public static final int SSL_TMP_KEY_DH_2048  = 6;
+    public static final int SSL_TMP_KEY_DH_4096  = 7;
+    public static final int SSL_TMP_KEY_MAX      = 8;
+
+    /*
+     * Define the SSL options
+     */
+    public static final int SSL_OPT_NONE           = 0;
+    public static final int SSL_OPT_RELSET         = (1<<0);
+    public static final int SSL_OPT_STDENVVARS     = (1<<1);
+    public static final int SSL_OPT_EXPORTCERTDATA = (1<<3);
+    public static final int SSL_OPT_FAKEBASICAUTH  = (1<<4);
+    public static final int SSL_OPT_STRICTREQUIRE  = (1<<5);
+    public static final int SSL_OPT_OPTRENEGOTIATE = (1<<6);
+    public static final int SSL_OPT_ALL            = (SSL_OPT_STDENVVARS|SSL_OPT_EXPORTCERTDATA|SSL_OPT_FAKEBASICAUTH|SSL_OPT_STRICTREQUIRE|SSL_OPT_OPTRENEGOTIATE);
+
+    /*
+     * Define the SSL Protocol options
+     */
+    public static final int SSL_PROTOCOL_NONE  = 0;
+    public static final int SSL_PROTOCOL_SSLV2 = (1<<0);
+    public static final int SSL_PROTOCOL_SSLV3 = (1<<1);
+    public static final int SSL_PROTOCOL_TLSV1 = (1<<2);
+    public static final int SSL_PROTOCOL_TLSV1_1 = (1<<3);
+    public static final int SSL_PROTOCOL_TLSV1_2 = (1<<4);
+    public static final int SSL_PROTOCOL_ALL   = (SSL_PROTOCOL_TLSV1 | SSL_PROTOCOL_TLSV1_1 | SSL_PROTOCOL_TLSV1_2);
+
+    /*
+     * Define the SSL verify levels
+     */
+    public static final int SSL_CVERIFY_UNSET          = UNSET;
+    public static final int SSL_CVERIFY_NONE           = 0;
+    public static final int SSL_CVERIFY_OPTIONAL       = 1;
+    public static final int SSL_CVERIFY_REQUIRE        = 2;
+    public static final int SSL_CVERIFY_OPTIONAL_NO_CA = 3;
+
+    /* Use either SSL_VERIFY_NONE or SSL_VERIFY_PEER, the last 2 options
+     * are 'ored' with SSL_VERIFY_PEER if they are desired
+     */
+    public static final int SSL_VERIFY_NONE                 = 0;
+    public static final int SSL_VERIFY_PEER                 = 1;
+    public static final int SSL_VERIFY_FAIL_IF_NO_PEER_CERT = 2;
+    public static final int SSL_VERIFY_CLIENT_ONCE          = 4;
+    public static final int SSL_VERIFY_PEER_STRICT          = (SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
+
+    public static final int SSL_OP_MICROSOFT_SESS_ID_BUG            = 0x00000001;
+    public static final int SSL_OP_NETSCAPE_CHALLENGE_BUG           = 0x00000002;
+    public static final int SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG = 0x00000008;
+    public static final int SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG      = 0x00000010;
+    public static final int SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER       = 0x00000020;
+    public static final int SSL_OP_MSIE_SSLV2_RSA_PADDING           = 0x00000040;
+    public static final int SSL_OP_SSLEAY_080_CLIENT_DH_BUG         = 0x00000080;
+    public static final int SSL_OP_TLS_D5_BUG                       = 0x00000100;
+    public static final int SSL_OP_TLS_BLOCK_PADDING_BUG            = 0x00000200;
+
+    /* Disable SSL 3.0/TLS 1.0 CBC vulnerability workaround that was added
+     * in OpenSSL 0.9.6d.  Usually (depending on the application protocol)
+     * the workaround is not needed.  Unfortunately some broken SSL/TLS
+     * implementations cannot handle it at all, which is why we include
+     * it in SSL_OP_ALL. */
+    public static final int SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS      = 0x00000800;
+
+    /* SSL_OP_ALL: various bug workarounds that should be rather harmless.
+     *             This used to be 0x000FFFFFL before 0.9.7. */
+    public static final int SSL_OP_ALL                              = 0x00000FFF;
+    /* As server, disallow session resumption on renegotiation */
+    public static final int SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION = 0x00010000;
+    /* Don't use compression even if supported */
+    public static final int SSL_OP_NO_COMPRESSION                         = 0x00020000;
+    /* Permit unsafe legacy renegotiation */
+    public static final int SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION      = 0x00040000;
+    /* If set, always create a new key when using tmp_eddh parameters */
+    public static final int SSL_OP_SINGLE_ECDH_USE                  = 0x00080000;
+    /* If set, always create a new key when using tmp_dh parameters */
+    public static final int SSL_OP_SINGLE_DH_USE                    = 0x00100000;
+    /* Set to always use the tmp_rsa key when doing RSA operations,
+     * even when this violates protocol specs */
+    public static final int SSL_OP_EPHEMERAL_RSA                    = 0x00200000;
+    /* Set on servers to choose the cipher according to the server's
+     * preferences */
+    public static final int SSL_OP_CIPHER_SERVER_PREFERENCE         = 0x00400000;
+    /* If set, a server will allow a client to issue a SSLv3.0 version number
+     * as latest version supported in the premaster secret, even when TLSv1.0
+     * (version 3.1) was announced in the client hello. Normally this is
+     * forbidden to prevent version rollback attacks. */
+    public static final int SSL_OP_TLS_ROLLBACK_BUG                 = 0x00800000;
+
+    public static final int SSL_OP_NO_SSLv2                         = 0x01000000;
+    public static final int SSL_OP_NO_SSLv3                         = 0x02000000;
+    public static final int SSL_OP_NO_TLSv1                         = 0x04000000;
+    public static final int SSL_OP_NO_TLSv1_2                       = 0x08000000;
+    public static final int SSL_OP_NO_TLSv1_1                       = 0x10000000;
+
+    public static final int SSL_OP_NO_TICKET                        = 0x00004000;
+
+    // SSL_OP_PKCS1_CHECK_1 and SSL_OP_PKCS1_CHECK_2 flags are unsupported
+    // in the current version of OpenSSL library. See ssl.h changes in commit
+    // 7409d7ad517650db332ae528915a570e4e0ab88b (30 Apr 2011) of OpenSSL.
+    /**
+     * @deprecated Unsupported in the current version of OpenSSL
+     */
+    @Deprecated
+    public static final int SSL_OP_PKCS1_CHECK_1                    = 0x08000000;
+    /**
+     * @deprecated Unsupported in the current version of OpenSSL
+     */
+    @Deprecated
+    public static final int SSL_OP_PKCS1_CHECK_2                    = 0x10000000;
+    public static final int SSL_OP_NETSCAPE_CA_DN_BUG               = 0x20000000;
+    public static final int SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG  = 0x40000000;
+
+    public static final int SSL_CRT_FORMAT_UNDEF    = 0;
+    public static final int SSL_CRT_FORMAT_ASN1     = 1;
+    public static final int SSL_CRT_FORMAT_TEXT     = 2;
+    public static final int SSL_CRT_FORMAT_PEM      = 3;
+    public static final int SSL_CRT_FORMAT_NETSCAPE = 4;
+    public static final int SSL_CRT_FORMAT_PKCS12   = 5;
+    public static final int SSL_CRT_FORMAT_SMIME    = 6;
+    public static final int SSL_CRT_FORMAT_ENGINE   = 7;
+
+    public static final int SSL_MODE_CLIENT         = 0;
+    public static final int SSL_MODE_SERVER         = 1;
+    public static final int SSL_MODE_COMBINED       = 2;
+
+    public static final int SSL_SHUTDOWN_TYPE_UNSET    = 0;
+    public static final int SSL_SHUTDOWN_TYPE_STANDARD = 1;
+    public static final int SSL_SHUTDOWN_TYPE_UNCLEAN  = 2;
+    public static final int SSL_SHUTDOWN_TYPE_ACCURATE = 3;
+
+    public static final int SSL_INFO_SESSION_ID                = 0x0001;
+    public static final int SSL_INFO_CIPHER                    = 0x0002;
+    public static final int SSL_INFO_CIPHER_USEKEYSIZE         = 0x0003;
+    public static final int SSL_INFO_CIPHER_ALGKEYSIZE         = 0x0004;
+    public static final int SSL_INFO_CIPHER_VERSION            = 0x0005;
+    public static final int SSL_INFO_CIPHER_DESCRIPTION        = 0x0006;
+    public static final int SSL_INFO_PROTOCOL                  = 0x0007;
+
+    /* To obtain the CountryName of the Client Certificate Issuer
+     * use the SSL_INFO_CLIENT_I_DN + SSL_INFO_DN_COUNTRYNAME
+     */
+    public static final int SSL_INFO_CLIENT_S_DN               = 0x0010;
+    public static final int SSL_INFO_CLIENT_I_DN               = 0x0020;
+    public static final int SSL_INFO_SERVER_S_DN               = 0x0040;
+    public static final int SSL_INFO_SERVER_I_DN               = 0x0080;
+
+    public static final int SSL_INFO_DN_COUNTRYNAME            = 0x0001;
+    public static final int SSL_INFO_DN_STATEORPROVINCENAME    = 0x0002;
+    public static final int SSL_INFO_DN_LOCALITYNAME           = 0x0003;
+    public static final int SSL_INFO_DN_ORGANIZATIONNAME       = 0x0004;
+    public static final int SSL_INFO_DN_ORGANIZATIONALUNITNAME = 0x0005;
+    public static final int SSL_INFO_DN_COMMONNAME             = 0x0006;
+    public static final int SSL_INFO_DN_TITLE                  = 0x0007;
+    public static final int SSL_INFO_DN_INITIALS               = 0x0008;
+    public static final int SSL_INFO_DN_GIVENNAME              = 0x0009;
+    public static final int SSL_INFO_DN_SURNAME                = 0x000A;
+    public static final int SSL_INFO_DN_DESCRIPTION            = 0x000B;
+    public static final int SSL_INFO_DN_UNIQUEIDENTIFIER       = 0x000C;
+    public static final int SSL_INFO_DN_EMAILADDRESS           = 0x000D;
+
+    public static final int SSL_INFO_CLIENT_M_VERSION          = 0x0101;
+    public static final int SSL_INFO_CLIENT_M_SERIAL           = 0x0102;
+    public static final int SSL_INFO_CLIENT_V_START            = 0x0103;
+    public static final int SSL_INFO_CLIENT_V_END              = 0x0104;
+    public static final int SSL_INFO_CLIENT_A_SIG              = 0x0105;
+    public static final int SSL_INFO_CLIENT_A_KEY              = 0x0106;
+    public static final int SSL_INFO_CLIENT_CERT               = 0x0107;
+    public static final int SSL_INFO_CLIENT_V_REMAIN           = 0x0108;
+
+    public static final int SSL_INFO_SERVER_M_VERSION          = 0x0201;
+    public static final int SSL_INFO_SERVER_M_SERIAL           = 0x0202;
+    public static final int SSL_INFO_SERVER_V_START            = 0x0203;
+    public static final int SSL_INFO_SERVER_V_END              = 0x0204;
+    public static final int SSL_INFO_SERVER_A_SIG              = 0x0205;
+    public static final int SSL_INFO_SERVER_A_KEY              = 0x0206;
+    public static final int SSL_INFO_SERVER_CERT               = 0x0207;
+    /* Return client certificate chain.
+     * Add certificate chain number to that flag (0 ... verify depth)
+     */
+    public static final int SSL_INFO_CLIENT_CERT_CHAIN         = 0x0400;
+
+    /* Only support OFF and SERVER for now */
+    public static final long SSL_SESS_CACHE_OFF = 0x0000;
+    public static final long SSL_SESS_CACHE_SERVER = 0x0002;
+
+    public static final int SSL_SELECTOR_FAILURE_NO_ADVERTISE = 0;
+    public static final int SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL = 1;
+
+    public static final int SSL_ST_CONNECT = 0x1000;
+    public static final int SSL_ST_ACCEPT =  0x2000;
+
+    /* Return OpenSSL version number */
+    public static native int version();
+
+    /* Return OpenSSL version string */
+    public static native String versionString();
+
+    /**
+     * Initialize OpenSSL support.
+     * This function needs to be called once for the
+     * lifetime of JVM. Library.init() has to be called before.
+     * @param engine Support for external a Crypto Device ("engine"),
+     *                usually
+     * a hardware accelerator card for crypto operations.
+     * @return APR status code
+     */
+    public static native int initialize(String engine);
+
+    /**
+     * Get the status of FIPS Mode.
+     *
+     * @return FIPS_mode return code. It is <code>0</code> if OpenSSL is not
+     *  in FIPS mode, <code>1</code> if OpenSSL is in FIPS Mode.
+     * @throws Exception If tcnative was not compiled with FIPS Mode available.
+     * @see <a href="http://wiki.openssl.org/index.php/FIPS_mode%28%29">OpenSSL method FIPS_mode()</a>
+     */
+    public static native int fipsModeGet() throws Exception;
+
+    /**
+     * Enable/Disable FIPS Mode.
+     *
+     * @param mode 1 - enable, 0 - disable
+     *
+     * @return FIPS_mode_set return code
+     * @throws Exception If tcnative was not compiled with FIPS Mode available,
+     *  or if {@code FIPS_mode_set()} call returned an error value.
+     * @see <a href="http://wiki.openssl.org/index.php/FIPS_mode_set%28%29">OpenSSL method FIPS_mode_set()</a>
+     */
+    public static native int fipsModeSet(int mode) throws Exception;
+
+    /**
+     * Add content of the file to the PRNG
+     * @param filename Filename containing random data.
+     *        If null the default file will be tested.
+     *        The seed file is $RANDFILE if that environment variable is
+     *        set, $HOME/.rnd otherwise.
+     *        In case both files are unavailable builtin
+     *        random seed generator is used.
+     */
+    public static native boolean randLoad(String filename);
+
+    /**
+     * Writes a number of random bytes (currently 1024) to
+     * file <code>filename</code> which can be used to initialize the PRNG
+     * by calling randLoad in a later session.
+     * @param filename Filename to save the data
+     */
+    public static native boolean randSave(String filename);
+
+    /**
+     * Creates random data to filename
+     * @param filename Filename to save the data
+     * @param len The length of random sequence in bytes
+     * @param base64 Output the data in Base64 encoded format
+     */
+    public static native boolean randMake(String filename, int len,
+                                          boolean base64);
+
+    /**
+     * Sets global random filename.
+     * @param filename Filename to use.
+     *        If set it will be used for SSL initialization
+     *        and all contexts where explicitly not set.
+     */
+    public static native void randSet(String filename);
+
+    /**
+     * Initialize new BIO
+     * @param pool The pool to use.
+     * @param callback BIOCallback to use
+     * @return new BIO handle
+     */
+     public static native long newBIO(long pool, BIOCallback callback)
+            throws Exception;
+
+    /**
+     * Initialize new in-memory BIO that is located in the secure heap.
+     * @return New BIO handle
+     */
+    public static native long newMemBIO() throws Exception;
+
+    /**
+     * Close BIO and dereference callback object
+     * @param bio BIO to close and destroy.
+     * @return APR Status code
+     */
+     public static native int closeBIO(long bio);
+
+    /**
+     * Set global Password callback for obtaining passwords.
+     * @param callback PasswordCallback implementation to use.
+     */
+     public static native void setPasswordCallback(PasswordCallback callback);
+
+    /**
+     * Set global Password for decrypting certificates and keys.
+     * @param password Password to use.
+     */
+     public static native void setPassword(String password);
+
+    /**
+     * Generate temporary RSA key.
+     * <br>
+     * Index can be one of:
+     * <PRE>
+     * SSL_TMP_KEY_RSA_512
+     * SSL_TMP_KEY_RSA_1024
+     * SSL_TMP_KEY_RSA_2048
+     * SSL_TMP_KEY_RSA_4096
+     * </PRE>
+     * By default 512 and 1024 keys are generated on startup.
+     * You can use a low priority thread to generate them on the fly.
+     * @param idx temporary key index.
+     */
+    public static native boolean generateRSATempKey(int idx);
+
+    /**
+     * Load temporary DSA key from file
+     * <br>
+     * Index can be one of:
+     * <PRE>
+     * SSL_TMP_KEY_DH_512
+     * SSL_TMP_KEY_DH_1024
+     * SSL_TMP_KEY_DH_2048
+     * SSL_TMP_KEY_DH_4096
+     * </PRE>
+     * @param idx temporary key index.
+     * @param file File containing DH params.
+     */
+    public static native boolean loadDSATempKey(int idx, String file);
+
+    /**
+     * Return last SSL error string
+     */
+    public static native String getLastError();
+
+    /**
+     * Return true if all the requested SSL_OP_* are supported by OpenSSL.
+     *
+     * <i>Note that for versions of tcnative &lt; 1.1.25, this method will
+     * return <code>true</code> if and only if <code>op</code>=
+     * {@link #SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION} and tcnative
+     * supports that flag.</i>
+     *
+     * @param op Bitwise-OR of all SSL_OP_* to test.
+     *
+     * @return true if all SSL_OP_* are supported by OpenSSL library.
+     */
+    public static native boolean hasOp(int op);
+
+    /*
+     * Begin Twitter API additions
+     */
+
+    public static final int SSL_SENT_SHUTDOWN = 1;
+    public static final int SSL_RECEIVED_SHUTDOWN = 2;
+
+    public static final int SSL_ERROR_NONE             = 0;
+    public static final int SSL_ERROR_SSL              = 1;
+    public static final int SSL_ERROR_WANT_READ        = 2;
+    public static final int SSL_ERROR_WANT_WRITE       = 3;
+    public static final int SSL_ERROR_WANT_X509_LOOKUP = 4;
+    public static final int SSL_ERROR_SYSCALL          = 5; /* look at error stack/return value/errno */
+    public static final int SSL_ERROR_ZERO_RETURN      = 6;
+    public static final int SSL_ERROR_WANT_CONNECT     = 7;
+    public static final int SSL_ERROR_WANT_ACCEPT      = 8;
+
+    /**
+     * SSL_new
+     * @param ctx Server or Client context to use.
+     * @param server if true configure SSL instance to use accept handshake routines
+     *               if false configure SSL instance to use connect handshake routines
+     * @return pointer to SSL instance (SSL *)
+     */
+    public static native long newSSL(long ctx, boolean server);
+
+    /**
+     * SSL_set_bio
+     * @param ssl SSL pointer (SSL *)
+     * @param rbio read BIO pointer (BIO *)
+     * @param wbio write BIO pointer (BIO *)
+     */
+    public static native void setBIO(long ssl, long rbio, long wbio);
+
+    /**
+     * SSL_get_error
+     * @param ssl SSL pointer (SSL *)
+     * @param ret TLS/SSL I/O return value
+     */
+    public static native int getError(long ssl, int ret);
+
+    /**
+     * BIO_ctrl_pending
+     * @param bio BIO pointer (BIO *)
+     * @return
+     */
+    public static native int pendingWrittenBytesInBIO(long bio);
+
+    /**
+     * SSL_pending
+     * @param ssl SSL pointer (SSL *)
+     * @return
+     */
+    public static native int pendingReadableBytesInSSL(long ssl);
+
+    /**
+     * BIO_write
+     * @param bio
+     * @param wbuf
+     * @param wlen
+     * @return
+     */
+    public static native int writeToBIO(long bio, long wbuf, int wlen);
+
+    /**
+     * BIO_read
+     * @param bio
+     * @param rbuf
+     * @param rlen
+     * @return
+     */
+    public static native int readFromBIO(long bio, long rbuf, int rlen);
+
+    /**
+     * SSL_write
+     * @param ssl the SSL instance (SSL *)
+     * @param wbuf
+     * @param wlen
+     * @return
+     */
+    public static native int writeToSSL(long ssl, long wbuf, int wlen);
+
+    /**
+     * SSL_read
+     * @param ssl the SSL instance (SSL *)
+     * @param rbuf
+     * @param rlen
+     * @return
+     */
+    public static native int readFromSSL(long ssl, long rbuf, int rlen);
+
+    /**
+     * SSL_get_shutdown
+     * @param ssl the SSL instance (SSL *)
+     * @return
+     */
+    public static native int getShutdown(long ssl);
+
+    /**
+     * SSL_set_shutdown
+     * @param ssl the SSL instance (SSL *)
+     * @param mode
+     */
+    public static native void setShutdown(long ssl, int mode);
+
+    /**
+     * SSL_free
+     * @param ssl the SSL instance (SSL *)
+     */
+    public static native void freeSSL(long ssl);
+
+    /**
+     * Wire up internal and network BIOs for the given SSL instance.
+     *
+     * <b>Warning: you must explicitly free this resource by calling freeBIO</b>
+     *
+     * While the SSL's internal/application data BIO will be freed when freeSSL is called on
+     * the provided SSL instance, you must call freeBIO on the returned network BIO.
+     *
+     * @param ssl the SSL instance (SSL *)
+     * @return pointer to the Network BIO (BIO *)
+     */
+    public static native long makeNetworkBIO(long ssl);
+
+    /**
+     * BIO_free
+     * @param bio
+     */
+    public static native void freeBIO(long bio);
+
+    /**
+     * BIO_flush
+     * @param bio
+     */
+    public static native void flushBIO(long bio);
+
+    /**
+     * SSL_shutdown
+     * @param ssl the SSL instance (SSL *)
+     * @return
+     */
+    public static native int shutdownSSL(long ssl);
+
+    /**
+     * Get the error number representing the last error OpenSSL encountered on this thread.
+     * @return
+     */
+    public static native int getLastErrorNumber();
+
+    /**
+     * SSL_get_cipher
+     * @param ssl the SSL instance (SSL *)
+     * @return
+     */
+    public static native String getCipherForSSL(long ssl);
+
+    /**
+     * SSL_get_version
+     * @param ssl the SSL instance (SSL *)
+     * @return
+     */
+    public static native String getVersion(long ssl);
+
+    /**
+     * SSL_do_handshake
+     * @param ssl the SSL instance (SSL *)
+     */
+    public static native int doHandshake(long ssl);
+
+    /**
+     * SSL_in_init
+     * @param SSL
+     * @return
+     */
+    public static native int isInInit(long SSL);
+
+    /**
+     * SSL_get0_next_proto_negotiated
+     * @param ssl the SSL instance (SSL *)
+     * @return
+     */
+    public static native String getNextProtoNegotiated(long ssl);
+
+    /*
+     * End Twitter API Additions
+     */
+
+    /**
+     * SSL_get0_alpn_selected
+     * @param ssl the SSL instance (SSL *)
+     * @return
+     */
+    public static native String getAlpnSelected(long ssl);
+
+    /**
+     * Get the peer certificate chain or {@code null} if non was send.
+     */
+    public static native byte[][] getPeerCertChain(long ssl);
+
+    /**
+     * Get the peer certificate or {@code null} if non was send.
+     */
+    public static native byte[] getPeerCertificate(long ssl);
+    /*
+     * Get the error number representing for the given {@code errorNumber}.
+     */
+    public static native String getErrorString(long errorNumber);
+
+    /**
+     * SSL_get_time
+     * @param ssl the SSL instance (SSL *)
+     * @return returns the time at which the session ssl was established. The time is given in seconds since the Epoch
+     */
+    public static native long getTime(long ssl);
+
+    /**
+     * SSL_get_timeout
+     * @param ssl the SSL instance (SSL *)
+     * @return returns the timeout for the session ssl The time is given in seconds since the Epoch
+     */
+    public static native long getTimeout(long ssl);
+
+    /**
+     * SSL_set_timeout
+     * @param ssl the SSL instance (SSL *)
+     * @param seconds timeout in seconds
+     * @return returns the timeout for the session ssl before this call. The time is given in seconds since the Epoch
+     */
+    public static native long setTimeout(long ssl, long seconds);
+
+    /**
+     * Set Type of Client Certificate verification and Maximum depth of CA Certificates
+     * in Client Certificate verification.
+     * <br />
+     * This directive sets the Certificate verification level for the Client
+     * Authentication. Notice that this directive can be used both in per-server
+     * and per-directory context. In per-server context it applies to the client
+     * authentication process used in the standard SSL handshake when a connection
+     * is established. In per-directory context it forces a SSL renegotiation with
+     * the reconfigured client verification level after the HTTP request was read
+     * but before the HTTP response is sent.
+     * <br />
+     * The following levels are available for level:
+     * <pre>
+     * SSL_CVERIFY_NONE           - No client Certificate is required at all
+     * SSL_CVERIFY_OPTIONAL       - The client may present a valid Certificate
+     * SSL_CVERIFY_REQUIRE        - The client has to present a valid Certificate
+     * SSL_CVERIFY_OPTIONAL_NO_CA - The client may present a valid Certificate
+     *                              but it need not to be (successfully) verifiable
+     * </pre>
+     * <br />
+     * The depth actually is the maximum number of intermediate certificate issuers,
+     * i.e. the number of CA certificates which are max allowed to be followed while
+     * verifying the client certificate. A depth of 0 means that self-signed client
+     * certificates are accepted only, the default depth of 1 means the client
+     * certificate can be self-signed or has to be signed by a CA which is directly
+     * known to the server (i.e. the CA's certificate is under
+     * {@code setCACertificatePath}, etc.
+     *
+     * @param ssl the SSL instance (SSL *)
+     * @param level Type of Client Certificate verification.
+     * @param depth Maximum depth of CA Certificates in Client Certificate
+     *              verification.
+     */
+    public static native void setVerify(long ssl, int level, int depth);
+
+    /**
+     * Set OpenSSL Option.
+     * @param ssl the SSL instance (SSL *)
+     * @param options  See SSL.SSL_OP_* for option flags.
+     */
+    public static native void setOptions(long ssl, int options);
+
+    /**
+     * Get OpenSSL Option.
+     * @param ssl the SSL instance (SSL *)
+     * @return options  See SSL.SSL_OP_* for option flags.
+     */
+    public static native int getOptions(long ssl);
+
+    /**
+     * Returns all Returns the cipher suites that are available for negotiation in an SSL handshake.
+     * @param ssl the SSL instance (SSL *)
+     * @return ciphers
+     */
+    public static native String[] getCiphers(long ssl);
+
+    /**
+     * Returns the cipher suites available for negotiation in SSL handshake.
+     * <br />
+     * This complex directive uses a colon-separated cipher-spec string consisting
+     * of OpenSSL cipher specifications to configure the Cipher Suite the client
+     * is permitted to negotiate in the SSL handshake phase. Notice that this
+     * directive can be used both in per-server and per-directory context.
+     * In per-server context it applies to the standard SSL handshake when a
+     * connection is established. In per-directory context it forces a SSL
+     * renegotiation with the reconfigured Cipher Suite after the HTTP request
+     * was read but before the HTTP response is sent.
+     * @param ssl the SSL instance (SSL *)
+     * @param ciphers an SSL cipher specification
+     */
+    public static native boolean setCipherSuites(long ssl, String ciphers)
+            throws Exception;
+
+    /**
+     * Returns the ID of the session as byte array representation.
+     *
+     * @param ssl the SSL instance (SSL *)
+     * @return the session as byte array representation obtained via SSL_SESSION_get_id.
+     */
+    public static native byte[] getSessionId(long ssl);
+
+    /**
+     * Returns the number of handshakes done for this SSL instance. This also includes renegations.
+     *
+     * @param ssl the SSL instance (SSL *)
+     * @return the number of handshakes done for this SSL instance.
+     */
+    public static native int getHandshakeCount(long ssl);
+
+    /**
+     * Clear all the errors from the error queue that OpenSSL encountered on this thread.
+     */
+    public static native void clearError();
+
+    /**
+     * Call SSL_renegotiate.
+     *
+     * @param ssl the SSL instance (SSL *)
+     * @return the result of the operation
+     */
+    public static native int renegotiate(long ssl);
+
+    /**
+     * Call SSL_set_state.
+     *
+     * @param ssl the SSL instance (SSL *)
+     */
+    public static native void setState(long ssl, int state);
+}
diff --git a/java/src/org/apache/tomcat/jni/SSLContext.java b/java/src/org/apache/tomcat/jni/SSLContext.java
new file mode 100644
index 0000000..a40d318
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/SSLContext.java
@@ -0,0 +1,497 @@
+/*
+ *  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.tomcat.jni;
+
+/** SSL Context
+ *
+ * @author Mladen Turk
+ */
+public final class SSLContext {
+
+
+    /**
+     * Initialize new SSL context
+     * @param pool The pool to use.
+     * @param protocol The SSL protocol to use. It can be any combination of
+     * the following:
+     * <PRE>
+     * {@link SSL#SSL_PROTOCOL_SSLV2}
+     * {@link SSL#SSL_PROTOCOL_SSLV3}
+     * {@link SSL#SSL_PROTOCOL_TLSV1}
+     * {@link SSL#SSL_PROTOCOL_TLSV1_1}
+     * {@link SSL#SSL_PROTOCOL_TLSV1_2}
+     * {@link SSL#SSL_PROTOCOL_ALL} ( == all TLS versions, no SSL)
+     * </PRE>
+     * @param mode SSL mode to use
+     * <PRE>
+     * SSL_MODE_CLIENT
+     * SSL_MODE_SERVER
+     * SSL_MODE_COMBINED
+     * </PRE>
+     */
+    public static native long make(long pool, int protocol, int mode)
+        throws Exception;
+
+    /**
+     * Free the resources used by the Context
+     * @param ctx Server or Client context to free.
+     * @return APR Status code.
+     */
+    public static native int free(long ctx);
+
+    /**
+     * Set Session context id. Usually host:port combination.
+     * @param ctx Context to use.
+     * @param id  String that uniquely identifies this context.
+     */
+    public static native void setContextId(long ctx, String id);
+
+    /**
+     * Associate BIOCallback for input or output data capture.
+     * <br>
+     * First word in the output string will contain error
+     * level in the form:
+     * <PRE>
+     * [ERROR]  -- Critical error messages
+     * [WARN]   -- Warning messages
+     * [INFO]   -- Informational messages
+     * [DEBUG]  -- Debugging messaged
+     * </PRE>
+     * Callback can use that word to determine application logging level
+     * by intercepting <b>write</b> call.
+     * If the <b>bio</b> is set to 0 no error messages will be displayed.
+     * Default is to use the stderr output stream.
+     * @param ctx Server or Client context to use.
+     * @param bio BIO handle to use, created with SSL.newBIO
+     * @param dir BIO direction (1 for input 0 for output).
+     */
+    public static native void setBIO(long ctx, long bio, int dir);
+
+    /**
+     * Set OpenSSL Option.
+     * @param ctx Server or Client context to use.
+     * @param options  See SSL.SSL_OP_* for option flags.
+     */
+    public static native void setOptions(long ctx, int options);
+
+    /**
+     * Get OpenSSL Option.
+     * @param ctx Server or Client context to use.
+     * @return options  See SSL.SSL_OP_* for option flags.
+     */
+    public static native int getOptions(long ctx);
+
+    /**
+     * Clears OpenSSL Options.
+     * @param ctx Server or Client context to use.
+     * @param options  See SSL.SSL_OP_* for option flags.
+     */
+    public static native void clearOptions(long ctx, int options);
+
+    /**
+     * Sets the "quiet shutdown" flag for <b>ctx</b> to be
+     * <b>mode</b>. SSL objects created from <b>ctx</b> inherit the
+     * <b>mode</b> valid at the time and may be 0 or 1.
+     * <br>
+     * Normally when a SSL connection is finished, the parties must send out
+     * "close notify" alert messages using L&lt;SSL_shutdown(3)|SSL_shutdown(3)&gt;
+     * for a clean shutdown.
+     * <br>
+     * When setting the "quiet shutdown" flag to 1, <b>SSL.shutdown</b>
+     * will set the internal flags to SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN.
+     * (<b>SSL_shutdown</b> then behaves like called with
+     * SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN.)
+     * The session is thus considered to be shutdown, but no "close notify" alert
+     * is sent to the peer. This behaviour violates the TLS standard.
+     * The default is normal shutdown behaviour as described by the TLS standard.
+     * @param ctx Server or Client context to use.
+     * @param mode True to set the quiet shutdown.
+     */
+    public static native void setQuietShutdown(long ctx, boolean mode);
+
+    /**
+     * Cipher Suite available for negotiation in SSL handshake.
+     * <br>
+     * This complex directive uses a colon-separated cipher-spec string consisting
+     * of OpenSSL cipher specifications to configure the Cipher Suite the client
+     * is permitted to negotiate in the SSL handshake phase. Notice that this
+     * directive can be used both in per-server and per-directory context.
+     * In per-server context it applies to the standard SSL handshake when a
+     * connection is established. In per-directory context it forces a SSL
+     * renegotiation with the reconfigured Cipher Suite after the HTTP request
+     * was read but before the HTTP response is sent.
+     * @param ctx Server or Client context to use.
+     * @param ciphers An SSL cipher specification.
+     */
+    public static native boolean setCipherSuite(long ctx, String ciphers)
+        throws Exception;
+
+    /**
+     * Set File of concatenated PEM-encoded CA CRLs or
+     * directory of PEM-encoded CA Certificates for Client Auth
+     * <br>
+     * This directive sets the all-in-one file where you can assemble the
+     * Certificate Revocation Lists (CRL) of Certification Authorities (CA)
+     * whose clients you deal with. These are used for Client Authentication.
+     * Such a file is simply the concatenation of the various PEM-encoded CRL
+     * files, in order of preference.
+     * <br>
+     * The files in this directory have to be PEM-encoded and are accessed through
+     * hash filenames. So usually you can't just place the Certificate files there:
+     * you also have to create symbolic links named hash-value.N. And you should
+     * always make sure this directory contains the appropriate symbolic links.
+     * Use the Makefile which comes with mod_ssl to accomplish this task.
+     * @param ctx Server or Client context to use.
+     * @param file File of concatenated PEM-encoded CA CRLs for Client Auth.
+     * @param path Directory of PEM-encoded CA Certificates for Client Auth.
+     */
+    public static native boolean setCARevocation(long ctx, String file,
+                                                 String path)
+        throws Exception;
+
+    /**
+     * Set File of PEM-encoded Server CA Certificates
+     * <br>
+     * This directive sets the optional all-in-one file where you can assemble the
+     * certificates of Certification Authorities (CA) which form the certificate
+     * chain of the server certificate. This starts with the issuing CA certificate
+     * of of the server certificate and can range up to the root CA certificate.
+     * Such a file is simply the concatenation of the various PEM-encoded CA
+     * Certificate files, usually in certificate chain order.
+     * <br>
+     * But be careful: Providing the certificate chain works only if you are using
+     * a single (either RSA or DSA) based server certificate. If you are using a
+     * coupled RSA+DSA certificate pair, this will work only if actually both
+     * certificates use the same certificate chain. Else the browsers will be
+     * confused in this situation.
+     * @param ctx Server or Client context to use.
+     * @param file File of PEM-encoded Server CA Certificates.
+     * @param skipfirst Skip first certificate if chain file is inside
+     *                  certificate file.
+     */
+    public static native boolean setCertificateChainFile(long ctx, String file,
+                                                         boolean skipfirst);
+    /**
+     * Set BIO of PEM-encoded Server CA Certificates
+     * <p>
+     * This directive sets the optional all-in-one file where you can assemble the
+     * certificates of Certification Authorities (CA) which form the certificate
+     * chain of the server certificate. This starts with the issuing CA certificate
+     * of of the server certificate and can range up to the root CA certificate.
+     * Such a file is simply the concatenation of the various PEM-encoded CA
+     * Certificate files, usually in certificate chain order.
+     * <p>
+     * But be careful: Providing the certificate chain works only if you are using
+     * a single (either RSA or DSA) based server certificate. If you are using a
+     * coupled RSA+DSA certificate pair, this will work only if actually both
+     * certificates use the same certificate chain. Otherwsie the browsers will be
+     * confused in this situation.
+     * @param ctx Server or Client context to use.
+     * @param bio BIO of PEM-encoded Server CA Certificates.
+     * @param skipfirst Skip first certificate if chain file is inside
+     *                  certificate file.
+     */
+    public static native boolean setCertificateChainBio(long ctx, long bio, boolean skipfirst);
+
+    /**
+     * Set Certificate
+     * <br>
+     * Point setCertificateFile at a PEM encoded certificate.  If
+     * the certificate is encrypted, then you will be prompted for a
+     * pass phrase.  Note that a kill -HUP will prompt again. A test
+     * certificate can be generated with `make certificate' under
+     * built time. Keep in mind that if you've both a RSA and a DSA
+     * certificate you can configure both in parallel (to also allow
+     * the use of DSA ciphers, etc.)
+     * <br>
+     * If the key is not combined with the certificate, use key param
+     * to point at the key file.  Keep in mind that if
+     * you've both a RSA and a DSA private key you can configure
+     * both in parallel (to also allow the use of DSA ciphers, etc.)
+     * @param ctx Server or Client context to use.
+     * @param cert Certificate file.
+     * @param key Private Key file to use if not in cert.
+     * @param password Certificate password. If null and certificate
+     *                 is encrypted, password prompt will be displayed.
+     * @param idx Certificate index SSL_AIDX_RSA or SSL_AIDX_DSA.
+     */
+    public static native boolean setCertificate(long ctx, String cert,
+                                                String key, String password,
+                                                int idx)
+        throws Exception;
+
+    /**
+     * Set Certificate
+     * <br>
+     * Point setCertificate at a PEM encoded certificate stored in a BIO. If
+     * the certificate is encrypted, then you will be prompted for a
+     * pass phrase.  Note that a kill -HUP will prompt again. A test
+     * certificate can be generated with `make certificate' under
+     * built time. Keep in mind that if you've both a RSA and a DSA
+     * certificate you can configure both in parallel (to also allow
+     * the use of DSA ciphers, etc.)
+     * <br>
+     * If the key is not combined with the certificate, use key param
+     * to point at the key file.  Keep in mind that if
+     * you've both a RSA and a DSA private key you can configure
+     * both in parallel (to also allow the use of DSA ciphers, etc.)
+     * @param ctx Server or Client context to use.
+     * @param certBio Certificate BIO.
+     * @param keyBio Private Key BIO to use if not in cert.
+     * @param password Certificate password. If null and certificate
+     *                 is encrypted, password prompt will be displayed.
+     * @param idx Certificate index SSL_AIDX_RSA or SSL_AIDX_DSA.
+     */
+    public static native boolean setCertificateBio(
+            long ctx, long certBio, long keyBio, String password, int idx) throws Exception;
+
+    /**
+     * Set the size of the internal session cache.
+     * http://www.openssl.org/docs/ssl/SSL_CTX_sess_set_cache_size.html
+     */
+    public static native long setSessionCacheSize(long ctx, long size);
+
+    /**
+     * Get the size of the internal session cache.
+     * http://www.openssl.org/docs/ssl/SSL_CTX_sess_get_cache_size.html
+     */
+    public static native long getSessionCacheSize(long ctx);
+
+    /**
+     * Set the timeout for the internal session cache in seconds.
+     * http://www.openssl.org/docs/ssl/SSL_CTX_set_timeout.html
+     */
+    public static native long setSessionCacheTimeout(long ctx, long timeoutSeconds);
+
+    /**
+     * Get the timeout for the internal session cache in seconds.
+     * http://www.openssl.org/docs/ssl/SSL_CTX_set_timeout.html
+     */
+    public static native long getSessionCacheTimeout(long ctx);
+
+    /**
+     * Set the mode of the internal session cache and return the previous used mode.
+     */
+    public static native long setSessionCacheMode(long ctx, long mode);
+
+    /**
+     * Get the mode of the current used internal session cache.
+     */
+    public static native long getSessionCacheMode(long ctx);
+
+    /**
+     * Session resumption statistics methods.
+     * http://www.openssl.org/docs/ssl/SSL_CTX_sess_number.html
+     */
+    public static native long sessionAccept(long ctx);
+    public static native long sessionAcceptGood(long ctx);
+    public static native long sessionAcceptRenegotiate(long ctx);
+    public static native long sessionCacheFull(long ctx);
+    public static native long sessionCbHits(long ctx);
+    public static native long sessionConnect(long ctx);
+    public static native long sessionConnectGood(long ctx);
+    public static native long sessionConnectRenegotiate(long ctx);
+    public static native long sessionHits(long ctx);
+    public static native long sessionMisses(long ctx);
+    public static native long sessionNumber(long ctx);
+    public static native long sessionTimeouts(long ctx);
+
+    /**
+     * Set TLS session keys.
+     */
+    public static void setSessionTicketKeys(long ctx, SessionTicketKey[] keys) {
+        if (keys == null || keys.length == 0) {
+            throw new IllegalArgumentException("Length of the keys should be longer than 0.");
+        }
+        byte[] binaryKeys = new byte[keys.length * SessionTicketKey.TICKET_KEY_SIZE];
+        for (int i = 0; i < keys.length; i++) {
+            SessionTicketKey key = keys[i];
+            int dstCurPos = SessionTicketKey.TICKET_KEY_SIZE * i;
+            System.arraycopy(key.getName(), 0, binaryKeys, dstCurPos, SessionTicketKey.NAME_SIZE);
+            dstCurPos += SessionTicketKey.NAME_SIZE;
+            System.arraycopy(key.getHmacKey(), 0, binaryKeys, dstCurPos, SessionTicketKey.HMAC_KEY_SIZE);
+            dstCurPos += SessionTicketKey.HMAC_KEY_SIZE;
+            System.arraycopy(key.getAesKey(), 0, binaryKeys, dstCurPos, SessionTicketKey.AES_KEY_SIZE);
+        }
+        setSessionTicketKeys0(ctx, binaryKeys);
+    }
+
+    /**
+     * Set TLS session keys. This allows us to share keys across TFEs.
+     */
+    @Deprecated
+    public static void setSessionTicketKeys(long ctx, byte[] keys) {
+        if (keys.length % SessionTicketKey.TICKET_KEY_SIZE != 0) {
+            throw new IllegalArgumentException("Session ticket keys provided were wrong size. keys.length % " + SessionTicketKey.TICKET_KEY_SIZE + " must be 0");
+        }
+        setSessionTicketKeys0(ctx, keys);
+    }
+    /**
+     * Set TLS session keys. This allows us to share keys across TFEs.
+     */
+    private static native void setSessionTicketKeys0(long ctx, byte[] keys);
+
+    /**
+     * Set File and Directory of concatenated PEM-encoded CA Certificates
+     * for Client Auth
+     * <br>
+     * This directive sets the all-in-one file where you can assemble the
+     * Certificates of Certification Authorities (CA) whose clients you deal with.
+     * These are used for Client Authentication. Such a file is simply the
+     * concatenation of the various PEM-encoded Certificate files, in order of
+     * preference. This can be used alternatively and/or additionally to
+     * path.
+     * <br>
+     * The files in this directory have to be PEM-encoded and are accessed through
+     * hash filenames. So usually you can't just place the Certificate files there:
+     * you also have to create symbolic links named hash-value.N. And you should
+     * always make sure this directory contains the appropriate symbolic links.
+     * Use the Makefile which comes with mod_ssl to accomplish this task.
+     * @param ctx Server or Client context to use.
+     * @param file File of concatenated PEM-encoded CA Certificates for
+     *             Client Auth.
+     * @param path Directory of PEM-encoded CA Certificates for Client Auth.
+     */
+    public static native boolean setCACertificate(long ctx, String file,
+                                                  String path)
+        throws Exception;
+
+    /**
+     * Set file for randomness
+     * @param ctx Server or Client context to use.
+     * @param file random file.
+     */
+    public static native void setRandom(long ctx, String file);
+
+    /**
+     * Set SSL connection shutdown type
+     * <br>
+     * The following levels are available for level:
+     * <PRE>
+     * SSL_SHUTDOWN_TYPE_STANDARD
+     * SSL_SHUTDOWN_TYPE_UNCLEAN
+     * SSL_SHUTDOWN_TYPE_ACCURATE
+     * </PRE>
+     * @param ctx Server or Client context to use.
+     * @param type Shutdown type to use.
+     */
+    public static native void setShutdownType(long ctx, int type);
+
+    /**
+     * Set Type of Client Certificate verification and Maximum depth of CA Certificates
+     * in Client Certificate verification.
+     * <br>
+     * This directive sets the Certificate verification level for the Client
+     * Authentication. Notice that this directive can be used both in per-server
+     * and per-directory context. In per-server context it applies to the client
+     * authentication process used in the standard SSL handshake when a connection
+     * is established. In per-directory context it forces a SSL renegotiation with
+     * the reconfigured client verification level after the HTTP request was read
+     * but before the HTTP response is sent.
+     * <br>
+     * The following levels are available for level:
+     * <PRE>
+     * SSL_CVERIFY_NONE           - No client Certificate is required at all
+     * SSL_CVERIFY_OPTIONAL       - The client may present a valid Certificate
+     * SSL_CVERIFY_REQUIRE        - The client has to present a valid Certificate
+     * SSL_CVERIFY_OPTIONAL_NO_CA - The client may present a valid Certificate
+     *                              but it need not to be (successfully) verifiable
+     * </PRE>
+     * <br>
+     * The depth actually is the maximum number of intermediate certificate issuers,
+     * i.e. the number of CA certificates which are max allowed to be followed while
+     * verifying the client certificate. A depth of 0 means that self-signed client
+     * certificates are accepted only, the default depth of 1 means the client
+     * certificate can be self-signed or has to be signed by a CA which is directly
+     * known to the server (i.e. the CA's certificate is under
+     * <code>setCACertificatePath</code>), etc.
+     * @param ctx Server or Client context to use.
+     * @param level Type of Client Certificate verification.
+     * @param depth Maximum depth of CA Certificates in Client Certificate
+     *              verification.
+     */
+    public static native void setVerify(long ctx, int level, int depth);
+
+    /**
+     * Allow to hook {@link CertificateVerifier} into the handshake processing.
+     * This will call {@code SSL_CTX_set_cert_verify_callback} and so replace the default verification
+     * callback used by openssl
+     * @param ctx Server or Client context to use.
+     * @param verifier the verifier to call during handshake.
+     */
+    public static native void setCertVerifyCallback(long ctx, CertificateVerifier verifier);
+
+    /**
+     * Set next protocol for next protocol negotiation extension
+     * @param ctx Server context to use.
+     * @param nextProtos comma delimited list of protocols in priority order
+     *
+     * @deprecated use {@link #setNpnProtos(long, String[], int)}
+     */
+    @Deprecated
+    public static void setNextProtos(long ctx, String nextProtos) {
+        setNpnProtos(ctx, nextProtos.split(","), SSL.SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL);
+    }
+
+    /**
+     * Set next protocol for next protocol negotiation extension
+     * @param ctx Server context to use.
+     * @param nextProtos protocols in priority order
+     * @param selectorFailureBehavior see {@link SSL#SSL_SELECTOR_FAILURE_NO_ADVERTISE}
+     *                                and {@link SSL#SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL}
+     */
+    public static native void setNpnProtos(long ctx, String[] nextProtos, int selectorFailureBehavior);
+
+    /**
+     * Set application layer protocol for application layer protocol negotiation extension
+     * @param ctx Server context to use.
+     * @param alpnProtos protocols in priority order
+     * @param selectorFailureBehavior see {@link SSL#SSL_SELECTOR_FAILURE_NO_ADVERTISE}
+     *                                and {@link SSL#SSL_SELECTOR_FAILURE_CHOOSE_MY_LAST_PROTOCOL}
+     */
+    public static native void setAlpnProtos(long ctx, String[] alpnProtos, int selectorFailureBehavior);
+
+    /**
+     * Set DH parameters
+     * @param ctx Server context to use.
+     * @param cert DH param file (can be generated from e.g. {@code openssl dhparam -rand - 2048 > dhparam.pem} -
+     *             see the <a href="https://www.openssl.org/docs/apps/dhparam.html">OpenSSL documentation</a>).
+     */
+    public static native void setTmpDH(long ctx, String cert)
+            throws Exception;
+    
+    /**
+     * Set ECDH elliptic curve by name
+     * @param ctx Server context to use.
+     * @param curveName the name of the elliptic curve to use
+     *             (available names can be obtained from {@code openssl ecparam -list_curves}).
+     */
+    public static native void setTmpECDHByCurveName(long ctx, String curveName)
+            throws Exception;
+
+    /**
+     * Set the context within which session be reused (server side only)
+     * http://www.openssl.org/docs/ssl/SSL_CTX_set_session_id_context.html
+     *
+     * @param ctx Server context to use.
+     * @param sidCtx can be any kind of binary data, it is therefore possible to use e.g. the name
+     *               of the application and/or the hostname and/or service name
+     * @return {@code true} if success, {@code false} otherwise.
+     */
+    public static native boolean setSessionIdContext(long ctx, byte[] sidCtx);
+}
diff --git a/java/src/org/apache/tomcat/jni/SSLExt.java b/java/src/org/apache/tomcat/jni/SSLExt.java
new file mode 100644
index 0000000..e794de0
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/SSLExt.java
@@ -0,0 +1,159 @@
+/*
+ *  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.tomcat.jni;
+
+/**
+ * Support TLS extensions and extra methods.
+ *
+ * The methods are separated to make it easier for java code to
+ * support existing native library - it can check if this class can
+ * be loaded in order to use the exensions.
+ *
+ * @author Costin Manolache
+ */
+public final class SSLExt {
+
+
+    /**
+     * Set advertised NPN protocol.
+     * This is only available for recent or patched openssl.
+     *
+     * Example: "\x06spdy/2"
+     *
+     * Works with TLS1, doesn't with SSL2/SSL3
+     *
+     * Servers sends list in ServerHelo, client selects it and
+     * sends it back after ChangeChipher
+     *
+     * Not supported in 1.0.0, seems to be in 1.0.1 and after
+     */
+    public static native int setNPN(long tcctx, byte[] proto, int len);
+
+    /**
+     * Get other side's advertised protocols.
+     * Only works after handshake.
+     */
+    public static native int getNPN(long tcsock, byte[] proto);
+
+    /**
+     * Enabling dump/debugging on the socket. Both raw and decrypted
+     * packets will be logged.
+     */
+    public static native int debug(long tcsock);
+
+    /**
+     * Server: Extract the session data associated with the socket.
+     * Must be saved, keyed by session ID.
+     */
+    public static native byte[] getSessionData(long tcsock);
+
+    /**
+     * Server: Set the session data for a socket.
+     */
+    public static native int setSessionData(long tcsock, byte[] data, int len);
+
+
+    /**
+     * Client: get the ticket received from server, if tickets are supported.
+     */
+    public static native int getTicket(long tcsock, byte[] resBuf);
+
+    /**
+     * Client: set the previously received ticket.
+     */
+    public static native int setTicket(long tcsock, byte[] data, int len);
+
+    /**
+     * Set the key used by server to generate tickets.
+     * Key must be 48 bytes.
+     */
+    public static native int setTicketKeys(long ctx, byte[] data, int len);
+
+    /**
+     * For client side calls. Data should be a \0 terminated string
+     */
+    public static native int setSNI(long tcsock, byte[] data, int len);
+
+    /**
+     * Return the last openssl error
+     */
+    public static native String sslErrReasonErrorString();
+
+    public static native long sslCtxSetMode(long ctx, long mode);
+
+    /* Allow SSL_write(..., n) to return r with 0 < r < n (i.e. report success
+     * when just a single record has been written): */
+    public static final int SSL_MODE_ENABLE_PARTIAL_WRITE = 0x1;
+
+    /* Make it possible to retry SSL_write() with changed buffer location
+     * (buffer contents must stay the same!); this is not the default to avoid
+     * the misconception that non-blocking SSL_write() behaves like
+     * non-blocking write(): */
+    public static final int SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER = 0x2;
+
+    /* Don't attempt to automatically build certificate chain */
+    static final int SSL_MODE_NO_AUTO_CHAIN = 0x8;
+
+    /* Save RAM by releasing read and write buffers when they're empty. (SSL3 and
+     * TLS only.)  "Released" buffers are put onto a free-list in the context
+     * or just freed (depending on the context's setting for freelist_max_len). */
+    public static final int SSL_MODE_RELEASE_BUFFERS = 0x10;
+
+    // 1.1
+    //static final int SSL_MODE_HANDSHAKE_CUTTHROUGH = ..;
+
+    /**
+     * SSL_set_mode
+     */
+    public static native long sslSetMode(long tcsock, long mode);
+
+    public static int setNPN(long sslContext, byte[] spdyNPN) {
+        try {
+            return SSLExt.setNPN(sslContext, spdyNPN, spdyNPN.length);
+        } catch (Throwable t) {
+            t.printStackTrace();
+            return -1;
+        }
+    }
+
+    /**
+     * Higher level method, checking if the specified protocol has been
+     * negotiated.
+     */
+    public static boolean checkNPN(long tcsocket, byte[] expected) {
+        byte[] npn = new byte[expected.length + 1];
+        int npnLen = 0;
+        try {
+            npnLen = SSLExt.getNPN(tcsocket, npn);
+            if (npnLen != expected.length) {
+                return false;
+            }
+        } catch (Throwable t) {
+            // ignore
+            return false;
+        }
+        for (int i = 0; i < expected.length; i++) {
+            if (expected[i] != npn[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+
+}
diff --git a/java/src/org/apache/tomcat/jni/SSLSocket.java b/java/src/org/apache/tomcat/jni/SSLSocket.java
new file mode 100644
index 0000000..82a4b98
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/SSLSocket.java
@@ -0,0 +1,110 @@
+/*
+ *  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.tomcat.jni;
+
+/** SSL Socket
+ *
+ * @author Mladen Turk
+ */
+public class SSLSocket {
+
+    /**
+     * Attach APR socket on a SSL connection.
+     * @param ctx SSLContext to use.
+     * @param sock APR Socket that already did physical connect or accept.
+     * @return APR_STATUS code.
+     */
+    public static native int attach(long ctx, long sock)
+        throws Exception;
+
+    /**
+     * Do a SSL handshake.
+     * @param thesocket The socket to use
+     */
+    public static native int handshake(long thesocket);
+
+    /**
+     * Do a SSL renegotiation.
+     * SSL supports per-directory re-configuration of SSL parameters.
+     * This is implemented by performing an SSL renegotiation of the
+     * re-configured parameters after the request is read, but before the
+     * response is sent. In more detail: the renegotiation happens after the
+     * request line and MIME headers were read, but _before_ the attached
+     * request body is read. The reason simply is that in the HTTP protocol
+     * usually there is no acknowledgment step between the headers and the
+     * body (there is the 100-continue feature and the chunking facility
+     * only), so Apache has no API hook for this step.
+     *
+     * @param thesocket The socket to use
+     */
+    public static native int renegotiate(long thesocket);
+
+    /**
+     * Set Type of Client Certificate verification and Maximum depth of CA
+     * Certificates in Client Certificate verification.
+     * <br>
+     * This is used to change the verification level for a connection prior to
+     * starting a re-negotiation.
+     * <br>
+     * The following levels are available for level:
+     * <PRE>
+     * SSL_CVERIFY_NONE           - No client Certificate is required at all
+     * SSL_CVERIFY_OPTIONAL       - The client may present a valid Certificate
+     * SSL_CVERIFY_REQUIRE        - The client has to present a valid
+     *                              Certificate
+     * SSL_CVERIFY_OPTIONAL_NO_CA - The client may present a valid Certificate
+     *                              but it need not to be (successfully)
+     *                              verifiable
+     * </PRE>
+     * <br>
+     * @param sock  The socket to change.
+     * @param level Type of Client Certificate verification.
+     */
+    public static native void setVerify(long sock, int level, int depth);
+
+    /**
+     * Return SSL Info parameter as byte array.
+     *
+     * @param sock The socket to read the data from.
+     * @param id Parameter id.
+     * @return Byte array containing info id value.
+     */
+    public static native byte[] getInfoB(long sock, int id)
+        throws Exception;
+
+    /**
+     * Return SSL Info parameter as String.
+     *
+     * @param sock The socket to read the data from.
+     * @param id Parameter id.
+     * @return String containing info id value.
+     */
+    public static native String getInfoS(long sock, int id)
+        throws Exception;
+
+    /**
+     * Return SSL Info parameter as integer.
+     *
+     * @param sock The socket to read the data from.
+     * @param id Parameter id.
+     * @return Integer containing info id value or -1 on error.
+     */
+    public static native int getInfoI(long sock, int id)
+        throws Exception;
+
+}
diff --git a/java/src/org/apache/tomcat/jni/SessionTicketKey.java b/java/src/org/apache/tomcat/jni/SessionTicketKey.java
new file mode 100644
index 0000000..12e75a0
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/SessionTicketKey.java
@@ -0,0 +1,89 @@
+/*
+ *  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.tomcat.jni;
+
+/**
+ * Session Ticket Key
+ */
+public final class SessionTicketKey {
+    /**
+     * Size of session ticket key name
+     */
+    public static final int NAME_SIZE = 16;
+    /**
+     * Size of session ticket key HMAC key
+     */
+    public static final int HMAC_KEY_SIZE = 16;
+    /**
+     * Size of session ticket key AES key
+     */
+    public static final int AES_KEY_SIZE = 16;
+    /**
+     * Size of session ticker key
+     */
+    public static final int TICKET_KEY_SIZE = NAME_SIZE + HMAC_KEY_SIZE + AES_KEY_SIZE;
+
+    private final byte[] name;
+    private final byte[] hmacKey;
+    private final byte[] aesKey;
+
+    /**
+     * Construct SesionTicketKey.
+     * @param name the name of the session ticket key
+     * @param hmacKey the HMAC key of the session ticket key
+     * @param aesKey the AES key of the session ticket key
+     */
+    public SessionTicketKey(byte[] name, byte[] hmacKey, byte[] aesKey) {
+        if (name == null || name.length != NAME_SIZE) {
+            throw new IllegalArgumentException("Length of name should be 16");
+        }
+        if (hmacKey == null || hmacKey.length != HMAC_KEY_SIZE) {
+            throw new IllegalArgumentException("Length of hmacKey should be 16");
+        }
+        if (aesKey == null || aesKey.length != AES_KEY_SIZE) {
+            throw new IllegalArgumentException("Length of aesKey should be 16");
+        }
+        this.name = name;
+        this.hmacKey = hmacKey;
+        this.aesKey = aesKey;
+    }
+
+    /**
+     * Get name.
+     * @return the name of the session ticket key
+     */
+    public byte[] getName() {
+        return name;
+    }
+
+    /**
+     * Get HMAC key.
+     * @return the HMAC key of the session ticket key
+     */
+    public byte[] getHmacKey() {
+        return hmacKey;
+    }
+
+    /**
+     * Get AES Key.
+     * @return the AES key of the session ticket key
+     */
+    public byte[] getAesKey() {
+        return aesKey;
+    }
+}
diff --git a/java/src/org/apache/tomcat/jni/Shm.java b/java/src/org/apache/tomcat/jni/Shm.java
new file mode 100644
index 0000000..6ccf9cf
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Shm.java
@@ -0,0 +1,122 @@
+/*
+ *  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.tomcat.jni;
+
+import java.nio.ByteBuffer;
+
+/** Shm
+ *
+ * @author Mladen Turk
+ */
+public class Shm {
+
+    /**
+     * Create and make accessible a shared memory segment.
+     * <br>
+     * A note about Anonymous vs. Named shared memory segments:<br>
+     *         Not all platforms support anonymous shared memory segments, but in
+     *         some cases it is preferred over other types of shared memory
+     *         implementations. Passing a NULL 'file' parameter to this function
+     *         will cause the subsystem to use anonymous shared memory segments.
+     *         If such a system is not available, APR_ENOTIMPL is returned.
+     * <br>
+     * A note about allocation sizes:<br>
+     *         On some platforms it is necessary to store some metainformation
+     *         about the segment within the actual segment. In order to supply
+     *         the caller with the requested size it may be necessary for the
+     *         implementation to request a slightly greater segment length
+     *         from the subsystem. In all cases, the apr_shm_baseaddr_get()
+     *         function will return the first usable byte of memory.
+     * @param reqsize The desired size of the segment.
+     * @param filename The file to use for shared memory on platforms that
+     *        require it.
+     * @param pool the pool from which to allocate the shared memory
+     *        structure.
+     * @return The created shared memory structure.
+     *
+     */
+    public static native long create(long reqsize, String filename, long pool)
+        throws Error;
+
+    /**
+     * Remove shared memory segment associated with a filename.
+     * <br>
+     * This function is only supported on platforms which support
+     * name-based shared memory segments, and will return APR_ENOTIMPL on
+     * platforms without such support.
+     * @param filename The filename associated with shared-memory segment which
+     *        needs to be removed
+     * @param pool The pool used for file operations
+     */
+    public static native int remove(String filename, long pool);
+
+    /**
+     * Destroy a shared memory segment and associated memory.
+     * @param m The shared memory segment structure to destroy.
+     */
+    public static native int destroy(long m);
+
+    /**
+     * Attach to a shared memory segment that was created
+     * by another process.
+     * @param filename The file used to create the original segment.
+     *        (This MUST match the original filename.)
+     * @param pool the pool from which to allocate the shared memory
+     *        structure for this process.
+     * @return The created shared memory structure.
+     */
+    public static native long attach(String filename, long pool)
+        throws Error;
+
+    /**
+     * Detach from a shared memory segment without destroying it.
+     * @param m The shared memory structure representing the segment
+     *        to detach from.
+     */
+    public static native int detach(long m);
+
+    /**
+     * Retrieve the base address of the shared memory segment.
+     * NOTE: This address is only usable within the callers address
+     * space, since this API does not guarantee that other attaching
+     * processes will maintain the same address mapping.
+     * @param m The shared memory segment from which to retrieve
+     *        the base address.
+     * @return address, aligned by APR_ALIGN_DEFAULT.
+     */
+    public static native long baseaddr(long m);
+
+    /**
+     * Retrieve the length of a shared memory segment in bytes.
+     * @param m The shared memory segment from which to retrieve
+     *        the segment length.
+     */
+    public static native long size(long m);
+
+    /**
+     * Retrieve new ByteBuffer base address of the shared memory segment.
+     * NOTE: This address is only usable within the callers address
+     * space, since this API does not guarantee that other attaching
+     * processes will maintain the same address mapping.
+     * @param m The shared memory segment from which to retrieve
+     *        the base address.
+     * @return address, aligned by APR_ALIGN_DEFAULT.
+     */
+    public static native ByteBuffer buffer(long m);
+
+}
diff --git a/java/src/org/apache/tomcat/jni/Sockaddr.java b/java/src/org/apache/tomcat/jni/Sockaddr.java
new file mode 100644
index 0000000..f5262a7
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Sockaddr.java
@@ -0,0 +1,40 @@
+/*
+ *  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.tomcat.jni;
+
+/** Sockaddr
+ *
+ * @author Mladen Turk
+ */
+public class Sockaddr {
+
+   /** The pool to use... */
+    public long pool;
+    /** The hostname */
+    public String hostname;
+    /** Either a string of the port number or the service name for the port */
+    public String servname;
+    /** The numeric port */
+    public int port;
+    /** The family */
+    public int family;
+    /** If multiple addresses were found by apr_sockaddr_info_get(), this
+     *  points to a representation of the next address. */
+    public long next;
+
+}
diff --git a/java/src/org/apache/tomcat/jni/Socket.java b/java/src/org/apache/tomcat/jni/Socket.java
new file mode 100644
index 0000000..a135bff
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Socket.java
@@ -0,0 +1,584 @@
+/*
+ *  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.tomcat.jni;
+
+/* Import needed classes */
+import java.nio.ByteBuffer;
+
+/** Socket
+ *
+ * @author Mladen Turk
+ */
+public class Socket {
+
+    /* Standard socket defines */
+    public static final int SOCK_STREAM = 0;
+    public static final int SOCK_DGRAM  = 1;
+    /*
+     * apr_sockopt Socket option definitions
+     */
+    public static final int APR_SO_LINGER       = 1;    /** Linger */
+    public static final int APR_SO_KEEPALIVE    = 2;    /** Keepalive */
+    public static final int APR_SO_DEBUG        = 4;    /** Debug */
+    public static final int APR_SO_NONBLOCK     = 8;    /** Non-blocking IO */
+    public static final int APR_SO_REUSEADDR    = 16;   /** Reuse addresses */
+    public static final int APR_SO_SNDBUF       = 64;   /** Send buffer */
+    public static final int APR_SO_RCVBUF       = 128;  /** Receive buffer */
+    public static final int APR_SO_DISCONNECTED = 256;  /** Disconnected */
+    /** For SCTP sockets, this is mapped to STCP_NODELAY internally. */
+    public static final int APR_TCP_NODELAY     = 512;
+    public static final int APR_TCP_NOPUSH      = 1024; /** No push */
+    /** This flag is ONLY set internally when we set APR_TCP_NOPUSH with
+     * APR_TCP_NODELAY set to tell us that APR_TCP_NODELAY should be turned on
+     * again when NOPUSH is turned off
+     */
+    public static final int APR_RESET_NODELAY   = 2048;
+    /** Set on non-blocking sockets (timeout != 0) on which the
+     * previous read() did not fill a buffer completely.  the next
+     * apr_socket_recv()  will first call select()/poll() rather than
+     * going straight into read().  (Can also be set by an application to
+     * force a select()/poll() call before the next read, in cases where
+     * the app expects that an immediate read would fail.)
+     */
+    public static final int APR_INCOMPLETE_READ = 4096;
+    /** like APR_INCOMPLETE_READ, but for write
+     */
+    public static final int APR_INCOMPLETE_WRITE = 8192;
+    /** Don't accept IPv4 connections on an IPv6 listening socket.
+     */
+    public static final int APR_IPV6_V6ONLY      = 16384;
+    /** Delay accepting of new connections until data is available.
+     */
+    public static final int APR_TCP_DEFER_ACCEPT = 32768;
+
+    /** Define what type of socket shutdown should occur.
+     * apr_shutdown_how_e enum
+     */
+    public static final int APR_SHUTDOWN_READ      = 0; /** no longer allow read request */
+    public static final int APR_SHUTDOWN_WRITE     = 1; /** no longer allow write requests */
+    public static final int APR_SHUTDOWN_READWRITE = 2; /** no longer allow read or write requests */
+
+    public static final int APR_IPV4_ADDR_OK = 0x01;
+    public static final int APR_IPV6_ADDR_OK = 0x02;
+
+    /* TODO: Missing:
+     * APR_INET
+     * APR_UNSPEC
+     * APR_INET6
+     */
+    public static final int APR_UNSPEC = 0;
+    public static final int APR_INET   = 1;
+    public static final int APR_INET6  = 2;
+
+    public static final int APR_PROTO_TCP  =   6; /** TCP  */
+    public static final int APR_PROTO_UDP  =  17; /** UDP  */
+    public static final int APR_PROTO_SCTP = 132; /** SCTP */
+
+    /**
+     * Enum to tell us if we're interested in remote or local socket
+     * apr_interface_e
+     */
+    public static final int APR_LOCAL  = 0;
+    public static final int APR_REMOTE = 1;
+
+    /* Socket.get types */
+    public static final int SOCKET_GET_POOL = 0;
+    public static final int SOCKET_GET_IMPL = 1;
+    public static final int SOCKET_GET_APRS = 2;
+    public static final int SOCKET_GET_TYPE = 3;
+
+    /**
+     * Create a socket.
+     * @param family The address family of the socket (e.g., APR_INET).
+     * @param type The type of the socket (e.g., SOCK_STREAM).
+     * @param protocol The protocol of the socket (e.g., APR_PROTO_TCP).
+     * @param cont The parent pool to use
+     * @return The new socket that has been set up.
+     */
+    public static native long create(int family, int type,
+                                     int protocol, long cont)
+        throws Exception;
+
+
+    /**
+     * Shutdown either reading, writing, or both sides of a socket.
+     * <br>
+     * This does not actually close the socket descriptor, it just
+     *      controls which calls are still valid on the socket.
+     * @param thesocket The socket to close
+     * @param how How to shutdown the socket.  One of:
+     * <PRE>
+     * APR_SHUTDOWN_READ         no longer allow read requests
+     * APR_SHUTDOWN_WRITE        no longer allow write requests
+     * APR_SHUTDOWN_READWRITE    no longer allow read or write requests
+     * </PRE>
+     */
+    public static native int shutdown(long thesocket, int how);
+
+    /**
+     * Close a socket.
+     * @param thesocket The socket to close
+     */
+    public static native int close(long thesocket);
+
+    /**
+     * Destroy a pool associated with socket
+     * @param thesocket The destroy
+     */
+    public static native void destroy(long thesocket);
+
+    /**
+     * Bind the socket to its associated port
+     * @param sock The socket to bind
+     * @param sa The socket address to bind to
+     * This may be where we will find out if there is any other process
+     *      using the selected port.
+     */
+    public static native int bind(long sock, long sa);
+
+    /**
+     * Listen to a bound socket for connections.
+     * @param sock The socket to listen on
+     * @param backlog The number of outstanding connections allowed in the sockets
+     *                listen queue.  If this value is less than zero, the listen
+     *                queue size is set to zero.
+     */
+    public static native int listen(long sock, int backlog);
+
+    /**
+     * Accept a new connection request
+     * @param sock The socket we are listening on.
+     * @param pool The pool for the new socket.
+     * @return  A copy of the socket that is connected to the socket that
+     *          made the connection request.  This is the socket which should
+     *          be used for all future communication.
+     */
+    public static native long acceptx(long sock, long pool)
+        throws Exception;
+
+    /**
+     * Accept a new connection request
+     * @param sock The socket we are listening on.
+     * @return  A copy of the socket that is connected to the socket that
+     *          made the connection request.  This is the socket which should
+     *          be used for all future communication.
+     */
+    public static native long accept(long sock)
+        throws Exception;
+
+    /**
+     * Set an OS level accept filter.
+     * @param sock The socket to put the accept filter on.
+     * @param name The accept filter
+     * @param args Any extra args to the accept filter.  Passing NULL here removes
+     *             the accept filter.
+     */
+    public static native int acceptfilter(long sock, String name, String args);
+
+    /**
+     * Query the specified socket if at the OOB/Urgent data mark
+     * @param sock The socket to query
+     * @return True if socket is at the OOB/urgent mark,
+     *         otherwise return false.
+     */
+    public static native boolean atmark(long sock);
+
+    /**
+     * Issue a connection request to a socket either on the same machine
+     * or a different one.
+     * @param sock The socket we wish to use for our side of the connection
+     * @param sa The address of the machine we wish to connect to.
+     */
+    public static native int connect(long sock, long sa);
+
+    /**
+     * Send data over a network.
+     * <PRE>
+     * This functions acts like a blocking write by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     *
+     * It is possible for both bytes to be sent and an error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to send the data over.
+     * @param buf The buffer which contains the data to be sent.
+     * @param offset Offset in the byte buffer.
+     * @param len The number of bytes to write; (-1) for full array.
+     * @return The number of bytes send.
+     *
+     */
+    public static native int send(long sock, byte[] buf, int offset, int len);
+
+    /**
+     * Send data over a network.
+     * <PRE>
+     * This functions acts like a blocking write by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     *
+     * It is possible for both bytes to be sent and an error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to send the data over.
+     * @param buf The Byte buffer which contains the data to be sent.
+     * @param offset The offset within the buffer array of the first buffer from
+     *               which bytes are to be retrieved; must be non-negative
+     *               and no larger than buf.length
+     * @param len The maximum number of buffers to be accessed; must be non-negative
+     *            and no larger than buf.length - offset
+     * @return The number of bytes send.
+     *
+     */
+    public static native int sendb(long sock, ByteBuffer buf,
+                                   int offset, int len);
+
+    /**
+     * Send data over a network without retry
+     * <PRE>
+     * This functions acts like a blocking write by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     *
+     * It is possible for both bytes to be sent and an error to be returned.
+     *
+     * </PRE>
+     * @param sock The socket to send the data over.
+     * @param buf The Byte buffer which contains the data to be sent.
+     * @param offset The offset within the buffer array of the first buffer from
+     *               which bytes are to be retrieved; must be non-negative
+     *               and no larger than buf.length
+     * @param len The maximum number of buffers to be accessed; must be non-negative
+     *            and no larger than buf.length - offset
+     * @return The number of bytes send.
+     *
+     */
+    public static native int sendib(long sock, ByteBuffer buf,
+                                    int offset, int len);
+
+    /**
+     * Send data over a network using internally set ByteBuffer
+     */
+    public static native int sendbb(long sock,
+                                   int offset, int len);
+
+    /**
+     * Send data over a network using internally set ByteBuffer
+     * without internal retry.
+     */
+    public static native int sendibb(long sock,
+                                     int offset, int len);
+
+    /**
+     * Send multiple packets of data over a network.
+     * <PRE>
+     * This functions acts like a blocking write by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     * The number of bytes actually sent is stored in argument 3.
+     *
+     * It is possible for both bytes to be sent and an error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to send the data over.
+     * @param vec The array from which to get the data to send.
+     *
+     */
+    public static native int sendv(long sock, byte[][] vec);
+
+    /**
+     * @param sock The socket to send from
+     * @param where The apr_sockaddr_t describing where to send the data
+     * @param flags The flags to use
+     * @param buf  The data to send
+     * @param offset Offset in the byte buffer.
+     * @param len  The length of the data to send
+     */
+    public static native int sendto(long sock, long where, int flags,
+                                    byte[] buf, int offset, int len);
+
+    /**
+     * Read data from a network.
+     *
+     * <PRE>
+     * This functions acts like a blocking read by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     * The number of bytes actually received is stored in argument 3.
+     *
+     * It is possible for both bytes to be received and an APR_EOF or
+     * other error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to read the data from.
+     * @param buf The buffer to store the data in.
+     * @param offset Offset in the byte buffer.
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @return the number of bytes received.
+     */
+    public static native int recv(long sock, byte[] buf, int offset, int nbytes);
+
+    /**
+     * Read data from a network with timeout.
+     *
+     * <PRE>
+     * This functions acts like a blocking read by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     * The number of bytes actually received is stored in argument 3.
+     *
+     * It is possible for both bytes to be received and an APR_EOF or
+     * other error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to read the data from.
+     * @param buf The buffer to store the data in.
+     * @param offset Offset in the byte buffer.
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @param timeout The socket timeout in microseconds.
+     * @return the number of bytes received.
+     */
+    public static native int recvt(long sock, byte[] buf, int offset,
+                                   int nbytes, long timeout);
+
+    /**
+     * Read data from a network.
+     *
+     * <PRE>
+     * This functions acts like a blocking read by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     * The number of bytes actually received is stored in argument 3.
+     *
+     * It is possible for both bytes to be received and an APR_EOF or
+     * other error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to read the data from.
+     * @param buf The buffer to store the data in.
+     * @param offset Offset in the byte buffer.
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @return the number of bytes received.
+     */
+    public static native int recvb(long sock, ByteBuffer buf,
+                                   int offset, int nbytes);
+    /**
+     * Read data from a network using internally set ByteBuffer
+     */
+    public static native int recvbb(long sock,
+                                    int offset, int nbytes);
+    /**
+     * Read data from a network with timeout.
+     *
+     * <PRE>
+     * This functions acts like a blocking read by default.  To change
+     * this behavior, use apr_socket_timeout_set() or the APR_SO_NONBLOCK
+     * socket option.
+     * The number of bytes actually received is stored in argument 3.
+     *
+     * It is possible for both bytes to be received and an APR_EOF or
+     * other error to be returned.
+     *
+     * APR_EINTR is never returned.
+     * </PRE>
+     * @param sock The socket to read the data from.
+     * @param buf The buffer to store the data in.
+     * @param offset Offset in the byte buffer.
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @param timeout The socket timeout in microseconds.
+     * @return the number of bytes received.
+     */
+    public static native int recvbt(long sock, ByteBuffer buf,
+                                    int offset, int nbytes, long timeout);
+    /**
+     * Read data from a network with timeout using internally set ByteBuffer
+     */
+    public static native int recvbbt(long sock,
+                                     int offset, int nbytes, long timeout);
+
+    /**
+     * @param from The apr_sockaddr_t to fill in the recipient info
+     * @param sock The socket to use
+     * @param flags The flags to use
+     * @param buf  The buffer to use
+     * @param offset Offset in the byte buffer.
+     * @param nbytes The number of bytes to read (-1) for full array.
+     * @return the number of bytes received.
+     */
+    public static native int recvfrom(long from, long sock, int flags,
+                                      byte[] buf, int offset, int nbytes);
+
+    /**
+     * Setup socket options for the specified socket
+     * @param sock The socket to set up.
+     * @param opt The option we would like to configure.  One of:
+     * <PRE>
+     * APR_SO_DEBUG      --  turn on debugging information
+     * APR_SO_KEEPALIVE  --  keep connections active
+     * APR_SO_LINGER     --  lingers on close if data is present
+     * APR_SO_NONBLOCK   --  Turns blocking on/off for socket
+     *                       When this option is enabled, use
+     *                       the APR_STATUS_IS_EAGAIN() macro to
+     *                       see if a send or receive function
+     *                       could not transfer data without
+     *                       blocking.
+     * APR_SO_REUSEADDR  --  The rules used in validating addresses
+     *                       supplied to bind should allow reuse
+     *                       of local addresses.
+     * APR_SO_SNDBUF     --  Set the SendBufferSize
+     * APR_SO_RCVBUF     --  Set the ReceiveBufferSize
+     * </PRE>
+     * @param on Value for the option.
+     */
+    public static native int optSet(long sock, int opt, int on);
+
+    /**
+     * Query socket options for the specified socket
+     * @param sock The socket to query
+     * @param opt The option we would like to query.  One of:
+     * <PRE>
+     * APR_SO_DEBUG      --  turn on debugging information
+     * APR_SO_KEEPALIVE  --  keep connections active
+     * APR_SO_LINGER     --  lingers on close if data is present
+     * APR_SO_NONBLOCK   --  Turns blocking on/off for socket
+     * APR_SO_REUSEADDR  --  The rules used in validating addresses
+     *                       supplied to bind should allow reuse
+     *                       of local addresses.
+     * APR_SO_SNDBUF     --  Set the SendBufferSize
+     * APR_SO_RCVBUF     --  Set the ReceiveBufferSize
+     * APR_SO_DISCONNECTED -- Query the disconnected state of the socket.
+     *                       (Currently only used on Windows)
+     * </PRE>
+     * @return Socket option returned on the call.
+     */
+    public static native int optGet(long sock, int opt)
+        throws Exception;
+
+    /**
+     * Setup socket timeout for the specified socket
+     * @param sock The socket to set up.
+     * @param t Value for the timeout in microseconds.
+     * <PRE>
+     * t &gt; 0  -- read and write calls return APR_TIMEUP if specified time
+     *           elapses with no data read or written
+     * t == 0 -- read and write calls never block
+     * t &lt; 0  -- read and write calls block
+     * </PRE>
+     */
+    public static native int timeoutSet(long sock, long t);
+
+    /**
+     * Query socket timeout for the specified socket
+     * @param sock The socket to query
+     * @return Socket timeout returned from the query.
+     */
+    public static native long timeoutGet(long sock)
+        throws Exception;
+
+    /**
+     * Send a file from an open file descriptor to a socket, along with
+     * optional headers and trailers.
+     * <br>
+     * This functions acts like a blocking write by default.  To change
+     *         this behavior, use apr_socket_timeout_set() or the
+     *         APR_SO_NONBLOCK socket option.
+     * The number of bytes actually sent is stored in the len parameter.
+     * The offset parameter is passed by reference for no reason; its
+     * value will never be modified by the apr_socket_sendfile() function.
+     * @param sock The socket to which we're writing
+     * @param file The open file from which to read
+     * @param headers Array containing the headers to send
+     * @param trailers Array containing the trailers to send
+     * @param offset Offset into the file where we should begin writing
+     * @param len Number of bytes to send from the file
+     * @param flags APR flags that are mapped to OS specific flags
+     * @return Number of bytes actually sent, including headers,
+     *         file, and trailers
+     *
+     */
+    public static native long sendfile(long sock, long file, byte [][] headers,
+                                       byte[][] trailers, long offset,
+                                       long len, int flags);
+
+    /**
+     * Send a file without header and trailer arrays.
+     */
+    public static native long sendfilen(long sock, long file, long offset,
+                                        long len, int flags);
+
+    /**
+     * Create a child pool from associated socket pool.
+     * @param thesocket The socket to use
+     */
+    public static native long pool(long thesocket)
+        throws Exception;
+
+    /**
+     * Private method for getting the socket struct members
+     * @param socket The socket to use
+     * @param what Struct member to obtain
+     * <PRE>
+     * SOCKET_GET_POOL  - The socket pool
+     * SOCKET_GET_IMPL  - The socket implementation object
+     * SOCKET_GET_APRS  - APR socket
+     * SOCKET_GET_TYPE  - Socket type
+     * </PRE>
+     * @return The structure member address
+     */
+    private static native long get(long socket, int what);
+
+    /**
+     * Set internal send ByteBuffer.
+     * This function will preset internal Java ByteBuffer for
+     * consecutive sendbb calls.
+     * @param sock The socket to use
+     * @param buf The ByteBuffer
+     */
+    public static native void setsbb(long sock, ByteBuffer buf);
+
+    /**
+     * Set internal receive ByteBuffer.
+     * This function will preset internal Java ByteBuffer for
+     * consecutive revcvbb/recvbbt calls.
+     * @param sock The socket to use
+     * @param buf The ByteBuffer
+     */
+    public static native void setrbb(long sock, ByteBuffer buf);
+
+    /**
+     * Set the data associated with the current socket.
+     * @param sock The currently open socket.
+     * @param data The user data to associate with the socket.
+     * @param key The key to associate with the data.
+     */
+      public static native int dataSet(long sock, String key, Object data);
+
+    /**
+     * Return the data associated with the current socket
+     * @param key The key to associate with the user data.
+     * @param sock The currently open socket.
+     * @return Data or null in case of error.
+     */
+     public static native Object dataGet(long sock, String key);
+}
diff --git a/java/src/org/apache/tomcat/jni/Status.java b/java/src/org/apache/tomcat/jni/Status.java
new file mode 100644
index 0000000..bb79797
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Status.java
@@ -0,0 +1,263 @@
+/*
+ *  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.tomcat.jni;
+
+/** Status
+ *
+ * @author Mladen Turk
+ */
+public class Status {
+
+    /**
+     * APR_OS_START_ERROR is where the APR specific error values start.
+     */
+     public static final int APR_OS_START_ERROR   = 20000;
+    /**
+     * APR_OS_ERRSPACE_SIZE is the maximum number of errors you can fit
+     *    into one of the error/status ranges below -- except for
+     *    APR_OS_START_USERERR, which see.
+     */
+     public static final int APR_OS_ERRSPACE_SIZE = 50000;
+    /**
+     * APR_OS_START_STATUS is where the APR specific status codes start.
+     */
+     public static final int APR_OS_START_STATUS  = (APR_OS_START_ERROR + APR_OS_ERRSPACE_SIZE);
+
+    /**
+     * APR_OS_START_USERERR are reserved for applications that use APR that
+     *     layer their own error codes along with APR's.  Note that the
+     *     error immediately following this one is set ten times farther
+     *     away than usual, so that users of apr have a lot of room in
+     *     which to declare custom error codes.
+     */
+    public static final int APR_OS_START_USERERR  = (APR_OS_START_STATUS + APR_OS_ERRSPACE_SIZE);
+    /**
+     * APR_OS_START_USEERR is obsolete, defined for compatibility only.
+     * Use APR_OS_START_USERERR instead.
+     */
+    public static final int APR_OS_START_USEERR    = APR_OS_START_USERERR;
+    /**
+     * APR_OS_START_CANONERR is where APR versions of errno values are defined
+     *     on systems which don't have the corresponding errno.
+     */
+    public static final int APR_OS_START_CANONERR  = (APR_OS_START_USERERR + (APR_OS_ERRSPACE_SIZE * 10));
+
+    /**
+     * APR_OS_START_EAIERR folds EAI_ error codes from getaddrinfo() into
+     *     apr_status_t values.
+     */
+    public static final int APR_OS_START_EAIERR  = (APR_OS_START_CANONERR + APR_OS_ERRSPACE_SIZE);
+    /**
+     * APR_OS_START_SYSERR folds platform-specific system error values into
+     *     apr_status_t values.
+     */
+    public static final int APR_OS_START_SYSERR  = (APR_OS_START_EAIERR + APR_OS_ERRSPACE_SIZE);
+
+    /** no error. */
+    public static final int APR_SUCCESS = 0;
+
+    /**
+     * APR Error Values
+     * <PRE>
+     * <b>APR ERROR VALUES</b>
+     * APR_ENOSTAT      APR was unable to perform a stat on the file
+     * APR_ENOPOOL      APR was not provided a pool with which to allocate memory
+     * APR_EBADDATE     APR was given an invalid date
+     * APR_EINVALSOCK   APR was given an invalid socket
+     * APR_ENOPROC      APR was not given a process structure
+     * APR_ENOTIME      APR was not given a time structure
+     * APR_ENODIR       APR was not given a directory structure
+     * APR_ENOLOCK      APR was not given a lock structure
+     * APR_ENOPOLL      APR was not given a poll structure
+     * APR_ENOSOCKET    APR was not given a socket
+     * APR_ENOTHREAD    APR was not given a thread structure
+     * APR_ENOTHDKEY    APR was not given a thread key structure
+     * APR_ENOSHMAVAIL  There is no more shared memory available
+     * APR_EDSOOPEN     APR was unable to open the dso object.  For more
+     *                  information call apr_dso_error().
+     * APR_EGENERAL     General failure (specific information not available)
+     * APR_EBADIP       The specified IP address is invalid
+     * APR_EBADMASK     The specified netmask is invalid
+     * APR_ESYMNOTFOUND Could not find the requested symbol
+     * </PRE>
+     *
+     */
+    public static final int APR_ENOSTAT       = (APR_OS_START_ERROR + 1);
+    public static final int APR_ENOPOOL       = (APR_OS_START_ERROR + 2);
+    public static final int APR_EBADDATE      = (APR_OS_START_ERROR + 4);
+    public static final int APR_EINVALSOCK    = (APR_OS_START_ERROR + 5);
+    public static final int APR_ENOPROC       = (APR_OS_START_ERROR + 6);
+    public static final int APR_ENOTIME       = (APR_OS_START_ERROR + 7);
+    public static final int APR_ENODIR        = (APR_OS_START_ERROR + 8);
+    public static final int APR_ENOLOCK       = (APR_OS_START_ERROR + 9);
+    public static final int APR_ENOPOLL       = (APR_OS_START_ERROR + 10);
+    public static final int APR_ENOSOCKET     = (APR_OS_START_ERROR + 11);
+    public static final int APR_ENOTHREAD     = (APR_OS_START_ERROR + 12);
+    public static final int APR_ENOTHDKEY     = (APR_OS_START_ERROR + 13);
+    public static final int APR_EGENERAL      = (APR_OS_START_ERROR + 14);
+    public static final int APR_ENOSHMAVAIL   = (APR_OS_START_ERROR + 15);
+    public static final int APR_EBADIP        = (APR_OS_START_ERROR + 16);
+    public static final int APR_EBADMASK      = (APR_OS_START_ERROR + 17);
+    public static final int APR_EDSOOPEN      = (APR_OS_START_ERROR + 19);
+    public static final int APR_EABSOLUTE     = (APR_OS_START_ERROR + 20);
+    public static final int APR_ERELATIVE     = (APR_OS_START_ERROR + 21);
+    public static final int APR_EINCOMPLETE   = (APR_OS_START_ERROR + 22);
+    public static final int APR_EABOVEROOT    = (APR_OS_START_ERROR + 23);
+    public static final int APR_EBADPATH      = (APR_OS_START_ERROR + 24);
+    public static final int APR_EPATHWILD     = (APR_OS_START_ERROR + 25);
+    public static final int APR_ESYMNOTFOUND  = (APR_OS_START_ERROR + 26);
+    public static final int APR_EPROC_UNKNOWN = (APR_OS_START_ERROR + 27);
+    public static final int APR_ENOTENOUGHENTROPY = (APR_OS_START_ERROR + 28);
+
+    /** APR Status Values
+     * <PRE>
+     * <b>APR STATUS VALUES</b>
+     * APR_INCHILD        Program is currently executing in the child
+     * APR_INPARENT       Program is currently executing in the parent
+     * APR_DETACH         The thread is detached
+     * APR_NOTDETACH      The thread is not detached
+     * APR_CHILD_DONE     The child has finished executing
+     * APR_CHILD_NOTDONE  The child has not finished executing
+     * APR_TIMEUP         The operation did not finish before the timeout
+     * APR_INCOMPLETE     The operation was incomplete although some processing
+     *                    was performed and the results are partially valid
+     * APR_BADCH          Getopt found an option not in the option string
+     * APR_BADARG         Getopt found an option that is missing an argument
+     *                    and an argument was specified in the option string
+     * APR_EOF            APR has encountered the end of the file
+     * APR_NOTFOUND       APR was unable to find the socket in the poll structure
+     * APR_ANONYMOUS      APR is using anonymous shared memory
+     * APR_FILEBASED      APR is using a file name as the key to the shared memory
+     * APR_KEYBASED       APR is using a shared key as the key to the shared memory
+     * APR_EINIT          Ininitalizer value.  If no option has been found, but
+     *                    the status variable requires a value, this should be used
+     * APR_ENOTIMPL       The APR function has not been implemented on this
+     *                    platform, either because nobody has gotten to it yet,
+     *                    or the function is impossible on this platform.
+     * APR_EMISMATCH      Two passwords do not match.
+     * APR_EBUSY          The given lock was busy.
+     * </PRE>
+     *
+     */
+    public static final int APR_INCHILD       = (APR_OS_START_STATUS + 1);
+    public static final int APR_INPARENT      = (APR_OS_START_STATUS + 2);
+    public static final int APR_DETACH        = (APR_OS_START_STATUS + 3);
+    public static final int APR_NOTDETACH     = (APR_OS_START_STATUS + 4);
+    public static final int APR_CHILD_DONE    = (APR_OS_START_STATUS + 5);
+    public static final int APR_CHILD_NOTDONE = (APR_OS_START_STATUS + 6);
+    public static final int APR_TIMEUP        = (APR_OS_START_STATUS + 7);
+    public static final int APR_INCOMPLETE    = (APR_OS_START_STATUS + 8);
+    public static final int APR_BADCH         = (APR_OS_START_STATUS + 12);
+    public static final int APR_BADARG        = (APR_OS_START_STATUS + 13);
+    public static final int APR_EOF           = (APR_OS_START_STATUS + 14);
+    public static final int APR_NOTFOUND      = (APR_OS_START_STATUS + 15);
+    public static final int APR_ANONYMOUS     = (APR_OS_START_STATUS + 19);
+    public static final int APR_FILEBASED     = (APR_OS_START_STATUS + 20);
+    public static final int APR_KEYBASED      = (APR_OS_START_STATUS + 21);
+    public static final int APR_EINIT         = (APR_OS_START_STATUS + 22);
+    public static final int APR_ENOTIMPL      = (APR_OS_START_STATUS + 23);
+    public static final int APR_EMISMATCH     = (APR_OS_START_STATUS + 24);
+    public static final int APR_EBUSY         = (APR_OS_START_STATUS + 25);
+
+    public static final int TIMEUP            = (APR_OS_START_USERERR + 1);
+    public static final int EAGAIN            = (APR_OS_START_USERERR + 2);
+    public static final int EINTR             = (APR_OS_START_USERERR + 3);
+    public static final int EINPROGRESS       = (APR_OS_START_USERERR + 4);
+    public static final int ETIMEDOUT         = (APR_OS_START_USERERR + 5);
+
+    private static native boolean is(int err, int idx);
+    /**
+     * APR_STATUS_IS Status Value Tests
+     * <br><b>Warning :</b> For any particular error condition, more than one of these tests
+     *      may match. This is because platform-specific error codes may not
+     *      always match the semantics of the POSIX codes these tests (and the
+     *      corresponding APR error codes) are named after. A notable example
+     *      are the APR_STATUS_IS_ENOENT and APR_STATUS_IS_ENOTDIR tests on
+     *      Win32 platforms. The programmer should always be aware of this and
+     *      adjust the order of the tests accordingly.
+     *
+     */
+    public static final boolean APR_STATUS_IS_ENOSTAT(int s)    { return is(s, 1); }
+    public static final boolean APR_STATUS_IS_ENOPOOL(int s)    { return is(s, 2); }
+    /* empty slot: +3 */
+    public static final boolean APR_STATUS_IS_EBADDATE(int s)   { return is(s, 4); }
+    public static final boolean APR_STATUS_IS_EINVALSOCK(int s) { return is(s, 5); }
+    public static final boolean APR_STATUS_IS_ENOPROC(int s)    { return is(s, 6); }
+    public static final boolean APR_STATUS_IS_ENOTIME(int s)    { return is(s, 7); }
+    public static final boolean APR_STATUS_IS_ENODIR(int s)     { return is(s, 8); }
+    public static final boolean APR_STATUS_IS_ENOLOCK(int s)    { return is(s, 9); }
+    public static final boolean APR_STATUS_IS_ENOPOLL(int s)    { return is(s, 10); }
+    public static final boolean APR_STATUS_IS_ENOSOCKET(int s)  { return is(s, 11); }
+    public static final boolean APR_STATUS_IS_ENOTHREAD(int s)  { return is(s, 12); }
+    public static final boolean APR_STATUS_IS_ENOTHDKEY(int s)  { return is(s, 13); }
+    public static final boolean APR_STATUS_IS_EGENERAL(int s)   { return is(s, 14); }
+    public static final boolean APR_STATUS_IS_ENOSHMAVAIL(int s){ return is(s, 15); }
+    public static final boolean APR_STATUS_IS_EBADIP(int s)     { return is(s, 16); }
+    public static final boolean APR_STATUS_IS_EBADMASK(int s)   { return is(s, 17); }
+    /* empty slot: +18 */
+    public static final boolean APR_STATUS_IS_EDSOPEN(int s)    { return is(s, 19); }
+    public static final boolean APR_STATUS_IS_EABSOLUTE(int s)  { return is(s, 20); }
+    public static final boolean APR_STATUS_IS_ERELATIVE(int s)  { return is(s, 21); }
+    public static final boolean APR_STATUS_IS_EINCOMPLETE(int s){ return is(s, 22); }
+    public static final boolean APR_STATUS_IS_EABOVEROOT(int s) { return is(s, 23); }
+    public static final boolean APR_STATUS_IS_EBADPATH(int s)   { return is(s, 24); }
+    public static final boolean APR_STATUS_IS_EPATHWILD(int s)  { return is(s, 25); }
+    public static final boolean APR_STATUS_IS_ESYMNOTFOUND(int s)      { return is(s, 26); }
+    public static final boolean APR_STATUS_IS_EPROC_UNKNOWN(int s)     { return is(s, 27); }
+    public static final boolean APR_STATUS_IS_ENOTENOUGHENTROPY(int s) { return is(s, 28); }
+
+    /*
+     * APR_Error
+     */
+    public static final boolean APR_STATUS_IS_INCHILD(int s)    { return is(s, 51); }
+    public static final boolean APR_STATUS_IS_INPARENT(int s)   { return is(s, 52); }
+    public static final boolean APR_STATUS_IS_DETACH(int s)     { return is(s, 53); }
+    public static final boolean APR_STATUS_IS_NOTDETACH(int s)  { return is(s, 54); }
+    public static final boolean APR_STATUS_IS_CHILD_DONE(int s) { return is(s, 55); }
+    public static final boolean APR_STATUS_IS_CHILD_NOTDONE(int s)  { return is(s, 56); }
+    public static final boolean APR_STATUS_IS_TIMEUP(int s)     { return is(s, 57); }
+    public static final boolean APR_STATUS_IS_INCOMPLETE(int s) { return is(s, 58); }
+    /* empty slot: +9 */
+    /* empty slot: +10 */
+    /* empty slot: +11 */
+    public static final boolean APR_STATUS_IS_BADCH(int s)      { return is(s, 62); }
+    public static final boolean APR_STATUS_IS_BADARG(int s)     { return is(s, 63); }
+    public static final boolean APR_STATUS_IS_EOF(int s)        { return is(s, 64); }
+    public static final boolean APR_STATUS_IS_NOTFOUND(int s)   { return is(s, 65); }
+    /* empty slot: +16 */
+    /* empty slot: +17 */
+    /* empty slot: +18 */
+    public static final boolean APR_STATUS_IS_ANONYMOUS(int s)  { return is(s, 69); }
+    public static final boolean APR_STATUS_IS_FILEBASED(int s)  { return is(s, 70); }
+    public static final boolean APR_STATUS_IS_KEYBASED(int s)   { return is(s, 71); }
+    public static final boolean APR_STATUS_IS_EINIT(int s)      { return is(s, 72); }
+    public static final boolean APR_STATUS_IS_ENOTIMPL(int s)   { return is(s, 73); }
+    public static final boolean APR_STATUS_IS_EMISMATCH(int s)  { return is(s, 74); }
+    public static final boolean APR_STATUS_IS_EBUSY(int s)      { return is(s, 75); }
+
+    /* Socket errors */
+    public static final boolean APR_STATUS_IS_EAGAIN(int s)     { return is(s, 90); }
+    public static final boolean APR_STATUS_IS_ETIMEDOUT(int s)  { return is(s, 91); }
+    public static final boolean APR_STATUS_IS_ECONNABORTED(int s) { return is(s, 92); }
+    public static final boolean APR_STATUS_IS_ECONNRESET(int s)   { return is(s, 93); }
+    public static final boolean APR_STATUS_IS_EINPROGRESS(int s)  { return is(s, 94); }
+    public static final boolean APR_STATUS_IS_EINTR(int s)      { return is(s, 95); }
+    public static final boolean APR_STATUS_IS_ENOTSOCK(int s)   { return is(s, 96); }
+    public static final boolean APR_STATUS_IS_EINVAL(int s)     { return is(s, 97); }
+
+}
diff --git a/java/src/org/apache/tomcat/jni/Stdlib.java b/java/src/org/apache/tomcat/jni/Stdlib.java
new file mode 100644
index 0000000..532a53e
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Stdlib.java
@@ -0,0 +1,88 @@
+/*
+ *  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.tomcat.jni;
+
+/** Stdlib
+ *
+ * @author Mladen Turk
+ */
+public class Stdlib {
+
+    /**
+     * Read from plain memory
+     * @param dst Destination byte array
+     * @param src Source memory address
+     * @param sz Number of bytes to copy.
+     */
+    public static native boolean memread(byte [] dst, long src, int sz);
+
+    /**
+     * Write to plain memory
+     * @param dst Destination memory address
+     * @param src Source byte array
+     * @param sz Number of bytes to copy.
+     */
+    public static native boolean memwrite(long dst, byte [] src, int sz);
+
+    /**
+     * Sets buffers to a specified character
+     * @param dst Destination memory address
+     * @param c Character to set.
+     * @param sz Number of characters.
+     */
+    public static native boolean memset(long dst, int c, int sz);
+
+    /**
+     * Allocates memory blocks.
+     * @param sz Bytes to allocate.
+     */
+    public static native long malloc(int sz);
+
+    /**
+     * Reallocate memory blocks.
+     * @param mem Pointer to previously allocated memory block.
+     * @param sz New size in bytes.
+     */
+    public static native long realloc(long mem, int sz);
+
+    /**
+     * Allocates an array in memory with elements initialized to 0.
+     * @param num Number of elements.
+     * @param sz Length in bytes of each element.
+     */
+    public static native long calloc(int num, int sz);
+
+    /**
+     * Deallocates or frees a memory block.
+     * @param mem Previously allocated memory block to be freed.
+     */
+    public static native void free(long mem);
+
+    /**
+     * Get current process pid.
+     * @return current pid or &lt; 1 in case of error.
+     */
+    public static native int getpid();
+
+    /**
+     * Get current process parent pid.
+     * @return parent pid or &lt; 1 in case of error.
+     */
+    public static native int getppid();
+
+}
diff --git a/java/src/org/apache/tomcat/jni/Thread.java b/java/src/org/apache/tomcat/jni/Thread.java
new file mode 100644
index 0000000..e7fc920
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Thread.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.tomcat.jni;
+
+/** Thread
+ *
+ * @author Mladen Turk
+ */
+public class Thread {
+
+    /**
+     * Get the current thread ID handle.
+     */
+    public static native long current();
+
+}
diff --git a/java/src/org/apache/tomcat/jni/Time.java b/java/src/org/apache/tomcat/jni/Time.java
new file mode 100644
index 0000000..02e6d49
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/Time.java
@@ -0,0 +1,72 @@
+/*
+ *  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.tomcat.jni;
+
+/** Time
+ *
+ * @author Mladen Turk
+ */
+public class Time {
+
+    /** number of microseconds per second */
+    public static final long APR_USEC_PER_SEC  = 1000000L;
+    /** number of milliseconds per microsecond */
+    public static final long APR_MSEC_PER_USEC = 1000L;
+
+    /** @return apr_time_t as a second */
+    public static long sec(long t)
+    {
+        return t / APR_USEC_PER_SEC;
+    }
+
+    /** @return apr_time_t as a msec */
+    public static long msec(long t)
+    {
+        return t / APR_MSEC_PER_USEC;
+    }
+
+    /**
+     * number of microseconds since 00:00:00 January 1, 1970 UTC
+     * @return the current time
+     */
+    public static native long now();
+
+    /**
+     * Formats dates in the RFC822
+     * format in an efficient manner.
+     * @param t the time to convert
+     */
+    public static native String rfc822(long t);
+
+    /**
+     * Formats dates in the ctime() format
+     * in an efficient manner.
+     * Unlike ANSI/ISO C ctime(), apr_ctime() does not include
+     * a \n at the end of the string.
+     * @param t the time to convert
+     */
+    public static native String ctime(long t);
+
+    /**
+     * Sleep for the specified number of micro-seconds.
+     * <br><b>Warning :</b> May sleep for longer than the specified time.
+     * @param t desired amount of time to sleep.
+     */
+    public static native void sleep(long t);
+
+}
diff --git a/java/src/org/apache/tomcat/jni/User.java b/java/src/org/apache/tomcat/jni/User.java
new file mode 100644
index 0000000..0af7283
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/User.java
@@ -0,0 +1,125 @@
+/*
+ *  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.tomcat.jni;
+
+/** User
+ *
+ * @author Mladen Turk
+ */
+public class User {
+
+    /**
+     * Get the userid (and groupid) of the calling process
+     * This function is available only if APR_HAS_USER is defined.
+     * @param p The pool from which to allocate working space
+     * @return Returns the user id
+     */
+     public static native long uidCurrent(long p)
+        throws Error;
+
+    /**
+     * Get the groupid of the calling process
+     * This function is available only if APR_HAS_USER is defined.
+     * @param p The pool from which to allocate working space
+     * @return Returns the group id
+     */
+     public static native long gidCurrent(long p)
+        throws Error;
+
+
+    /**
+     * Get the userid for the specified username
+     * This function is available only if APR_HAS_USER is defined.
+     * @param username The username to lookup
+     * @param p The pool from which to allocate working space
+     * @return Returns the user id
+     */
+     public static native long uid(String username, long p)
+        throws Error;
+
+    /**
+     * Get the groupid for the specified username
+     * This function is available only if APR_HAS_USER is defined.
+     * @param username The username to lookup
+     * @param p The pool from which to allocate working space
+     * @return  Returns the user's group id
+     */
+     public static native long usergid(String username, long p)
+        throws Error;
+
+    /**
+     * Get the groupid for a specified group name
+     * This function is available only if APR_HAS_USER is defined.
+     * @param groupname The group name to look up
+     * @param p The pool from which to allocate working space
+     * @return  Returns the user's group id
+     */
+     public static native long gid(String groupname, long p)
+        throws Error;
+
+    /**
+     * Get the user name for a specified userid
+     * This function is available only if APR_HAS_USER is defined.
+     * @param userid The userid
+     * @param p The pool from which to allocate the string
+     * @return New string containing user name
+     */
+     public static native String username(long userid, long p)
+        throws Error;
+
+    /**
+     * Get the group name for a specified groupid
+     * This function is available only if APR_HAS_USER is defined.
+     * @param groupid The groupid
+     * @param p The pool from which to allocate the string
+     * @return New string containing group name
+     */
+     public static native String groupname(long groupid, long p)
+        throws Error;
+
+    /**
+     * Compare two user identifiers for equality.
+     * This function is available only if APR_HAS_USER is defined.
+     * @param left One uid to test
+     * @param right Another uid to test
+     * @return APR_SUCCESS if the apr_uid_t structures identify the same user,
+     * APR_EMISMATCH if not, APR_BADARG if an apr_uid_t is invalid.
+     */
+     public static native int uidcompare(long left, long right);
+
+    /**
+     * Compare two group identifiers for equality.
+     * This function is available only if APR_HAS_USER is defined.
+     * @param left One gid to test
+     * @param right Another gid to test
+     * @return APR_SUCCESS if the apr_gid_t structures identify the same group,
+     * APR_EMISMATCH if not, APR_BADARG if an apr_gid_t is invalid.
+     */
+     public static native int gidcompare(long left, long right);
+
+    /**
+     * Get the home directory for the named user
+     * This function is available only if APR_HAS_USER is defined.
+     * @param username The named user
+     * @param p The pool from which to allocate the string
+     * @return New string containing directory name
+     */
+     public static native String homepath(String username, long p)
+        throws Error;
+
+}
diff --git a/java/src/org/apache/tomcat/jni/socket/AprSocket.java b/java/src/org/apache/tomcat/jni/socket/AprSocket.java
new file mode 100644
index 0000000..1800bab
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/socket/AprSocket.java
@@ -0,0 +1,922 @@
+/*
+ *  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.tomcat.jni.socket;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.jni.Address;
+import org.apache.tomcat.jni.Error;
+import org.apache.tomcat.jni.Poll;
+import org.apache.tomcat.jni.SSL;
+import org.apache.tomcat.jni.SSLExt;
+import org.apache.tomcat.jni.SSLSocket;
+import org.apache.tomcat.jni.Sockaddr;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
+import org.apache.tomcat.jni.socket.AprSocketContext.AprPoller;
+import org.apache.tomcat.jni.socket.AprSocketContext.BlockingPollHandler;
+
+/**
+ * Native socket, using JNI + APR + openssl.
+ *
+ * The socket is non-blocking - you can register either a blocking or non
+ * blocking callback.
+ *
+ * There is no explicit method to register/unregister poll interest -
+ * it is done automatically, when read/write methods return 0.
+ *
+ * To keep the socket polling you must read all the available data, until
+ * read() returns 0. If you want to pause - don't read all input. To resume -
+ * read again until it returns 0.
+ *
+ * Same for write - when write() returns 0 the socket is registered for
+ * write interest.
+ *
+ * You can also use the blocking read/write methods.
+ */
+public class AprSocket implements Runnable {
+
+    private static final Logger log =
+            Logger.getLogger("org.apache.tomcat.jni.socket.AprSocket");
+
+    private static final byte[][] NO_CERTS = new byte[0][];
+
+    static final int CONNECTING = 0x1;
+    static final int CONNECTED = 0x2;
+
+    // Current ( real ) poll status
+    static final int POLLIN_ACTIVE = 0x4;
+    static final int POLLOUT_ACTIVE = 0x8;
+
+    static final int POLL = 0x10;
+
+    static final int SSL_ATTACHED = 0x40;
+
+    // Requested poll status. Set by read/write when needed.
+    // Cleared when polled
+    static final int POLLIN = 0x80;
+    static final int POLLOUT = 0x100;
+
+    static final int ACCEPTED = 0x200;
+    static final int ERROR = 0x400;
+    static final int CLOSED = 0x800;
+
+    static final int READING = 0x1000;
+    static final int WRITING = 0x2000;
+
+    // Not null
+    private final AprSocketContext context;
+
+    // only one - to save per/socket memory - context has similar callbacks.
+    BlockingPollHandler handler;
+
+    // Set while it's associated with a poller - it'll stay associated after
+    // connect until close. Destroy will happen in the poller.
+    // POLL bit indicates if the socket is actually polling.
+    AprPoller poller;
+
+    // Bit field indicating the status and socket should only be accessed with
+    // socketLock protection
+    private int status;
+
+    long socket;
+
+    //long to = 10000;
+
+    // Persistent info about the peer ( SSL, etc )
+    private HostInfo hostInfo;
+
+    AprSocket(AprSocketContext context) {
+        this.context = context;
+    }
+
+    public void recycle() {
+        status = 0;
+        hostInfo = null;
+        handler = null;
+        socket = 0;
+        poller = null;
+    }
+
+    @Override
+    public String toString() {
+        return (context.isServer() ? "AprSrv-" : "AprCli-") +
+                Long.toHexString(socket) + " " + Integer.toHexString(status);
+    }
+
+    public void setHandler(BlockingPollHandler l) {
+        handler = l;
+    }
+
+    private void setNonBlocking() {
+        if (socket != 0 && context.running) {
+            Socket.optSet(socket, Socket.APR_SO_NONBLOCK, 1);
+            Socket.timeoutSet(socket, 0);
+        }
+    }
+
+    /**
+     * Check if the socket is currently registered with a poller.
+     */
+    public boolean isPolling() {
+        synchronized (this) {
+            return (status & POLL) != 0;
+        }
+    }
+
+    public BlockingPollHandler getHandler() {
+        return handler;
+    }
+
+    public AprSocketContext getContext() {
+        return context;
+    }
+
+    AprSocket setHost(HostInfo hi) {
+        hostInfo = hi;
+        return this;
+    }
+
+    /**
+     */
+    public void connect() throws IOException {
+        if (isBlocking()) {
+            // will call handleConnected() at the end.
+            context.connectBlocking(this);
+        } else {
+            synchronized(this) {
+                if ((status & CONNECTING) != 0) {
+                    return;
+                }
+                status |= CONNECTING;
+            }
+            context.connectExecutor.execute(this);
+        }
+    }
+
+
+    // after connection is done, called from a thread pool ( not IO thread )
+    // may block for handshake.
+    void afterConnect() throws IOException {
+        if (hostInfo.secure) {
+            blockingStartTLS();
+        }
+
+        setNonBlocking(); // call again, to set the bits ( connect was blocking )
+
+        setStatus(CONNECTED);
+        clearStatus(CONNECTING);
+
+        notifyConnected(false);
+    }
+
+    public HostInfo getHost() {
+        return hostInfo;
+    }
+
+    /**
+     * Write as much data as possible to the socket.
+     *
+     * @param data
+     * @param off
+     * @param len
+     * @return  For both blocking and non-blocking, returns the number of bytes
+     *          written. If no data can be written (e.g. if the buffers are
+     *          full) 0 will be returned.
+     * @throws IOException
+     */
+    public int write(byte[] data, int off, int len, long to) throws IOException {
+        long max = System.currentTimeMillis() + to;
+
+        while (true) {
+            int rc = writeInternal(data, off, len);
+            if (rc < 0) {
+                throw new IOException("Write error " + rc);
+            } else if (rc == 0) {
+                // need poll out - do we need to update polling ?
+                context.findPollerAndAdd(this);
+            } else {
+                return rc;
+            }
+
+            try {
+                long waitTime = max - System.currentTimeMillis();
+                if (waitTime <= 0) {
+                    return 0;
+                }
+                wait(waitTime);
+            } catch (InterruptedException e) {
+                return 0;
+            }
+        }
+    }
+
+    public int write(byte[] data, int off, int len) throws IOException {
+        // In SSL mode, read/write can't be called at the same time.
+        int rc = writeInternal(data, off, len);
+        if (rc < 0) {
+            throw new IOException("Write error " + rc);
+        } else if (rc == 0) {
+            // need poll out - do we need to update polling ?
+            synchronized (this) {
+                context.findPollerAndAdd(this);
+            }
+        }
+        return rc;
+    }
+
+    private int writeInternal(byte[] data, int off, int len) throws IOException {
+        int rt = 0;
+        int sent = 0;
+        synchronized(this) {
+            if ((status & CLOSED) != 0
+                    || socket == 0
+                    || !context.running) {
+                throw new IOException("Closed");
+            }
+            if ((status & WRITING) != 0) {
+                throw new IOException("Write from 2 threads not allowed");
+            }
+            status |= WRITING;
+
+            while (len > 0) {
+                sent = Socket.send(socket, data, off, len);
+                if (sent <= 0) {
+                    break;
+                }
+                off += sent;
+                len -= sent;
+            }
+
+            status &= ~WRITING;
+        }
+
+        if (context.rawDataHandler != null) {
+            context.rawData(this, false, data, off, sent, len, false);
+        }
+
+        if (sent <= 0) {
+            if (sent == -Status.TIMEUP || sent == -Status.EAGAIN || sent == 0) {
+                setStatus(POLLOUT);
+                updatePolling();
+                return rt;
+            }
+            log.warning("apr.send(): Failed to send, closing " + sent);
+            reset();
+            throw new IOException("Error sending " + sent + " " + Error.strerror(-sent));
+        } else {
+            off += sent;
+            len -= sent;
+            rt += sent;
+            return sent;
+        }
+    }
+
+    public int read(byte[] data, int off, int len, long to) throws IOException {
+            int rd = readNB(data, off, len);
+            if (rd == 0) {
+                synchronized(this) {
+                    try {
+                        wait(to);
+                    } catch (InterruptedException e) {
+                        return 0;
+                    }
+                }
+                rd = readNB(data, off, len);
+            }
+            return processReadResult(data, off, len, rd);
+    }
+
+    public int read(byte[] data, int off, int len) throws IOException {
+        return readNB(data, off, len);
+    }
+
+    private int processReadResult(byte[] data, int off, int len, int read)
+            throws IOException {
+        if (context.rawDataHandler != null) {
+            context.rawData(this, true, data, off, read, len, false);
+        }
+
+        if (read > 0) {
+            return read;
+        }
+
+        if (read == 0 || read == -Status.TIMEUP || read == -Status.ETIMEDOUT
+                || read == -Status.EAGAIN) {
+            read = 0;
+            setStatus(POLLIN);
+            updatePolling();
+            return 0;
+        }
+
+        if (read == -Status.APR_EOF || read == -1) {
+            close();
+            return -1;
+        }
+        // abrupt close
+        reset();
+        throw new IOException("apr.read(): " + read + " " + Error.strerror(-read));
+    }
+
+    public int readNB(byte[] data, int off, int len) throws IOException {
+        int read;
+        synchronized(this) {
+            if ((status & CLOSED) != 0
+                    || socket == 0
+                    || !context.running) {
+                return -1;
+            }
+            if ((status & READING) != 0) {
+                throw new IOException("Read from 2 threads not allowed");
+            }
+            status |= READING;
+
+            read = Socket.recv(socket, data, off, len);
+            status &= ~READING;
+        }
+        return processReadResult(data, off, len, read);
+    }
+
+    /*
+      No support for shutdownOutput: SSL is quite tricky.
+      Use close() instead - no read/write will be allowed after.
+
+     */
+
+    public void close() {
+        synchronized (this) {
+            if ((status & CLOSED) != 0 || socket == 0) {
+                return;
+            }
+            status |= CLOSED;
+            status &= ~POLLIN;
+            status &= ~POLLOUT;
+        }
+        if (context.rawDataHandler != null) {
+            context.rawDataHandler.rawData(this, false, null, 0, 0, 0, true);
+        }
+        Socket.close(socket);
+        if (poller == null) {
+            maybeDestroy();
+        } else  {
+            try {
+                poller.requestUpdate(this);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    void maybeDestroy() {
+        synchronized(this) {
+            if (socket == 0 ||
+                    (status & CONNECTING) != 0 || !context.running) {
+                // closed or operation in progress
+                // if context stopped, pool will be destroyed and close
+                // all sockets automatically.
+                return;
+            }
+            if ((status & CLOSED) == 0) {
+                return; // not closed
+            }
+            if ((status & (WRITING | READING)) != 0) {
+                return; // not closed
+            }
+
+            if (context.rawDataHandler != null) {
+                context.rawDataHandler.rawData(this, false, null, -1, -1, -1, true);
+            }
+            if (log.isLoggable(Level.FINE)) {
+                log.info("closing: context.open=" + context.open.get() + " " + this);
+            }
+
+            context.open.decrementAndGet();
+
+            if (socket != 0 && (status & CLOSED) == 0) {
+                Socket.close(socket);
+                status |= CLOSED;
+            }
+
+            if (handler != null) {
+                if (isBlocking()) {
+                    context.getExecutor().execute(this);
+                } else {
+                    handler.closed(this);
+                }
+            }
+
+            context.destroySocket(this);
+        }
+    }
+
+
+
+    /**
+     * Close input and output, potentially sending RST, than close the socket.
+     *
+     * The proper way to close when gracefully done is by calling writeEnd() and
+     * reading all remaining input until -1 (EOF) is received.
+     *
+     * If EOF is received, the proper way to close is send whatever is remaining and
+     * call writeEnd();
+     */
+    public void reset() {
+        setStatus(ERROR);
+        close();
+    }
+
+
+    /**
+     */
+    public boolean isClosed() {
+        synchronized(this) {
+            if ((status & CLOSED) != 0 || socket == 0 || !context.running) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    public long getIOTimeout() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                return Socket.timeoutGet(socket) / 1000;
+            } catch (Exception e) {
+                throw new IOException(e);
+            }
+        } else {
+            throw new IOException("Socket is closed");
+        }
+    }
+
+    // Cert is in DER format
+    // Called after handshake
+    public byte[][] getPeerCert(boolean check) throws IOException {
+        getHost();
+        if (hostInfo.certs != null && hostInfo.certs != NO_CERTS && !check) {
+            return hostInfo.certs;
+        }
+        if (!checkBitAndSocket(SSL_ATTACHED)) {
+            return NO_CERTS;
+        }
+        try {
+            int certLength = SSLSocket.getInfoI(socket,
+                    SSL.SSL_INFO_CLIENT_CERT_CHAIN);
+            // TODO: if resumed, old certs are good.
+            // If not - warn if certs changed, remember first cert, etc.
+            if (certLength <= 0) {
+                // Can also happen on session resume - keep the old
+                if (hostInfo.certs == null) {
+                    hostInfo.certs = NO_CERTS;
+                }
+                return hostInfo.certs;
+            }
+            hostInfo.certs = new byte[certLength + 1][];
+
+            hostInfo.certs[0] = SSLSocket.getInfoB(socket,
+                    SSL.SSL_INFO_CLIENT_CERT);
+            for (int i = 0; i < certLength; i++) {
+                hostInfo.certs[i + 1] = SSLSocket.getInfoB(socket,
+                        SSL.SSL_INFO_CLIENT_CERT_CHAIN + i);
+            }
+            return hostInfo.certs;
+        } catch (Exception e) {
+            throw new IOException(e);
+        }
+    }
+
+    public X509Certificate[] getPeerX509Cert() throws IOException {
+        byte[][] certs = getPeerCert(false);
+        X509Certificate[] xcerts = new X509Certificate[certs.length];
+        if (certs.length == 0) {
+            return xcerts;
+        }
+        try {
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            for (int i = 0; i < certs.length; i++) {
+                if (certs[i] != null) {
+                    ByteArrayInputStream bis = new ByteArrayInputStream(
+                            certs[i]);
+                    xcerts[i] = (X509Certificate) cf.generateCertificate(bis);
+                    bis.close();
+                }
+            }
+        } catch (CertificateException ex) {
+            throw new IOException(ex);
+        }
+        return xcerts;
+    }
+
+    public String getCipherSuite() throws IOException {
+        if (checkBitAndSocket(SSL_ATTACHED)) {
+            return null;
+        }
+        try {
+            return SSLSocket.getInfoS(socket, SSL.SSL_INFO_CIPHER);
+        } catch (Exception e) {
+            throw new IOException(e);
+        }
+    }
+
+    public int getKeySize() throws IOException {
+        if (checkBitAndSocket(SSL_ATTACHED)) {
+            return -1;
+        }
+        try {
+            return SSLSocket.getInfoI(socket, SSL.SSL_INFO_CIPHER_USEKEYSIZE);
+        } catch (Exception e) {
+            throw new IOException(e);
+        }
+    }
+
+    public int getRemotePort() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                long sa = Address.get(Socket.APR_REMOTE, socket);
+                Sockaddr addr = Address.getInfo(sa);
+                return addr.port;
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        }
+        throw new IOException("Socket closed");
+    }
+
+    public String getRemoteAddress() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                long sa = Address.get(Socket.APR_REMOTE, socket);
+                return Address.getip(sa);
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        }
+        throw new IOException("Socket closed");
+    }
+
+    public String getRemoteHostname() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                long sa = Address.get(Socket.APR_REMOTE, socket);
+                String remoteHost = Address.getnameinfo(sa, 0);
+                if (remoteHost == null) {
+                    remoteHost = Address.getip(sa);
+                }
+                return remoteHost;
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        }
+        throw new IOException("Socket closed");
+    }
+
+    public int getLocalPort() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                long sa = Address.get(Socket.APR_LOCAL, socket);
+                Sockaddr addr = Address.getInfo(sa);
+                return addr.port;
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        }
+        throw new IOException("Socket closed");
+    }
+
+    public String getLocalAddress() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                long sa = Address.get(Socket.APR_LOCAL, socket);
+                return Address.getip(sa);
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        }
+        throw new IOException("Socket closed");
+    }
+
+    public String getLocalHostname() throws IOException {
+        if (socket != 0 && context.running) {
+            try {
+                long sa = Address.get(Socket.APR_LOCAL, socket);
+                return Address.getnameinfo(sa, 0);
+            } catch (Exception ex) {
+                throw new IOException(ex);
+            }
+        }
+        throw new IOException("Socket closed");
+    }
+
+    public boolean isBlocking() {
+        return ! (handler instanceof AprSocketContext.NonBlockingPollHandler);
+    }
+
+    public boolean isError() {
+        return checkPreConnect(ERROR);
+    }
+
+    void notifyError(Throwable err, boolean needsThread) {
+        if (handler instanceof AprSocketContext.NonBlockingPollHandler) {
+            if (err != null) {
+                ((AprSocketContext.NonBlockingPollHandler) handler).error(this, err);
+            }
+        } else {
+            // poller destroyed, etc
+            if (needsThread) {
+                context.getExecutor().execute(this);
+            } else {
+                try {
+                    notifyIO();
+                } catch (IOException e) {
+                    log.log(Level.SEVERE, this + " error ", e);
+                }
+            }
+        }
+    }
+
+    // Called after connect and from poller.
+    void notifyIO() throws IOException {
+        long t0 = System.currentTimeMillis();
+        try {
+            if (handler != null) {
+                handler.process(this, true, false, false);
+            }
+        } catch (Throwable t) {
+            throw new IOException(t);
+        } finally {
+            long t1 = System.currentTimeMillis();
+            t1 -= t0;
+            if (t1 > context.maxHandlerTime.get()) {
+                context.maxHandlerTime.set(t1);
+            }
+            context.totalHandlerTime.addAndGet(t1);
+            context.handlerCount.incrementAndGet();
+        }
+    }
+
+    private void notifyConnected(boolean server) throws IOException {
+        // Will set the handler on the channel for accepted
+        context.onSocket(this);
+
+        if (handler instanceof AprSocketContext.NonBlockingPollHandler) {
+            ((AprSocketContext.NonBlockingPollHandler) handler).connected(this);
+
+            ((AprSocketContext.NonBlockingPollHandler) handler).process(this, true, true, false);
+            // Now register for polling - unless process() set suspendRead and
+            // doesn't need out notifications
+            updatePolling();
+        } else {
+            if (server) {
+                // client will block in connect().
+                // Server: call process();
+                notifyIO();
+            }
+        }
+    }
+
+    private void updatePolling() throws IOException {
+        synchronized (this) {
+            if ((status & CLOSED) != 0) {
+                maybeDestroy();
+                return;
+            }
+        }
+        context.findPollerAndAdd(this);
+    }
+
+    @Override
+    public void run() {
+        if (!context.running) {
+            return;
+        }
+        if (checkPreConnect(CLOSED)) {
+            if (handler != null) {
+                handler.closed(this);
+            }
+            return;
+        }
+        if (!checkPreConnect(CONNECTED)) {
+            if (checkBitAndSocket(ACCEPTED)) {
+                try {
+                    context.open.incrementAndGet();
+
+                    if (log.isLoggable(Level.FINE)) {
+                        log.info("Accept: " + context.open.get() + " " + this + " " +
+                                getRemotePort());
+                    }
+                    if (context.tcpNoDelay) {
+                        Socket.optSet(socket, Socket.APR_TCP_NODELAY, 1);
+                    }
+
+                    setStatus(CONNECTED);
+                    if (context.sslMode) {
+                        Socket.timeoutSet(socket,
+                                context.connectTimeout * 1000L);
+                        blockingStartTLS();
+                    }
+                    setNonBlocking(); // call again, to set the bits ( connect was blocking )
+
+                    notifyConnected(true);
+                    return;
+                } catch (Throwable t) {
+                    t.printStackTrace(); // no error handler yet
+                    reset();
+                    notifyError(t, false);
+                    return;
+                }
+            }
+            if (checkPreConnect(CONNECTING)) {
+                // Non-blocking connect - will call 'afterConnection' at the end.
+                try {
+                    context.connectBlocking(this);
+                } catch (IOException t) {
+                    reset(); // also sets status ERROR
+                    if (handler instanceof AprSocketContext.NonBlockingPollHandler) {
+                        ((AprSocketContext.NonBlockingPollHandler) handler).process(this, false, false, true);
+                    }
+                    notifyError(t, false);
+                }
+            }
+        } else {
+            if (handler != null) {
+                try {
+                    notifyIO();
+                } catch (Throwable e) {
+                    log.log(Level.SEVERE, this + " error ", e);
+                    reset();
+                    // no notifyIO - just did it.
+                }
+            }
+        }
+    }
+
+    /**
+     * This is a blocking call ! ( can be made non-blocking, but too complex )
+     *
+     * Will be called automatically after connect() or accept if 'secure' is
+     * true.
+     *
+     * Can be called manually to upgrade the channel
+     * @throws IOException
+     */
+    public void blockingStartTLS() throws IOException {
+        synchronized(this) {
+            if (socket == 0 || !context.running) {
+                return;
+            }
+            if ((status & SSL_ATTACHED) != 0) {
+                return;
+            }
+            status |= SSL_ATTACHED;
+        }
+
+        try {
+            if (log.isLoggable(Level.FINE)) {
+                log.info(this + " StartSSL");
+            }
+
+            AprSocketContext aprCon = context;
+            SSLSocket.attach(aprCon.getSslCtx(), socket);
+
+            if (context.debugSSL) {
+                SSLExt.debug(socket);
+            }
+            if (!getContext().isServer()) {
+                if (context.USE_TICKETS && hostInfo.ticketLen > 0) {
+                    SSLExt.setTicket(socket, hostInfo.ticket,
+                            hostInfo.ticketLen);
+                } else if (hostInfo.sessDer != null) {
+                    SSLExt.setSessionData(socket, hostInfo.sessDer,
+                            hostInfo.sessDer.length);
+                }
+            }
+            SSLExt.sslSetMode(socket, SSLExt.SSL_MODE_ENABLE_PARTIAL_WRITE |
+                    SSLExt.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+            int rc = SSLSocket.handshake(socket);
+
+            // At this point we have the session ID, remote certs, etc
+            // we can lookup host info
+            if (hostInfo == null) {
+                hostInfo = new HostInfo();
+            }
+
+            if (rc != Status.APR_SUCCESS) {
+                throw new IOException(this + " Handshake failed " + rc + " "
+                        + Error.strerror(rc) + " SSLL "
+                        + SSL.getLastError());
+            } else { // SUCCESS
+                handshakeDone();
+            }
+        } catch (IOException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new IOException(e);
+        }
+    }
+
+    private void handshakeDone() throws IOException {
+        getHost();
+        if (socket == 0 || !context.running) {
+            throw new IOException("Socket closed");
+        }
+        if (context.USE_TICKETS && ! context.isServer()) {
+            if (hostInfo.ticket == null) {
+                hostInfo.ticket = new byte[2048];
+            }
+            int ticketLen = SSLExt.getTicket(socket, hostInfo.ticket);
+            if (ticketLen > 0) {
+                hostInfo.ticketLen = ticketLen;
+                if (log.isLoggable(Level.FINE)) {
+                    log.info("Received ticket: " + ticketLen);
+                }
+            }
+        }
+
+        // TODO: if the ticket, session id or session changed - callback to
+        // save the session again
+        try {
+            hostInfo.sessDer = SSLExt.getSessionData(socket);
+            getPeerCert(true);
+            hostInfo.sessionId = SSLSocket.getInfoS(socket,
+                    SSL.SSL_INFO_SESSION_ID);
+        } catch (Exception e) {
+            throw new IOException(e);
+        }
+
+        hostInfo.npn = new byte[32];
+        hostInfo.npnLen = SSLExt.getNPN(socket, hostInfo.npn);
+
+        // If custom verification is used - should check the certificates
+        if (context.tlsCertVerifier != null) {
+            context.tlsCertVerifier.handshakeDone(this);
+        }
+    }
+
+    int requestedPolling() {
+        synchronized(this) {
+            if (socket == 0 || ((status & CLOSED) != 0)) {
+                return 0;
+            }
+            // Implicit:
+            //Poll.APR_POLLNVAL | Poll.APR_POLLHUP | Poll.APR_POLLERR |
+            int res = 0;
+            if ((status & POLLIN) != 0) {
+                res = Poll.APR_POLLIN;
+            }
+            if ((status & POLLOUT) != 0) {
+                res |= Poll.APR_POLLOUT;
+            }
+            return res;
+        }
+    }
+
+    boolean checkBitAndSocket(int bit) {
+        synchronized (this) {
+            return ((status & bit) != 0 && socket != 0 &&
+                    (status & CLOSED) == 0 && context.running);
+        }
+    }
+
+    boolean checkPreConnect(int bit) {
+        synchronized (this) {
+            return ((status & bit) != 0);
+        }
+    }
+
+    void clearStatus(int bit) {
+        synchronized (this) {
+            status &= ~bit;
+        }
+    }
+
+    boolean setStatus(int bit) {
+        synchronized (this) {
+            int old = status & bit;
+            status |= bit;
+            return old != 0;
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/java/src/org/apache/tomcat/jni/socket/AprSocketContext.java b/java/src/org/apache/tomcat/jni/socket/AprSocketContext.java
new file mode 100644
index 0000000..03036c9
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/socket/AprSocketContext.java
@@ -0,0 +1,1352 @@
+/*
+ *  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.tomcat.jni.socket;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.jni.Address;
+import org.apache.tomcat.jni.Error;
+import org.apache.tomcat.jni.Library;
+import org.apache.tomcat.jni.OS;
+import org.apache.tomcat.jni.Poll;
+import org.apache.tomcat.jni.Pool;
+import org.apache.tomcat.jni.SSL;
+import org.apache.tomcat.jni.SSLContext;
+import org.apache.tomcat.jni.SSLExt;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
+
+public class AprSocketContext {
+    /**
+     * Called when a chunk of data is sent or received. This is very low
+     * level, used mostly for debugging or stats.
+     */
+    public static interface RawDataHandler {
+        public void rawData(AprSocket ch, boolean input, byte[] data, int pos,
+                int len, int requested, boolean closed);
+    }
+
+    /**
+     * Called in SSL mode after the handshake is completed.
+     *
+     * @see AprSocketContext#customVerification(TlsCertVerifier)
+     */
+    public static interface TlsCertVerifier {
+        public void handshakeDone(AprSocket ch);
+    }
+
+    /**
+     * Delegates loading of persistent info about a host - public certs,
+     * tickets, config, persistent info etc.
+     */
+    public static interface HostInfoLoader {
+        public HostInfo getHostInfo(String name, int port, boolean ssl);
+    }
+
+    private static final Logger log = Logger.getLogger("AprSocketCtx");
+
+    // If interrupt() or thread-safe poll update are not supported - the
+    // poll updates will happen after the poll() timeout.
+    // The poll timeout with interrupt/thread safe updates can be much higher/
+    private static final int FALLBACK_POLL_TIME = 2000;
+
+    // It seems to send the ticket, get server helo / ChangeCipherSpec, but than
+    // SSL3_GET_RECORD:decryption failed or bad record mac in s3_pkt.c:480:
+    // Either bug in openssl, or some combination of ciphers - needs more debugging.
+    // ( this can save a roundtrip and CPU on TLS handshake )
+    boolean USE_TICKETS = false;
+
+    private final AprSocket END = new AprSocket(this);
+
+    private static final AtomicInteger contextNumber = new AtomicInteger();
+    private int contextId;
+
+    private final AtomicInteger threadNumber = new AtomicInteger();
+
+    /**
+     * For now - single acceptor thread per connector.
+     */
+    private AcceptorThread acceptor;
+    private AcceptorDispatchThread acceptorDispatch;
+
+    // APR/JNI is thread safe
+    private boolean threadSafe = true;
+
+    /**
+     * Pollers.
+     */
+    private final List<AprPoller> pollers = new ArrayList<>();
+
+    // Set on all accepted or connected sockets.
+    // TODO: add the other properties
+    boolean tcpNoDelay = true;
+
+    protected boolean running = true;
+
+    protected boolean sslMode;
+
+    // onSocket() will be called in accept thread.
+    // If false: use executor ( but that may choke the acceptor thread )
+    private boolean nonBlockingAccept = false;
+
+    private final BlockingQueue<AprSocket> acceptedQueue =
+            new LinkedBlockingQueue<>();
+
+    /**
+     * Root APR memory pool.
+     */
+    private long rootPool = 0;
+
+    /**
+     * SSL context.
+     */
+    private volatile long sslCtx = 0;
+
+    TlsCertVerifier tlsCertVerifier;
+
+    //
+    final int connectTimeout =  20000;
+    final int defaultTimeout = 100000;
+    // TODO: Use this
+    final int keepAliveTimeout = 20000;
+
+    final AtomicInteger open = new AtomicInteger();
+
+    /**
+     * Poll interval, in microseconds. If the platform doesn't support
+     * poll interrupt - it'll take this time to stop the poller.
+     *
+     */
+    private int pollTime = 5 * 1000000;
+
+    private HostInfoLoader hostInfoLoader;
+
+    final RawDataHandler rawDataHandler = null;
+
+    // TODO: do we need this here ?
+    private final Map<String, HostInfo> hosts = new HashMap<>();
+
+    private String certFile;
+    private String keyFile;
+
+    private byte[] spdyNPN;
+
+    private byte[] ticketKey;
+
+    // For resolving DNS ( i.e. connect ), callbacks
+    private ExecutorService threadPool;
+
+    // Separate executor for connect/handshakes
+    final ExecutorService connectExecutor;
+
+    final boolean debugSSL = false;
+    private boolean debugPoll = false;
+
+    private boolean deferAccept = false;
+
+    private int backlog = 100;
+
+    private boolean useSendfile;
+
+    private int sslProtocol = SSL.SSL_PROTOCOL_TLSV1 | SSL.SSL_PROTOCOL_TLSV1_1 | SSL.SSL_PROTOCOL_TLSV1_2;
+
+    /**
+     * Max time spent in a callback ( will be longer for blocking )
+     */
+    final AtomicLong maxHandlerTime = new AtomicLong();
+    final AtomicLong totalHandlerTime = new AtomicLong();
+    final AtomicLong handlerCount = new AtomicLong();
+
+    /**
+     * Total connections handled ( accepted or connected ).
+     */
+    private final AtomicInteger connectionsCount = new AtomicInteger();
+
+
+    public AprSocketContext() {
+        connectExecutor =new ThreadPoolExecutor(0, 64, 5, TimeUnit.SECONDS,
+                new LinkedBlockingQueue<Runnable>(), new RejectedExecutionHandler() {
+                    @Override
+                    public void rejectedExecution(Runnable r,
+                            java.util.concurrent.ThreadPoolExecutor executor) {
+                        AprSocket s = (AprSocket) r;
+                        log.severe("Rejecting " + s);
+                        s.reset();
+                    }
+                });
+        contextId = contextNumber.incrementAndGet();
+    }
+
+    /**
+     * Poller thread count.
+     */
+    private int pollerThreadCount = 4;
+    public void setPollerThreadCount(int pollerThreadCount) { this.pollerThreadCount = pollerThreadCount; }
+    public int getPollerThreadCount() { return pollerThreadCount; }
+
+    // to test the limits - default should be lower
+    private int maxConnections = 64 * 1024;
+    public void setMaxconnections(int maxCon) {
+        this.maxConnections = maxCon;
+    }
+
+    public void setBacklog(int backlog) { if (backlog > 0) this.backlog = backlog; }
+    public int getBacklog() { return backlog; }
+
+    /**
+     * Defer accept.
+     */
+    public void setDeferAccept(boolean deferAccept) { this.deferAccept = deferAccept; }
+    public boolean getDeferAccept() { return deferAccept; }
+
+    /**
+     * For client:
+     *   - ClientHello will include the npn extension ( the ID == 0x3374)
+     *   - if ServerHello includes a list of protocols - select one
+     *   - send it after ChangeCipherSpec and before Finish
+     *
+     *  For server:
+     *   - if ClientHello includes the npn extension
+     *    -- will send this string as list of supported protocols in ServerHello
+     *   - read the selection before Finish.
+     * @param npn
+     */
+    public void setNpn(String npn) {
+        byte[] data = npn.getBytes();
+        byte[] npnB = new byte[data.length + 2];
+
+        System.arraycopy(data, 0, npnB, 1, data.length);
+        npnB[0] = (byte) data.length;
+        npnB[npnB.length - 1] = 0;
+        spdyNPN = npnB;
+
+    }
+
+    public void setNpn(byte[] data) {
+        spdyNPN = data;
+    }
+
+    public void setHostLoader(HostInfoLoader handler) {
+        this.hostInfoLoader = handler;
+    }
+
+    public boolean isServer() {
+        return acceptor != null;
+    }
+
+    protected Executor getExecutor() {
+        if (threadPool == null) {
+            threadPool = Executors.newCachedThreadPool(new ThreadFactory( ) {
+                @Override
+                public Thread newThread(Runnable r) {
+                    Thread t = new Thread(r, "AprThread-" + contextId + "-" +
+                            threadNumber.incrementAndGet());
+                    t.setDaemon(true);
+                    return t;
+                }
+            });
+        }
+        return threadPool;
+    }
+
+    /**
+     * All accepted/connected sockets will start handshake automatically.
+     */
+    public AprSocketContext setTls() {
+        this.sslMode = true;
+        return this;
+    }
+
+    public void setTcpNoDelay(boolean b) {
+        tcpNoDelay = b;
+    }
+
+    public void setSslProtocol(String protocol) {
+        protocol = protocol.trim();
+        if ("SSLv2".equalsIgnoreCase(protocol)) {
+            sslProtocol = SSL.SSL_PROTOCOL_SSLV2;
+        } else if ("SSLv3".equalsIgnoreCase(protocol)) {
+            sslProtocol = SSL.SSL_PROTOCOL_SSLV3;
+        } else if ("TLSv1".equalsIgnoreCase(protocol)) {
+            sslProtocol = SSL.SSL_PROTOCOL_TLSV1;
+        } else if ("TLSv1.1".equalsIgnoreCase(protocol)) {
+            sslProtocol = SSL.SSL_PROTOCOL_TLSV1_1;
+        } else if ("TLSv1.2".equalsIgnoreCase(protocol)) {
+            sslProtocol = SSL.SSL_PROTOCOL_TLSV1_2;
+        } else if ("all".equalsIgnoreCase(protocol)) {
+            sslProtocol = SSL.SSL_PROTOCOL_ALL;
+        }
+    }
+
+    public void setTicketKey(byte[] key48Bytes) {
+        if(key48Bytes.length != 48) {
+            throw new RuntimeException("Key must be 48 bytes");
+        }
+        this.ticketKey = key48Bytes;
+    }
+
+    public void customVerification(TlsCertVerifier verifier) {
+        tlsCertVerifier = verifier;
+    }
+
+    // TODO: should have a separate method for switching to tls later.
+    /**
+     * Set certificate, will also enable TLS mode.
+     */
+    public AprSocketContext setKeys(String certPemFile, String keyDerFile) {
+        this.sslMode = true;
+        setTls();
+        certFile = certPemFile;
+        keyFile = keyDerFile;
+        return this;
+    }
+
+    /**
+     * SSL cipher suite.
+     */
+    private String SSLCipherSuite = "ALL";
+    public String getSSLCipherSuite() { return SSLCipherSuite; }
+    public void setSSLCipherSuite(String SSLCipherSuite) { this.SSLCipherSuite = SSLCipherSuite; }
+
+    /**
+     * Override or use hostInfoLoader to implement persistent/memcache storage.
+     */
+    public HostInfo getHostInfo(String host, int port, boolean ssl) {
+        if (hostInfoLoader != null) {
+            return hostInfoLoader.getHostInfo(host, port, ssl);
+        }
+        // Use local cache
+        String key = host + ":" + port;
+        HostInfo pi = hosts.get(key);
+        if (pi != null) {
+            return pi;
+        }
+        pi = new HostInfo(host, port, ssl);
+        hosts.put(key, pi);
+        return pi;
+    }
+
+    protected void rawData(AprSocket ch, boolean inp, byte[] data, int pos,
+            int len, int requested, boolean closed) {
+        if (rawDataHandler != null) {
+            rawDataHandler.rawData(ch, inp, data, pos, len, requested, closed);
+        }
+    }
+
+    public void listen(final int port) throws IOException {
+        if (acceptor != null) {
+            throw new IOException("Already accepting on " + acceptor.port);
+        }
+        if (sslMode && certFile == null) {
+            throw new IOException("Missing certificates for server");
+        }
+        if (sslMode || !nonBlockingAccept) {
+            acceptorDispatch = new AcceptorDispatchThread();
+            acceptorDispatch.setName("AprAcceptorDispatch-" + port);
+            acceptorDispatch.start();
+        }
+
+        acceptor = new AcceptorThread(port);
+        acceptor.prepare();
+        acceptor.setName("AprAcceptor-" + port);
+        acceptor.start();
+
+
+    }
+
+    /**
+     * Get a socket for connectiong to host:port.
+     */
+    public AprSocket socket(String host, int port, boolean ssl) {
+        HostInfo hi = getHostInfo(host, port, ssl);
+        return socket(hi);
+    }
+
+    public AprSocket socket(HostInfo hi) {
+        AprSocket sock = newSocket(this);
+        sock.setHost(hi);
+        return sock;
+    }
+
+    public AprSocket socket(long socket) {
+        AprSocket sock = newSocket(this);
+        // Tomcat doesn't set this
+        SSLExt.sslSetMode(socket, SSLExt.SSL_MODE_ENABLE_PARTIAL_WRITE |
+                SSLExt.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+        sock.setStatus(AprSocket.ACCEPTED);
+        sock.socket = socket;
+        return sock;
+    }
+
+
+    void destroySocket(AprSocket socket) {
+        // TODO: does it need to be done in io thread ?
+        synchronized (socket) {
+            if (socket.socket != 0) {
+                long s = socket.socket;
+                socket.socket = 0;
+                log.info("DESTROY: " + Long.toHexString(s));
+                Socket.destroy(s);
+            }
+        }
+    }
+
+    protected void connectBlocking(AprSocket apr) throws IOException {
+        try {
+            if (!running) {
+                throw new IOException("Stopped");
+            }
+            HostInfo hi = apr.getHost();
+
+            long clientSockP;
+            synchronized (pollers) {
+                long socketpool = Pool.create(getRootPool());
+
+                int family = Socket.APR_INET;
+
+                clientSockP = Socket.create(family,
+                        Socket.SOCK_STREAM,
+                        Socket.APR_PROTO_TCP, socketpool); // or rootPool ?
+            }
+            Socket.timeoutSet(clientSockP, connectTimeout * 1000);
+            if (OS.IS_UNIX) {
+                Socket.optSet(clientSockP, Socket.APR_SO_REUSEADDR, 1);
+            }
+
+            Socket.optSet(clientSockP, Socket.APR_SO_KEEPALIVE, 1);
+
+            // Blocking
+            // TODO: use socket pool
+            // TODO: cache it ( and TTL ) in hi
+            long inetAddress = Address.info(hi.host, Socket.APR_INET,
+                  hi.port, 0, rootPool);
+            // this may take a long time - stop/destroy must wait
+            // at least connect timeout
+            int rc = Socket.connect(clientSockP, inetAddress);
+
+            if (rc != 0) {
+                synchronized (pollers) {
+                    Socket.close(clientSockP);
+                    Socket.destroy(clientSockP);
+                }
+                /////Pool.destroy(socketpool);
+                throw new IOException("Socket.connect(): " + rc + " " + Error.strerror(rc) + " " + connectTimeout);
+            }
+            if (!running) {
+                throw new IOException("Stopped");
+            }
+
+            connectionsCount.incrementAndGet();
+            if (tcpNoDelay) {
+                Socket.optSet(clientSockP, Socket.APR_TCP_NODELAY, 1);
+            }
+
+            Socket.timeoutSet(clientSockP, defaultTimeout * 1000);
+
+            apr.socket = clientSockP;
+
+            apr.afterConnect();
+        } catch (IOException e) {
+            apr.reset();
+            throw e;
+        } catch (Throwable e) {
+            apr.reset();
+            e.printStackTrace();
+            throw new IOException(e);
+        }
+    }
+
+    AprSocket newSocket(AprSocketContext context) {
+        return new AprSocket(context);
+    }
+
+    /**
+     * To clean the pools - we could track if all channels are
+     * closed, but this seems simpler and safer.
+     */
+    @Override
+    protected void finalize() throws Throwable {
+        if (rootPool != 0) {
+            log.warning(this + " GC without stop()");
+            try {
+                stop();
+            } catch (Exception e) {
+                //TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+        }
+        super.finalize();
+    }
+
+
+    public void stop() {
+        synchronized (pollers) {
+            if (!running) {
+                return;
+            }
+            running = false;
+        }
+
+        if (rootPool != 0) {
+            if (acceptor != null) {
+                try {
+                    acceptor.unblock();
+                    acceptor.join();
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (acceptorDispatch != null) {
+                acceptedQueue.add(END);
+                try {
+                    acceptorDispatch.join();
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (threadPool != null) {
+                threadPool.shutdownNow();
+            }
+
+            log.info("Stopping pollers " + contextId);
+
+            while (true) {
+                AprPoller a;
+                synchronized (pollers) {
+                    if (pollers.size() == 0) {
+                        break;
+                    }
+                    a = pollers.remove(0);
+                }
+                a.interruptPoll();
+                try {
+                    a.join();
+                    log.info("Poller " + a.id + " done ");
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+
+    // Called when the last poller has been destroyed.
+    void destroy() {
+        synchronized (pollers) {
+            if (pollers.size() != 0) {
+                return;
+            }
+
+            if (rootPool == 0) {
+                return;
+            }
+            log.info("Destroy root pool " + rootPool);
+            //Pool.destroy(rootPool);
+            //rootPool = 0;
+        }
+    }
+
+    private static IOException noApr;
+    static {
+
+        try {
+            Library.initialize(null);
+            SSL.initialize(null);
+        } catch (Exception e) {
+            noApr = new IOException("APR not present", e);
+        }
+
+    }
+
+    private long getRootPool() throws IOException {
+        if (rootPool == 0) {
+            if (noApr != null) {
+                throw noApr;
+            }
+            // Create the root APR memory pool
+            rootPool = Pool.create(0);
+
+            // Adjust poller sizes
+            if ((OS.IS_WIN32 || OS.IS_WIN64) && (maxConnections > 1024)) {
+                // The maximum per poller to get reasonable performance is 1024
+                pollerThreadCount = maxConnections / 1024;
+                // Adjust poller size so that it won't reach the limit
+                maxConnections = maxConnections - (maxConnections % 1024);
+            }
+        }
+        return rootPool;
+    }
+
+    long getSslCtx() throws Exception {
+        if (sslCtx == 0) {
+            synchronized (AprSocketContext.class) {
+                if (sslCtx == 0) {
+                    boolean serverMode = acceptor != null;
+                    sslCtx = SSLContext.make(getRootPool(),
+                            sslProtocol,
+                            serverMode ? SSL.SSL_MODE_SERVER : SSL.SSL_MODE_CLIENT);
+
+
+                    // SSL.SSL_OP_NO_SSLv3
+                    int opts = SSL.SSL_OP_NO_SSLv2 |
+                        SSL.SSL_OP_SINGLE_DH_USE;
+
+                    if (!USE_TICKETS || serverMode && ticketKey == null) {
+                        opts |= SSL.SSL_OP_NO_TICKET;
+                    }
+
+                    SSLContext.setOptions(sslCtx, opts);
+                    // Set revocation
+                    //        SSLContext.setCARevocation(sslContext, SSLCARevocationFile, SSLCARevocationPath);
+
+                    // Client certificate verification - maybe make it option
+                    try {
+                        SSLContext.setCipherSuite(sslCtx, SSLCipherSuite);
+
+
+                        if (serverMode) {
+                            if (ticketKey != null) {
+                                //SSLExt.setTicketKeys(sslCtx, ticketKey, ticketKey.length);
+                            }
+                            if (certFile != null) {
+                                boolean rc = SSLContext.setCertificate(sslCtx,
+                                        certFile,
+                                        keyFile, null, SSL.SSL_AIDX_DSA);
+                                if (!rc) {
+                                    throw new IOException("Can't set keys");
+                                }
+                            }
+                            SSLContext.setVerify(sslCtx, SSL.SSL_CVERIFY_NONE, 10);
+
+                            if (spdyNPN != null) {
+                                SSLExt.setNPN(sslCtx, spdyNPN, spdyNPN.length);
+                            }
+                        } else {
+                            if (tlsCertVerifier != null) {
+                                // NONE ?
+                                SSLContext.setVerify(sslCtx,
+                                        SSL.SSL_CVERIFY_NONE, 10);
+                            } else {
+                                SSLContext.setCACertificate(sslCtx,
+                                        "/etc/ssl/certs/ca-certificates.crt",
+                                        "/etc/ssl/certs");
+                                SSLContext.setVerify(sslCtx,
+                                        SSL.SSL_CVERIFY_REQUIRE, 10);
+                            }
+
+                            if (spdyNPN != null) {
+                                SSLExt.setNPN(sslCtx, spdyNPN, spdyNPN.length);
+                            }
+                        }
+                    } catch (IOException e) {
+                        throw e;
+                    } catch (Exception e) {
+                        throw new IOException(e);
+                    }
+                }
+            // TODO: try release buffers
+            }
+        }
+        return sslCtx;
+    }
+
+    void findPollerAndAdd(AprSocket ch) throws IOException {
+        if (ch.poller != null) {
+            ch.poller.requestUpdate(ch);
+            return;
+        }
+        assignPoller(ch);
+    }
+
+    void assignPoller(AprSocket ch) throws IOException {
+        AprPoller target = null;
+        synchronized (pollers) {
+            // Make sure we have min number of pollers
+            int needPollers = pollerThreadCount - pollers.size();
+            if (needPollers > 0) {
+                for (int i = needPollers; i > 0; i--) {
+                    pollers.add(allocatePoller());
+                }
+            }
+            int max = 0;
+            for (AprPoller poller: pollers) {
+                int rem = poller.remaining();
+                if (rem > max) {
+                    target = poller;
+                    max = rem;
+                }
+            }
+        }
+        if (target != null && target.add(ch)) {
+            return;
+        }
+
+        // can't be added - add a new poller
+        synchronized (pollers) {
+            AprPoller poller = allocatePoller();
+            poller.add(ch);
+            pollers.add(poller);
+        }
+    }
+
+    /**
+     * Called on each accepted socket (for servers) or after connection (client)
+     * after handshake.
+     */
+    protected void onSocket(@SuppressWarnings("unused") AprSocket s) {
+        // Defaults to NO-OP. Parameter is used by sub-classes.
+    }
+
+    private class AcceptorThread extends Thread {
+        private final int port;
+        private long serverSockPool = 0;
+        private long serverSock = 0;
+
+        private long inetAddress;
+
+        AcceptorThread(int port) {
+            this.port = port;
+            setDaemon(true);
+        }
+
+        void prepare() throws IOException {
+            try {
+                // Create the pool for the server socket
+                serverSockPool = Pool.create(getRootPool());
+
+                int family = Socket.APR_INET;
+                inetAddress =
+                        Address.info(null, family, port, 0, serverSockPool);
+
+                // Create the APR server socket
+                serverSock = Socket.create(family,
+                        Socket.SOCK_STREAM,
+                        Socket.APR_PROTO_TCP, serverSockPool);
+
+                if (OS.IS_UNIX) {
+                    Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
+                }
+                // Deal with the firewalls that tend to drop the inactive sockets
+                Socket.optSet(serverSock, Socket.APR_SO_KEEPALIVE, 1);
+                // Bind the server socket
+                int ret = Socket.bind(serverSock, inetAddress);
+                if (ret != 0) {
+                    throw new IOException("Socket.bind " + ret + " " +
+                            Error.strerror(ret) + " port=" + port);
+                }
+                // Start listening on the server socket
+                ret = Socket.listen(serverSock, backlog );
+                if (ret != 0) {
+                    throw new IOException("endpoint.init.listen"
+                            + ret + " " + Error.strerror(ret));
+                }
+                if (OS.IS_WIN32 || OS.IS_WIN64) {
+                    // On Windows set the reuseaddr flag after the bind/listen
+                    Socket.optSet(serverSock, Socket.APR_SO_REUSEADDR, 1);
+                }
+
+                // Sendfile usage on systems which don't support it cause major problems
+                if (useSendfile && !Library.APR_HAS_SENDFILE) {
+                    useSendfile = false;
+                }
+
+                // Delay accepting of new connections until data is available
+                // Only Linux kernels 2.4 + have that implemented
+                // on other platforms this call is noop and will return APR_ENOTIMPL.
+                if (deferAccept) {
+                    if (Socket.optSet(serverSock, Socket.APR_TCP_DEFER_ACCEPT, 1) == Status.APR_ENOTIMPL) {
+                        deferAccept = false;
+                    }
+                }
+            } catch (Throwable t) {
+                throw new IOException(t);
+            }
+        }
+
+        void unblock() {
+            try (java.net.Socket sock = new java.net.Socket()) {
+                // Easiest ( maybe safest ) way to interrupt accept
+                // we could have it in non-blocking mode, etc
+                sock.connect(new InetSocketAddress("127.0.0.1", port));
+            } catch (Exception ex) {
+                // ignore - the acceptor may have shut down by itself.
+            }
+        }
+
+        @Override
+        public void run() {
+            while (running) {
+                try {
+                    // each socket has a pool.
+                    final AprSocket ch = newSocket(AprSocketContext.this);
+                    ch.setStatus(AprSocket.ACCEPTED);
+
+                    ch.socket = Socket.accept(serverSock);
+                    if (!running) {
+                        break;
+                    }
+                    connectionsCount.incrementAndGet();
+                    if (connectionsCount.get() % 1000 == 0) {
+                        System.err.println("Accepted: " + connectionsCount.get());
+                    }
+
+                    if (nonBlockingAccept && !sslMode) {
+                        ch.setStatus(AprSocket.CONNECTED);
+                        // TODO: SSL really needs a thread.
+                        onSocket(ch);
+                    } else {
+                        acceptedQueue.add(ch);
+                    }
+                } catch (Throwable e) {
+                    e.printStackTrace();
+                }
+            }
+            Socket.close(serverSock);
+        }
+    }
+
+    private class AcceptorDispatchThread extends Thread {
+
+        AcceptorDispatchThread() {
+            setDaemon(true);
+        }
+
+        @Override
+        public void run() {
+            while(running) {
+                try {
+                    AprSocket ch = acceptedQueue.take();
+                    if (ch == END) {
+                        return;
+                    }
+                    connectExecutor.execute(ch);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Create the poller. With some versions of APR, the maximum poller size will
+     * be 62 (recompiling APR is necessary to remove this limitation).
+     * @throws IOException
+     */
+    AprPoller allocatePoller() throws IOException {
+        long pool = Pool.create(getRootPool());
+        int size = maxConnections / pollerThreadCount;
+
+        long serverPollset = allocatePoller(size, pool);
+
+        if (serverPollset == 0 && size > 1024) {
+            log.severe("Falling back to 1024-sized poll, won't scale");
+            size = 1024;
+            serverPollset = allocatePoller(size, pool);
+        }
+        if (serverPollset == 0) {
+            log.severe("Falling back to 62-sized poll, won't scale");
+            size = 62;
+            serverPollset = allocatePoller(size, pool);
+        }
+
+        AprPoller res = new AprPoller();
+        res.pool = pool;
+        res.serverPollset = serverPollset;
+        res.desc = new long[size * 2];
+        res.size = size;
+        res.id = contextId++;
+        res.setDaemon(true);
+        res.setName("AprPoller-" + res.id);
+        res.start();
+        if (debugPoll && !sizeLogged) {
+            sizeLogged = true;
+            log.info("Poller size " + (res.desc.length / 2));
+        }
+        return res;
+    }
+
+    // Removed the 'thread safe' updates for now, to simplify the code
+    // last test shows a small improvement, can switch later.
+    private static boolean sizeLogged = false;
+
+    protected long allocatePoller(int size, long pool) {
+        int flag = threadSafe ? Poll.APR_POLLSET_THREADSAFE: 0;
+        for (int i = 0; i < 2; i++) {
+            try {
+                //  timeout must be -1 - or ttl will take effect, strange results.
+                return Poll.create(size, pool, flag, -1);
+            } catch (Error e) {
+                e.printStackTrace();
+                if (Status.APR_STATUS_IS_EINVAL(e.getError())) {
+                    log.info(" endpoint.poll.limitedpollsize " + size);
+                    return 0;
+                } else if (Status.APR_STATUS_IS_ENOTIMPL(e.getError())) {
+                    // thread safe not supported
+                    log.severe("THREAD SAFE NOT SUPPORTED" + e);
+                    threadSafe = false;
+                    // try again without the flags
+                    continue;
+                } else {
+                    log.severe("endpoint.poll.initfail" + e);
+                    return 0;
+                }
+            }
+        }
+        log.severe("Unexpected ENOTIMPL with flag==0");
+        return 0;
+    }
+
+    class AprPoller extends Thread {
+
+        private int id;
+        private int size;
+        private long serverPollset = 0;
+        private long pool = 0;
+        private long[] desc;
+
+        private long lastPoll;
+        private long lastPollTime;
+        private final AtomicBoolean inPoll = new AtomicBoolean(false);
+
+        // Should be replaced with socket data.
+        // used only to lookup by socket
+        private final Map<Long, AprSocket> channels = new HashMap<>();
+
+        // Active + pending, must be < desc.length / 2
+        // The channel will also have poller=this when active or pending
+        // How many sockets have poller == this
+        private final AtomicInteger keepAliveCount = new AtomicInteger();
+        // Tracks desc, how many sockets are actively polled
+        private final AtomicInteger polledCount = new AtomicInteger();
+
+        private final AtomicInteger pollCount = new AtomicInteger();
+
+        private final List<AprSocket> updates = new ArrayList<>();
+
+        @Override
+        public void run() {
+            if (!running) {
+                return;
+            }
+            if (debugPoll) {
+                log.info("Starting poller " + id + " " + (isServer() ? "SRV ": "CLI "));
+            }
+            long t0 = System.currentTimeMillis();
+            while (running) {
+                try {
+                    updates();
+
+                    // Pool for the specified interval. Remove signaled sockets
+                    synchronized (this) {
+                        inPoll.set(true);
+                    }
+                    // if updates are added after updates and poll - interrupt will have still
+                    // work
+
+                    int rv = Poll.poll(serverPollset, pollTime, desc, true);
+                    synchronized (this) {
+                        inPoll.set(false);
+                        if (!running) {
+                            break;
+                        }
+                    }
+
+                    pollCount.incrementAndGet();
+                    lastPoll = System.currentTimeMillis();
+                    lastPollTime = lastPoll - t0;
+
+                    if (rv > 0) {
+                        if (debugPoll) {
+                            log.info(" Poll() id=" + id + " rv=" + rv + " keepAliveCount=" + keepAliveCount +
+                                    " polled = " + polledCount.get()
+                                    + " time=" + lastPollTime);
+                        }
+                        polledCount.addAndGet(-rv);
+                        for (int pollIdx = 0; pollIdx < rv; pollIdx++) {
+                            long sock = desc[pollIdx * 2 + 1];
+                            AprSocket ch;
+                            boolean blocking = false;
+
+                            synchronized (channels) {
+                                ch = channels.get(Long.valueOf(sock));
+                                if (ch != null) {
+                                    blocking = ch.isBlocking();
+                                } else {
+                                    log.severe("Polled socket not found !!!!!" + Long.toHexString(sock));
+                                    // TODO: destroy/close the raw socket
+                                    continue;
+                                }
+                            }
+                            // was removed from polling
+                            ch.clearStatus(AprSocket.POLL);
+
+                            // We just removed it ( see last param to poll()).
+                            // Check for failed sockets and hand this socket off to a worker
+                            long mask = desc[pollIdx * 2];
+
+                            boolean err = ((mask & Poll.APR_POLLERR) == Poll.APR_POLLERR);
+                            boolean nval = ((mask & Poll.APR_POLLNVAL) != 0);
+                            if (err || nval) {
+                                System.err.println("ERR " + err + " NVAL " + nval);
+                            }
+
+                            boolean out = (mask & Poll.APR_POLLOUT) == Poll.APR_POLLOUT;
+                            boolean in = (mask & Poll.APR_POLLIN) == Poll.APR_POLLIN;
+                            if (debugPoll) {
+                                log.info(" Poll channel: " + Long.toHexString(mask) +
+                                        (out ? " OUT" :"") +
+                                        (in ? " IN": "") +
+                                        (err ? " ERR" : "") +
+                                        " Ch: " + ch);
+                            }
+
+                            // will be set again in process(), if all read/write is done
+                            ch.clearStatus(AprSocket.POLLOUT);
+                            ch.clearStatus(AprSocket.POLLIN);
+
+                            // try to send if needed
+                            if (blocking) {
+                                synchronized (ch) {
+                                    ch.notifyAll();
+                                }
+                                getExecutor().execute(ch);
+                            } else {
+                                ((AprSocketContext.NonBlockingPollHandler) ch.handler).process(ch, in, out, false);
+
+                                // Update polling for the channel (in IO thread, safe)
+                                updateIOThread(ch);
+                            }
+                        }
+                    } else if (rv < 0) {
+                        int errn = -rv;
+                        if (errn == Status.TIMEUP) {
+                            // to or interrupt
+//                            if (debugPoll) {
+//                                log.info(" Poll() timeup" + " keepAliveCount=" + keepAliveCount +
+//                                        " polled = " + polledCount.get()
+//                                        + " time=" + lastPollTime);
+//                            }
+                        } else if (errn == Status.EINTR) {
+                            // interrupt - no need to log
+                        } else {
+                            if (debugPoll) {
+                                log.info(" Poll() rv=" + rv + " keepAliveCount=" + keepAliveCount +
+                                        " polled = " + polledCount.get()
+                                        + " time=" + lastPollTime);
+                            }
+                            /* Any non timeup or interrupted error is critical */
+                            if (errn >  Status.APR_OS_START_USERERR) {
+                                errn -=  Status.APR_OS_START_USERERR;
+                            }
+                            log.severe("endpoint.poll.fail " + errn + " " + Error.strerror(errn));
+                            // Handle poll critical failure
+                            synchronized (this) {
+                                destroyPoller(); // will close all sockets
+                            }
+                            continue;
+                        }
+                    }
+                    // TODO: timeouts
+                } catch (Throwable t) {
+                    log.log(Level.SEVERE, "endpoint.poll.error", t);
+                }
+
+            }
+            if (!running) {
+                destroyPoller();
+            }
+        }
+
+        /**
+                 * Destroy the poller.
+                 */
+        protected void destroyPoller() {
+            synchronized (pollers) {
+                pollers.remove(this);
+            }
+            log.info("Poller stopped after cnt=" +
+                    pollCount.get() +
+                    " sockets=" + channels.size() +
+                    " lastPoll=" + lastPoll);
+
+            // Close all sockets
+            synchronized (this)  {
+                if (serverPollset == 0) {
+                    return;
+                }
+
+//                for (AprSocket ch: channels.values()) {
+//                    ch.poller = null;
+//                    ch.reset();
+//                }
+                keepAliveCount.set(0);
+                log.warning("Destroy pollset");
+                //serverPollset = 0;
+            }
+            Pool.destroy(pool);
+            pool = 0;
+            synchronized (pollers) {
+                // Now we can destroy the root pool
+                if (pollers.size() == 0 && !running) {
+                    log.info("Destroy server context");
+//                    AprSocketContext.this.destroy();
+                }
+            }
+        }
+
+        /**
+         * Called only in poller thread, only used if not thread safe
+         * @throws IOException
+         */
+        protected void updates() throws IOException {
+            synchronized (this) {
+                for (AprSocket up: updates) {
+                    updateIOThread(up);
+                }
+                updates.clear();
+            }
+        }
+
+        void interruptPoll() {
+            try {
+                int rc = Status.APR_SUCCESS;
+                synchronized (this) {
+                    if (serverPollset != 0) {
+                        rc = Poll.interrupt(serverPollset);
+                    } else {
+                        log.severe("Interrupt with closed pollset");
+                    }
+                }
+                if (rc != Status.APR_SUCCESS) {
+                    log.severe("Failed interrupt and not thread safe");
+                }
+            } catch (Throwable t) {
+                t.printStackTrace();
+                if (pollTime > FALLBACK_POLL_TIME) {
+                    pollTime = FALLBACK_POLL_TIME;
+                }
+            }
+        }
+
+
+        int remaining() {
+            synchronized (channels) {
+                return (desc.length - channels.size() * 2);
+            }
+        }
+
+
+
+        /**
+         * Called from any thread, return true if we could add it
+         * to pending.
+         */
+        boolean add(AprSocket ch) throws IOException {
+            synchronized (this) {
+                if (!running) {
+                    return false;
+                }
+                if (keepAliveCount.get() >= size) {
+                    return false;
+                }
+                keepAliveCount.incrementAndGet();
+                ch.poller = this;
+            }
+
+            requestUpdate(ch);
+
+            return true;
+        }
+
+        /**
+         * May be called outside of IOThread.
+         */
+        protected void requestUpdate(AprSocket ch) throws IOException {
+            synchronized (this) {
+                if (!running) {
+                    return;
+                }
+            }
+            if (isPollerThread()) {
+                updateIOThread(ch);
+            } else {
+                synchronized (this) {
+                    if (!updates.contains(ch)) {
+                        updates.add(ch);
+                    }
+                    interruptPoll();
+                }
+                if (debugPoll) {
+                    log.info("Poll: requestUpdate " + id + " " + ch);
+                }
+            }
+        }
+
+        private void updateIOThread(AprSocket ch) throws IOException {
+            if (!running || ch.socket == 0) {
+                return;
+            }
+            // called from IO thread, either in 'updates' or after
+            // poll.
+            //synchronized (ch)
+            boolean polling = ch.checkPreConnect(AprSocket.POLL);
+
+            int requested = ch.requestedPolling();
+            if (requested == 0) {
+                if (polling) {
+                    removeSafe(ch);
+                }
+                if (ch.isClosed()) {
+                    synchronized (channels) {
+                        ch.poller = null;
+                        channels.remove(Long.valueOf(ch.socket));
+                    }
+                    keepAliveCount.decrementAndGet();
+                    ch.reset();
+                }
+            } else {
+                if (polling) {
+                    removeSafe(ch);
+                }
+                // will close if error
+                pollAdd(ch, requested);
+            }
+            if (debugPoll) {
+                log.info("Poll: updated=" + id + " " + ch);
+            }
+        }
+
+        /**
+         * Called only from IO thread
+         */
+        private void pollAdd(AprSocket up, int req) throws IOException {
+            boolean failed = false;
+            int rv;
+            synchronized (channels) {
+                if (up.isClosed()) {
+                    return;
+                }
+                rv = Poll.add(serverPollset, up.socket, req);
+                if (rv != Status.APR_SUCCESS) {
+                    up.poller = null;
+                    keepAliveCount.decrementAndGet();
+                    failed = true;
+                } else {
+                    polledCount.incrementAndGet();
+                    channels.put(Long.valueOf(up.socket), up);
+                    up.setStatus(AprSocket.POLL);
+                }
+            }
+            if (failed) {
+                up.reset();
+                throw new IOException("poll add error " +  rv + " " + up + " " + Error.strerror(rv));
+            }
+        }
+
+        /**
+         * Called only from IO thread. Remove from Poll and channels,
+         * set POLL bit to false.
+         */
+        private void removeSafe(AprSocket up) {
+            int rv = Status.APR_EGENERAL;
+            if (running && serverPollset != 0 && up.socket != 0
+                    && !up.isClosed()) {
+                rv = Poll.remove(serverPollset, up.socket);
+            }
+            up.clearStatus(AprSocket.POLL);
+
+            if (rv != Status.APR_SUCCESS) {
+                log.severe("poll remove error " +  Error.strerror(rv) + " " + up);
+            } else {
+                polledCount.decrementAndGet();
+            }
+        }
+
+
+        public boolean isPollerThread() {
+            return Thread.currentThread() == this;
+        }
+
+    }
+
+    /**
+     * Callback for poll events, will be invoked in a thread pool.
+     *
+     */
+    public static interface BlockingPollHandler {
+
+        /**
+         * Called when the socket has been polled for in, out or closed.
+         *
+         *
+         */
+        public void process(AprSocket ch, boolean in, boolean out, boolean close);
+
+
+        /**
+         *  Called just before the socket is destroyed
+         */
+        public void closed(AprSocket ch);
+    }
+
+    /**
+     *  Additional callbacks for non-blocking.
+     *  This can be much faster - but it's harder to code, should be used only
+     *  for low-level protocol implementation, proxies, etc.
+     *
+     *  The model is restricted in many ways to avoid complexity and bugs:
+     *
+     *  - read can only happen in the IO thread associated with the poller
+     *  - user doesn't control poll interest - it is set automatically based
+     *  on read/write results
+     *  - it is only possible to suspend read, for TCP flow control - also
+     *  only from the IO thread. Resume can happen from any thread.
+     *  - it is also possible to call write() from any thread
+     */
+    public static interface NonBlockingPollHandler extends BlockingPollHandler {
+
+        /**
+         * Called after connection is established, in a thread pool.
+         * Process will be called next.
+         */
+        public void connected(AprSocket ch);
+
+        /**
+         * Before close, if an exception happens.
+         */
+        public void error(AprSocket ch, Throwable t);
+    }
+
+}
diff --git a/java/src/org/apache/tomcat/jni/socket/HostInfo.java b/java/src/org/apache/tomcat/jni/socket/HostInfo.java
new file mode 100644
index 0000000..4262910
--- /dev/null
+++ b/java/src/org/apache/tomcat/jni/socket/HostInfo.java
@@ -0,0 +1,84 @@
+/*
+ *  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.tomcat.jni.socket;
+
+import java.io.Serializable;
+
+/**
+ * Information about the remote host. Persisting this in memcache or similar
+ * storage can improve performance on future TLS connections by skipping roundtrips
+ * and reducing CPU use in handshake.
+ *
+ * This class is used in both server and client mode.
+ *
+ * AprSocketContextLitener.getPeer(name) can be used to read from an external storage.
+ *
+ * TODO: also save the SPDY persistent settings here.
+ * TODO: fix tickets, don't seem to work anymore.
+ */
+public class HostInfo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    public String host;
+
+    public int port;
+
+    public boolean secure;
+
+    /**
+     * Raw cert data (x.509 format).
+     * This is retrieved when a full handshake happens - if session reuse or tickets
+     * are used you'll not receive the certs again.
+     */
+    public byte[][] certs;
+
+    public byte[] ticket;
+    public int ticketLen;
+
+    public String sessionId;
+
+    /**
+     * DER-encoded session data.
+     */
+    public byte[] sessDer;
+
+    /**
+     * Negotiated NPN.
+     */
+    byte[] npn;
+    int npnLen;
+
+    public HostInfo() {
+    }
+
+    public HostInfo(String host, int port, boolean secure) {
+        this.host = host;
+        this.port = port;
+        this.secure = secure;
+    }
+
+    public String getNpn() {
+        return new String(npn, 0, npnLen);
+    }
+
+    public void setNpn(String npn) {
+        if (npn == null) {
+            npnLen = 0;
+        }
+    }
+}
\ No newline at end of file