os400: implement character encoding conversion support
diff --git a/Makefile.am b/Makefile.am
index 8bc8afe..761733e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -34,9 +34,11 @@
 
 OS400FILES = os400/README400 os400/initscript.sh os400/make.sh		\
 os400/make-src.sh os400/make-rpg.sh os400/make-include.sh		\
-os400/os400sys.c os400/libssh2_config.h os400/macros.h			\
+os400/os400sys.c os400/ccsid.c						\
+os400/libssh2_config.h os400/macros.h os400/libssh2_ccsid.h		\
 os400/include/alloca.h os400/include/sys/socket.h os400/include/stdio.h	\
 os400/libssh2rpg/libssh2.rpgle.in					\
+os400/libssh2rpg/libssh2_ccsid.rpgle.in					\
 os400/libssh2rpg/libssh2_publickey.rpgle				\
 os400/libssh2rpg/libssh2_sftp.rpgle					\
 Makefile.os400qc3.inc
diff --git a/os400/README400 b/os400/README400
index 2a3f5f7..fdac7da 100644
--- a/os400/README400
+++ b/os400/README400
@@ -13,9 +13,10 @@
   QADRT does not define ASCII wrappers for all C/system procedures: an
 additional module (os400sys.c) define some more of them, that are used by
 libssh2 and that QADRT left out.
-  Presently, there is no EBCDIC support wrappers provided. It is the
-responsibility of the caller to convert procedure string values and arguments
-between ASCII and EBCDIC.
+  Since standard library entry points expect and return ASCII character strings,
+additional procedures are provided for string transcoding (see below). No
+wrappers to standard procedures are provided: however, nested calls to
+transcoding procedures may be used.
 
 Crypto API is provided by the IBM QC3 API library. It supports RSA, but not DSA.
 
@@ -89,6 +90,62 @@
 
 
 
+String transcoding support:
+
+  To help passing arbitrarily encoded string arguments and/or receiving string
+values from/to the libssh2 API, three non-standard additional procedures are
+provided. They use a session pointer and a "string cache" pointer.
+  Each time a string is transcoded, it is cached in the given cache. It is
+the responsibility of the caller to release the cache when its associted strings
+are no longer needed. These procedures and the string cache type are defined
+in a new libssh2_ccsid.h header file.
+  To create a string cache, use:
+
+#include <libssh2_ccsid.h>
+libssh2_string_cache *  cache = NULL;
+
+  To release all strings in a cache, call:
+
+libssh2_release_string_cache(session, &cache);
+
+  The transcoding procedures are:
+
+char * libssh2_from_ccsid(LIBSSH2_SESSION *session,
+                          libssh2_string_cache **cache,
+                          unsigned short ccsid,
+                          const char *string, ssize_t inlen,
+                          size_t *outlen);
+char * libssh2_to_ccsid(LIBSSH2_SESSION *session,
+                        libssh2_string_cache **cache,
+                        unsigned short ccsid,
+                        const char *string, ssize_t inlen,
+                        size_t *outlen);
+
+where:
+    session      is a libssh2 session used for memory allocation.
+    cache        is the address of a string cache.
+    ccsid        is the external (i.e.: non libssh2) coded character set id.
+                 65535 means no conversion and 0 means the current job's CCSID.
+    string       is the string to convert.
+    inlen        is the source string length in bytes: set to -1 if
+                 null-terminated.
+    outlen       if not NULL, is the address of a variable that will receive
+                 the transcoded string length upon return.
+
+  libssh2_from_ccsid() transcodes the string from the given CCSID to libssh2
+internal encoding (UTF-8). It is intended to be used to convert API input
+parameters.
+  libssh2_to_ccsid() transcodes the string from libssh2 internal encoding
+(UTF-8) to the given CCSID. This has been implemented to get standard API
+string results in a program's native encoding.
+
+  Both these functions return a pointer to the null-terminated converted string,
+or NULL if an error occurred. In addition, the variable pointed by outlen
+receives the effective byte length of the (cached) translated string, or -1
+in case of error.
+
+
+
 ILE/RPG support:
 
   Since 95% of the OS/400 programmers use ILE/RPG exclusively, a definition
@@ -102,7 +159,7 @@
      d/include libssh2/libssh2rpg,libssh2
 
 in the global data section of the module's source code.
-If required, members ssh2_sftp and ssh2_pkey may also be included.
+If required, members ssh2_sftp, ssh2_pkey and ssh2_ccsid may also be included.
 
 For IFS source compilations, include members are located in directory
 /libssh2/include/libssh2rpg and have their original names retained.
diff --git a/os400/ccsid.c b/os400/ccsid.c
new file mode 100644
index 0000000..ef02f1d
--- /dev/null
+++ b/os400/ccsid.c
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2015 Patrick Monnerat, D+H <patrick.monnerat@dh.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms,
+ * with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ *   Redistributions of source code must retain the above
+ *   copyright notice, this list of conditions and the
+ *   following disclaimer.
+ *
+ *   Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials
+ *   provided with the distribution.
+ *
+ *   Neither the name of the copyright holder nor the names
+ *   of any other contributors may be used to endorse or
+ *   promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+/* Character encoding wrappers. */
+
+#include "libssh2_priv.h"
+#include "libssh2_ccsid.h"
+
+#include <qtqiconv.h>
+#include <iconv.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+
+
+#define CCSID_UTF8      1208
+#define CCSID_UTF16BE   13488
+#define STRING_GRANULE  256
+#define MAX_CHAR_SIZE   4
+
+#define OFFSET_OF(t, f) ((size_t) ((char *) &((t *) 0)->f - (char *) 0))
+
+
+struct _libssh2_string_cache {
+    libssh2_string_cache *  next;
+    char                    string[1];
+};
+
+
+static const QtqCode_T  utf8code = { CCSID_UTF8 };
+
+
+static ssize_t
+terminator_size(unsigned short ccsid)
+{
+    QtqCode_T outcode;
+    iconv_t cd;
+    char *inp;
+    char *outp;
+    size_t ilen;
+    size_t olen;
+    char buf[MAX_CHAR_SIZE];
+
+    /* Return the null-terminator size for the given CCSID. */
+
+    /* Fast check usual CCSIDs. */
+    switch (ccsid) {
+    case CCSID_UTF8:
+    case 0:                                 /* Job CCSID is SBCS EBCDIC. */
+        return 1;
+    case CCSID_UTF16BE:
+        return 2;
+    }
+
+    /* Convert an UTF-8 NUL to the target CCSID: use the converted size as
+       result. */
+    memset((void *) &outcode, 0, sizeof outcode);
+    outcode.CCSID = ccsid;
+    cd = QtqIconvOpen(&outcode, (QtqCode_T *) &utf8code);
+    if (cd.return_value == -1)
+        return -1;
+    inp = "";
+    ilen = 1;
+    outp = buf;
+    olen = sizeof buf;
+    iconv(cd, &inp, &ilen, &outp, &olen);
+    iconv_close(cd);
+    olen = sizeof buf - olen;
+    return olen? olen: -1;
+}
+
+static char *
+convert_ccsid(LIBSSH2_SESSION *session, libssh2_string_cache **cache,
+              unsigned short outccsid, unsigned short inccsid,
+              const char *instring, ssize_t inlen, size_t *outlen)
+{
+    char *inp;
+    char *outp;
+    size_t olen;
+    size_t ilen;
+    size_t buflen;
+    size_t curlen;
+    ssize_t termsize;
+    int i;
+    char *dst;
+    libssh2_string_cache *outstring;
+    QtqCode_T incode;
+    QtqCode_T outcode;
+    iconv_t cd;
+
+    if (!instring) {
+        if (outlen)
+            *outlen = 0;
+        return NULL;
+    }
+    if (outlen)
+        *outlen = -1;
+    if (!session || !cache)
+        return NULL;
+
+    /* Get terminator size. */
+    termsize = terminator_size(outccsid);
+    if (termsize < 0)
+        return NULL;
+ 
+    /* Prepare conversion parameters. */
+    memset((void *) &incode, 0, sizeof incode);
+    memset((void *) &outcode, 0, sizeof outcode);
+    incode.CCSID = inccsid;
+    outcode.CCSID = outccsid;
+    curlen = OFFSET_OF(libssh2_string_cache, string);
+    inp = (char *) instring;
+    ilen = inlen;
+    buflen = inlen + curlen;
+    if (inlen < 0) {
+        incode.length_option = 1;
+        buflen = STRING_GRANULE;
+        ilen = 0;
+    }
+
+    /* Allocate output string buffer and open conversion descriptor. */
+    dst = LIBSSH2_ALLOC(session, buflen + termsize);
+    if (!dst)
+        return NULL;
+    cd = QtqIconvOpen(&outcode, &incode);
+    if (cd.return_value == -1) {
+        LIBSSH2_FREE(session, (char *) dst);
+        return NULL;
+    }
+
+    /* Convert string. */
+    for (;;) {
+        outp = dst + curlen;
+        olen = buflen - curlen;
+        i = iconv(cd, &inp, &ilen, &outp, &olen);
+        if (inlen < 0 && olen == buflen - curlen) {
+            /* Special case: converted 0-length (sub)strings do not store the
+               terminator. */
+            if (termsize) {
+                memset(outp, 0, termsize);
+                olen -= termsize;
+            }
+        }
+        curlen = buflen - olen;
+        if (i >= 0 || errno != E2BIG)
+            break;
+        /* Must expand buffer. */
+        buflen += STRING_GRANULE;
+        outp = LIBSSH2_REALLOC(session, dst, buflen + termsize);
+        if (!outp)
+            break;
+        dst = outp;
+    }
+
+    iconv_close(cd);
+
+    /* Check for error. */
+    if (i < 0 || !outp) {
+        LIBSSH2_FREE(session, dst);
+        return NULL;
+    }
+
+    /* Process terminator. */
+    if (inlen < 0)
+        curlen -= termsize;
+    else if (termsize)
+        memset(dst + curlen, 0, termsize);
+
+    /* Shorten buffer if possible. */
+    if (curlen < buflen)
+        dst = LIBSSH2_REALLOC(session, dst, curlen + termsize);
+
+    /* Link to cache. */
+    outstring = (libssh2_string_cache *) dst;
+    outstring->next = *cache;
+    *cache = outstring;
+
+    /* Return length if required. */
+    if (outlen)
+        *outlen = curlen - OFFSET_OF(libssh2_string_cache, string);
+
+    return outstring->string;
+}
+
+LIBSSH2_API char *
+libssh2_from_ccsid(LIBSSH2_SESSION *session, libssh2_string_cache **cache,
+                   unsigned short ccsid, const char *string, ssize_t inlen,
+                   size_t *outlen)
+{
+    return convert_ccsid(session, cache,
+                         CCSID_UTF8, ccsid, string, inlen, outlen);
+}
+
+LIBSSH2_API char *
+libssh2_to_ccsid(LIBSSH2_SESSION *session, libssh2_string_cache **cache,
+                 unsigned short ccsid, const char *string, ssize_t inlen,
+                 size_t *outlen)
+{
+    return convert_ccsid(session, cache,
+                         ccsid, CCSID_UTF8, string, inlen, outlen);
+}
+
+LIBSSH2_API void
+libssh2_release_string_cache(LIBSSH2_SESSION *session,
+                             libssh2_string_cache **cache)
+{
+    libssh2_string_cache *p;
+
+    if (session && cache)
+        while ((p = *cache)) {
+            *cache = p->next;
+            LIBSSH2_FREE(session, (char *) p);
+        }
+}
+
+/* vim: set expandtab ts=4 sw=4: */
diff --git a/os400/libssh2_ccsid.h b/os400/libssh2_ccsid.h
new file mode 100644
index 0000000..632effa
--- /dev/null
+++ b/os400/libssh2_ccsid.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 Patrick Monnerat, D+H <patrick.monnerat@dh.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms,
+ * with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ *   Redistributions of source code must retain the above
+ *   copyright notice, this list of conditions and the
+ *   following disclaimer.
+ *
+ *   Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials
+ *   provided with the distribution.
+ *
+ *   Neither the name of the copyright holder nor the names
+ *   of any other contributors may be used to endorse or
+ *   promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+/* CCSID conversion support. */
+
+#ifndef LIBSSH2_CCSID_H_
+#define LIBSSH2_CCSID_H_
+
+#include "libssh2.h"
+
+typedef struct _libssh2_string_cache    libssh2_string_cache;
+
+
+LIBSSH2_API char *
+libssh2_from_ccsid(LIBSSH2_SESSION *session, libssh2_string_cache **cache,
+                   unsigned short ccsid, const char *string, ssize_t inlen,
+                   size_t *outlen);
+LIBSSH2_API char *
+libssh2_to_ccsid(LIBSSH2_SESSION *session, libssh2_string_cache **cache,
+                 unsigned short ccsid, const char *string, ssize_t inlen,
+                 size_t *outlen);
+LIBSSH2_API void
+libssh2_release_string_cache(LIBSSH2_SESSION *session,
+                             libssh2_string_cache **cache);
+
+#endif
+
+/* vim: set expandtab ts=4 sw=4: */
diff --git a/os400/libssh2rpg/libssh2_ccsid.rpgle.in b/os400/libssh2rpg/libssh2_ccsid.rpgle.in
new file mode 100644
index 0000000..bdc6f93
--- /dev/null
+++ b/os400/libssh2rpg/libssh2_ccsid.rpgle.in
@@ -0,0 +1,69 @@
+      * Copyright (c) 2015 Patrick Monnerat, D+H <patrick.monnerat@dh.com>
+      * All rights reserved.
+      *
+      * Redistribution and use in source and binary forms,
+      * with or without modification, are permitted provided
+      * that the following conditions are met:
+      *
+      *   Redistributions of source code must retain the above
+      *   copyright notice, this list of conditions and the
+      *   following disclaimer.
+      *
+      *   Redistributions in binary form must reproduce the above
+      *   copyright notice, this list of conditions and the following
+      *   disclaimer in the documentation and/or other materials
+      *   provided with the distribution.
+      *
+      *   Neither the name of the copyright holder nor the names
+      *   of any other contributors may be used to endorse or
+      *   promote products derived from this software without
+      *   specific prior written permission.
+      *
+      * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+      * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+      * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+      * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+      * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+      * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+      * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+      * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+      * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+      * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+      * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+      * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+      * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+      * OF SUCH DAMAGE.
+
+      /if not defined(LIBSSH2_CCSID_H_)
+      /define LIBSSH2_CCSID_H_
+
+      /include "libssh2rpg/libssh2"
+
+     d libssh2_from_ccsid...
+     d                 pr              *   extproc('libssh2_from_ccsid')        char *
+     d  session                        *   value                                LIBSSH2_SESSION *
+     d  cache                          *   value                                libssh2_string_cache
+     d                                                                          *(*)
+     d  ccsid                              value like(libssh2_Cushort)
+     d  string                         *   value options(*string)               const char *
+     d  inlen                              value like(libssh2_Cssize_t)
+     d  outlen                             like(libssh2_Csize_t) options(*omit)
+
+     d libssh2_to_ccsid...
+     d                 pr              *   extproc('libssh2_to_ccsid')          char *
+     d  session                        *   value                                LIBSSH2_SESSION *
+     d  cache                          *                                        libssh2_string_cache
+     d                                                                          *(*)
+     d  ccsid                              value like(libssh2_Cushort)
+     d  string                         *   value options(*string)               const char *
+     d  inlen                              value like(libssh2_Cssize_t)
+     d  outlen                             like(libssh2_Csize_t) options(*omit)
+
+     d libssh2_release_string_cache...
+     d                 pr                  extproc(
+     d                                     'libssh2_release_string_cache')
+     d  session                        *   value                                LIBSSH2_SESSION *
+     d  cache                          *                                        libssh2_string_cache
+     d                                                                          *(*)
+
+      /endif                                                                    LIBSSH2_CCSID_H_
diff --git a/os400/make-include.sh b/os400/make-include.sh
index d44980f..2cb7b72 100644
--- a/os400/make-include.sh
+++ b/os400/make-include.sh
@@ -44,7 +44,7 @@
 
 #       Copy the header files.
 
-for HFILE in *.h
+for HFILE in *.h "${TOPDIR}/os400/libssh2_ccsid.h"
 do      DEST="${SRCPF}/`db2_name \"${HFILE}\"`.MBR"
 
         if action_needed "${DEST}" "${HFILE}"
diff --git a/os400/make-src.sh b/os400/make-src.sh
index dd7d364..2e9a3c4 100644
--- a/os400/make-src.sh
+++ b/os400/make-src.sh
@@ -97,7 +97,8 @@
 
 INCLUDES="'`pwd`'"
 
-for SRC in "${SCRIPTDIR}/os400sys.c" ${CSOURCES} ${CRYPTO_CSOURCES} macros.c
+for SRC in "${TOPDIR}/os400/os400sys.c" "${TOPDIR}/os400/ccsid.c"       \
+           ${CSOURCES} ${CRYPTO_CSOURCES} macros.c
 do      MODULE=`db2_name "${SRC}"`
         make_module "${MODULE}" "${SRC}"
 done
@@ -142,7 +143,8 @@
 
 #       Gather the list of symbols to export.
 
-EXPORTS=`cat "${TOPDIR}"/include/*.h "${TOPDIR}"/os400/macros.h         |
+EXPORTS=`cat "${TOPDIR}"/include/*.h "${TOPDIR}/os400/macros.h"         \
+             "${TOPDIR}/os400/libssh2_ccsid.h"                          |
          extproto                                                       |
          sed -e 's/(.*//;s/[^A-Za-z0-9_]/ /g;s/ *$//;s/^.* //'`
 
@@ -178,7 +180,7 @@
         CMD="${CMD} BNDDIR(${TARGETLIB}/${STATBNDDIR}"
         if [ "${WITH_ZLIB}" != 0 ]
         then    CMD="${CMD} ${ZLIB_LIB}/${ZLIB_BNDDIR}"
-		liblist -a "${ZLIB_LIB}"
+                liblist -a "${ZLIB_LIB}"
         fi
         CMD="${CMD})"
         CMD="${CMD} BNDSRVPGM(QADRTTS)"