#!/usr/bin/python3

# This script has been written by Trevor Perrin originally.
# Ported to python3 by Ralf Senderek, October 2018
# Modified to use the new Buffer Protocol (PyObject_GetBuffer), November 2023
# Transformed comments for Javadoc and added method comments, March 2026
# Remove deprecated functions from cryptlib.h, April 2026

import sys
import os
import stat
import re

print ("\nUsing python3 to compile files for java, python2, python3 and net\n")

#Helper Functions
#==========================================================================

def parseEnumContents(enumContents, nameSpace, debugString):
    enumPattern = re.compile(r"(CRYPT_[\w_]*)\s*(=.*?)?(\Z|,|(?=/))", re.DOTALL)     # %1 [= %2][,]
    commentPattern = re.compile(r"\s*/\*(.*)\*/")                                    # /* %1 */
    counter = 0     #Keep track of the implicit enum value
    enumTuples = [] #Build a list of (key, val, comment) tuples, one for each enum value

    #Find the next "enum value"
    enumMatch = enumPattern.search(enumContents)
    while enumMatch:

        #Extract the key and RHS value
        rawKey, rawVal = enumMatch.groups()[:-1]

        key = rawKey.rstrip()
        if not key.startswith("CRYPT_"):
            raise "enum'd value doesn't start with CRYPT_ "+key
        key = key.replace("CRYPT_", "")

        #Evaluate the RHS (if it exists) to get the value
        if rawVal:
            rhs = rawVal[1:].strip().replace("CRYPT_", "")
            val = eval(rhs, nameSpace)
        #Otherwise the value is the implicit counter
        else:
            val = counter

        #Extract the comment
        commentMatch = commentPattern.match(enumContents, enumMatch.end())
        if commentMatch:
            rawComment = commentMatch.group(1)
            comment = rawComment.strip()
        else:
            comment = ""

        #Collect all the parsed values into a tuple
        enumTuple = (key, str(val), comment)

        #if comment:
        #    print (debugString+ ":" + " Parsed Element %s = %s /* %s */" % enumTuple)
        #else:
        #    print (debugString+ ":" + " Parsed Element %s = %s" % enumTuple[:-1])
        #input()

        nameSpace[key] = val    #Add new enum value into namespace
        counter = val + 1       #Increment implicit enum value

        #Accumulate the parsed (key, val, comment) tuples
        enumTuples.append(enumTuple)

        #Get next enum value
        enumMatch = enumPattern.search(enumContents, enumMatch.end())

    return enumTuples

#Parses a string of function parameters into a list of ParamStruct
class ParamStruct:
    def __init__(self):
        self.type = ""
        self.isPtr = 0
        self.isOut = 0
        self.category = ""
        self.name = ""
        self.rawIndex = 0 # The index of this param in the initial, raw cryptlib header
    def __str__(self):
        return str( (self.type, self.isPtr, self.category, self.name, self.rawIndex) )

def parseFunctionParams(functionParams):
    paramStructs = []
    functionParams = functionParams.replace("\n", "") #Make one big line
    functionParamsList = [e.strip() for e in functionParams.split(",")] #Break into a list of params
    index = 0
    for e in functionParamsList:
        pieces = e.split(" ")   #Separate each param into component pieces, ie ["C_IN", "CRYPT_ALGO_TYPE", "cryptAlgo"]
        p = ParamStruct()
        if len(pieces)==1 and pieces[0]=="void":
            continue
        elif len(pieces)==3:
            if pieces[0] == "C_OUT":
                p.isOut = 1
            if pieces[0] == "C_OUT_OPT":
                p.isOut = 1
            if pieces[1] == "C_STR":
                p.type = "char"
                p.name = pieces[2]
                p.isPtr = 1
            else:
                p.type = pieces[1]
                p.name = pieces[2]
                p.isPtr = 0
        elif len(pieces)==4:    #Ie ["C_OUT", "CRYPT_CONTEXT", "C_PTR", "cryptContext"]
            if pieces[0] == "C_OUT":
                p.isOut = 1
            if pieces[0] == "C_OUT_OPT":
                p.isOut = 1
            p.type = pieces[1]
            if pieces[2] == "C_PTR":
                p.isPtr = 1
            else:
                raise "Expecting C_PTR in parseFunctionParams"
            p.name = pieces[3]
        else:
            raise "parsing error in parseFunctionParams"
        if p.type in enumTypes:
            p.category = "enumType"
        elif p.type in intTypes:
            p.category = "intType"
        elif p.type in structTypes:
            p.category = "structType"
        elif p.type in rawTypes:
            p.category = "rawType"
        else:
            raise "unknown type error is parseFunctionParams"
        p.rawIndex = index
        index += 1
        paramStructs.append(p)
    #for p in paramStructs:
    #    print (p.__dict__)
    #input()
    return paramStructs

#Takes a string containing a JNI function parameters prototype list, and a list of ParamStructs
#And injects names into the string and returns it
def expandFunctionPrototype(functionPrototype, newParamStructs):
    functionPrototype = functionPrototype.replace("\n", "") #Make one big line
    paramPrototypesList = [e.strip() for e in functionPrototype.split(",")] #Break into a list of params
    paramNamesList = ["env", "cryptClass"] + [e.name for e in newParamStructs]
    newFunctionParams = ""
    for (p1, p2) in zip(paramPrototypesList, paramNamesList):
        newFunctionParams += p1 + " " + p2 + ", "
    newFunctionParams = newFunctionParams[:-2]
    return newFunctionParams






#Main
#=========================================================================
#Execution starts here...
#=========================================================================
if len(sys.argv) != 4:
    print ("cryptlibConverter.py <inFile> <outDir> <language>")
    sys.exit()

inFile = sys.argv[1]
outDir = sys.argv[2]
language = sys.argv[3]

if not os.path.exists(outDir):
    print ("Making output directory...")
    os.mkdir(outDir)

if not language in ("java", "python", "net"):
    print ("only java, python, and net are supported!")
    sys.exit()

if language == "java":

    # try to read the external function comment file for Javadoc (March 2026)
    FunctionComments = {}
    try:
         FunctionCommentFile = os.path.join(outDir, "function-comments.py3")
         F = open(FunctionCommentFile, "r")
         FuncDictString = F.read()
         F.close()
         FunctionComments = eval(FuncDictString)
         print("Found " + str(len(FunctionComments.keys())) + " function comments")
    except:
         print ("Error: cannot read function comments, missing file " + FunctionCommentFile)

    #ENUM IDIOM
    #typedefEnumTemplate = "public static class %(typedefName)s\n{public int getValue(){return m_value;}private %(typedefName)s(int value){m_value = value;}int m_value;}"
    #typedefEnumElementTemplate = "public static final %(typedefName)s %(name)-NPADs = new %(typedefName)s(%(value)-VPADs);"
    #ENUMs as ints
    typedefEnumTemplate = "// %(typedefName)s"
    typedefEnumElementTemplate = "public static final int %(name)-NPADs = %(value)-VPADs;"
    typedefEnumElementTemplateComment = " /** * %(comment)s */\n"+ typedefEnumElementTemplate
    simpleEnumElementTemplate = "public static final int %(name)-NPADs = %(value)-VPADs;"
    simpleEnumElementTemplateComment = " /** * %(comment)s */\n" + simpleEnumElementTemplate 
    defineNPad = "40"
    defineVPad = "4"
    defineTemplate = simpleEnumElementTemplate
    defineTemplateComment = simpleEnumElementTemplateComment
    exceptionPrefix = """
package cryptlib;

public class CryptException extends Exception
{
    private int m_status;
    private String m_message;
    public CryptException(int status)
    {
        m_status = status;
        String prefix = Integer.toString(status) + ": ";

"""
    exceptionPostfix = """\
        m_message = prefix + "Unknown Exception ?!?!";
    }

    public int getStatus()
    {
        return m_status;
    }

    public String getMessage()
    {
        return m_message;
    }
};"""
    exceptionTemplate = """\
        if (m_status == crypt.%(name)s) {
            m_message = prefix + "%(comment)s";
            return; }
"""
    cryptQueryInfoString = """
package cryptlib;

public class CRYPT_QUERY_INFO
{
    public String algoName;
    public int blockSize;
    public int minKeySize;
    public int keySize;
    public int maxKeySize;

    public CRYPT_QUERY_INFO(String newAlgoName, int newBlockSize, int newMinKeySize, int newKeySize, int newMaxKeySize)
    {
        algoName = newAlgoName;
        blockSize = newBlockSize;
        minKeySize = newMinKeySize;
        keySize = newKeySize;
        maxKeySize = newMaxKeySize;
    }
};"""
    cryptObjectInfoString = """
package cryptlib;

public class CRYPT_OBJECT_INFO
{
    public int objectType;
    public int cryptAlgo;
    public int cryptMode;
    public int hashAlgo;
    public byte[] salt;

    public CRYPT_OBJECT_INFO(int newObjectType, int newCryptAlgo, int newCryptMode, int newHashAlgo, byte[] newSalt)
    {
        objectType = newObjectType;
        cryptAlgo = newCryptAlgo;
        cryptMode = newCryptMode;
        hashAlgo = newHashAlgo;
        salt = newSalt;
    }
};"""
    addFunctionWrappers = 1
    wholeFunctionDeclaration = None
    functionDeclaration = "public static native "
    returnIntDeclaration = "int "
    returnVoidDeclaration = "void "
    paramsPrefix = "("
    paramsPostfix = ") throws CryptException;"
    paramWhiteSpace = "\t\t\t\t\t\t"
    paramVoidPtrTemplate = "java.nio.ByteBuffer %(name)s,\n"
    addFunctionAlternate = 1
    paramVoidPtrAlternate = ("java.nio.ByteBuffer", "byte[]")
    paramCharPtrTemplate = "String %(name)s,\n"
    paramIntTemplate = "int %(name)s,\n"
    paramIntTypeTemplate = "int %(name)s, // %(type)s\n"
    #wrapperLengthTemplate = "%s.capacity(), "
    #wrapperStringLengthTemplate = "%s.length(), "
    wrapperLengthTemplate = "%(1)s == null ? 0 : %(1)s.capacity(), "
    wrapperStringLengthTemplate = "%(1)s == null ? 0 : %(1)s.getBytes().length, "
    wrapperStringReplace = ("java.nio.ByteBuffer", "String")
    wrapperStringTemplate = '%(1)s == null ? null : %(1)s.getBytes()'
    #ENUM IDIOM
    #paramEnumTypeTemplate = "%(type)s %(name)s,\n"
    #ENUMs as ints
    paramEnumTypeTemplate = "int %(name)s, // %(type)s\n"
    commentPrefix = "//"
    classComment = "\t/** * <h2>This class represents all data structures, constant values and methods\n\t * that <a href=\"https://github.com/cryptlib/cryptlib\">cryptlib</a> makes available\n\t * via the nio interface.</h2>\n\t * <h3>Author:  Peter Gutmann</h3>\n\t */"
    classPrefix = "package cryptlib;\n\nimport java.nio.*;\n\n" + classComment + "\npublic class crypt\n{\n"
    classPostfix = "\n};"
    sFuncs = None
    sInts = None
elif language == "python":
    typedefEnumTemplate = ""
    #typedefEnumElementTemplate = "%(name)-NPADs = %(value)-VPADs"
    typedefEnumElementTemplate = """\

    v = Py_BuildValue("i", CRYPT_%(name)s);
    PyDict_SetItemString(moduleDict, "CRYPT_%(name)s", v);
    Py_DECREF(v);"""
    typedefEnumElementTemplateComment = typedefEnumElementTemplate + " /* %(comment)s */"
    simpleEnumElementTemplate = typedefEnumElementTemplate
    simpleEnumElementTemplateComment = typedefEnumElementTemplateComment
    defineNPad = "40"
    defineVPad = "4"
    defineTemplate = typedefEnumElementTemplate
    defineTemplateComment = typedefEnumElementTemplateComment
    exceptionPrefix = ""
    exceptionPostfix = ""
    exceptionTemplate = """\
    else if (status == CRYPT_%(name)s)
        o = Py_BuildValue("(is)", CRYPT_%(name)s, "%(comment)s");
"""
    addFunctionWrappers = 0
    wholeFunctionDeclaration = """\
static PyObject* python_crypt%s(PyObject* self, PyObject* args)
{
}
"""
    moduleFunctionEntry = "\t{ \"crypt%s\", python_crypt%s, METH_VARARGS }, "
    pyBeforeExceptions = r"""
#include <Python.h>
#include "../cryptlib.h"


static PyObject* cryptHandleClass;
static PyObject* cryptQueryInfoClass;
static PyObject* cryptObjectInfoClass;
static PyObject *CryptException;


static int getPointerWrite(PyObject* objPtr, unsigned char** bytesPtrPtr, int* lengthPtr)
{

    if (objPtr == Py_None)
    {
        *bytesPtrPtr = NULL;
        *lengthPtr = 0;
        return 1;
    }

    Py_buffer view;
    /*Check if PyObject is a array writable bytearray */
    if (PyObject_GetBuffer(objPtr, &view, PyBUF_WRITABLE) == -1)
         return 0;
    *lengthPtr = view.len;
    *bytesPtrPtr = view.buf;
    PyBuffer_Release(&view);

    return 1;
}

static int getPointerRead(PyObject* objPtr, unsigned char** bytesPtrPtr, int* lengthPtr)
{

    if (objPtr == Py_None)
    {
        *bytesPtrPtr = NULL;
        *lengthPtr = 0;
        return 1;
    }

    Py_buffer view;
    /*Check if PyObject is a simple buffer */
    if (PyObject_GetBuffer(objPtr, &view, PyBUF_FORMAT) == -1)
         return 0;
    *lengthPtr = view.len;
    *bytesPtrPtr = view.buf;
    PyBuffer_Release(&view);
    return 1;
}

static int getPointerWriteCheckIndices(PyObject* objPtr, unsigned char** bytesPtrPtr, int* lengthPtr)
{
    int checkLength = *lengthPtr;

    if (getPointerWrite(objPtr, bytesPtrPtr, lengthPtr) == 0)
        return 0;

    //If sequence is non-NULL and too short...
    if (*bytesPtrPtr && (*lengthPtr < checkLength))
    {
        PyErr_SetString(PyExc_IndexError, "A sequence passed to cryptlib was too small");
        return 0;
    }
    return 1;
}

static int getPointerReadString(PyObject* objPtr, char** charPtrPtr)
{

    Py_ssize_t length = 0;
    char* newPtr = NULL;

    if (objPtr == Py_None)
    {
        *charPtrPtr = NULL;
        return 1;
    }

    Py_buffer view;
    /*Check if PyObject is a simple readable buffer */
    if (PyObject_GetBuffer(objPtr, &view, PyBUF_FORMAT) == -1)
         return 0;
    length = view.len;
    *charPtrPtr = view.buf;
    PyBuffer_Release(&view);

    newPtr = malloc(length+1);
    if (newPtr == NULL)
    {
        PyErr_NoMemory();
        return 0;
    }
    memcpy(newPtr, *charPtrPtr, length);
    newPtr[length] = 0;
    *charPtrPtr = newPtr;
    return 1;
}

static void releasePointer(PyObject* objPtr, unsigned char* bytesPtr)
{
    /* nothing to release */
}

static void releasePointerString(PyObject* objPtr, char* charPtr)
{
    free(charPtr);
}

static PyObject* processStatus(int status)
{
    PyObject* o = NULL;

    /* If an error has already occurred, ignore the status and just fall through */
    if (PyErr_Occurred())
        return(NULL);

    if (status >= CRYPT_OK)
        return(Py_BuildValue(""));
"""
    pyBeforeFuncs = """\
    PyErr_SetObject(CryptException, o);
    Py_DECREF(o);
    return(NULL);
}

static int processStatusBool(int status)
{
    PyObject* o = processStatus(status);
    if (o == NULL)
        return(0);
    else
    {
        Py_DECREF(o);
        return(1);
    }
}

static PyObject* processStatusReturnInt(int status, int returnValue)
{
    PyObject* o = processStatus(status);
    if (o == NULL)
        return(0);
    else
    {
        Py_DECREF(o);
        o = Py_BuildValue("i", returnValue);
        return(o);
    }
}

static PyObject* processStatusReturnCryptHandle(int status, int returnValue)
{
    PyObject* o2;
    PyObject* o = processStatus(status);

    if (o == NULL)
        return(0);
    else
    {
        Py_DECREF(o);
        o2  = Py_BuildValue("(i)", returnValue);
        o = PyObject_CallObject(cryptHandleClass, o2);
        Py_DECREF(o2);
        return(o);
    }
}

static PyObject* processStatusReturnCryptQueryInfo(int status, CRYPT_QUERY_INFO returnValue)
{
    PyObject* o2;
    PyObject* o = processStatus(status);

    if (o == NULL)
        return(0);
    else
    {
        Py_DECREF(o);
        o2  = Py_BuildValue("(siiii)", returnValue.algoName, returnValue.blockSize, returnValue.minKeySize, returnValue.keySize, returnValue.maxKeySize);
        o = PyObject_CallObject(cryptQueryInfoClass, o2);
        Py_DECREF(o2);
        return(o);
    }
}

static PyObject* processStatusReturnCryptObjectInfo(int status, CRYPT_OBJECT_INFO returnValue)
{
    PyObject* o2;
    PyObject* o = processStatus(status);

    if (o == NULL)
        return(0);
    else
    {
        Py_DECREF(o);
        o2  = Py_BuildValue("(iiiis#)", returnValue.objectType, returnValue.cryptAlgo, returnValue.cryptMode, returnValue.hashAlgo, returnValue.salt, returnValue.saltSize);
        o = PyObject_CallObject(cryptObjectInfoClass, o2);
        Py_DECREF(o2);
        return(o);
    }
}
"""
    pyBeforeModuleFunctions = """

static PyMethodDef module_functions[] =
{
"""
    pyBeforeInts = r"""    {0, 0}
};

#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {
        PyModuleDef_HEAD_INIT,
        "cryptlib_py",
        NULL,
        -1,
        module_functions,
        NULL,
        NULL,
        NULL,
        NULL
};
#endif

PyMODINIT_FUNC
#if PY_MAJOR_VERSION >= 3
PyInit_cryptlib_py(void)
#else
initcryptlib_py(void)
#endif

{
    PyObject* module;
    #if PY_MAJOR_VERSION >= 3
    module = PyModule_Create(&moduledef);
    if (module == NULL){
        return NULL;
    }
    #else
    module = Py_InitModule("cryptlib_py", module_functions);
    #endif


    PyObject* moduleDict;

    PyObject* v = NULL;
    PyObject *globalsDict;

    moduleDict = PyModule_GetDict(module);

    CryptException = PyErr_NewException("cryptlib_py.CryptException", NULL, NULL);
    PyDict_SetItemString(moduleDict, "CryptException", CryptException);

    globalsDict = PyEval_GetGlobals();
    PyRun_String(
"from array import *\n\
import types\n\
class CryptHandle:\n\
    def __init__(self, value):\n\
        self.__dict__['value'] = value\n\
    def __int__(self):\n\
        return self.value\n\
    def __str__(self):\n\
        return str(self.value)\n\
    def __repr__(self):\n\
        return str(self.value)\n\
    def __getattr__(self, name):\n\
        name = name.upper()\n\
        if not name.startswith('CRYPT_'):\n\
            name = 'CRYPT_' + name\n\
        nameVal = globals()[name]\n\
        try:\n\
            return cryptGetAttribute(self, nameVal)\n\
        except CryptException:\n\
            length = cryptGetAttributeString(self, nameVal, None)\n\
            buf = array('c', '0' * length)\n\
            length = cryptGetAttributeString(self, nameVal, buf)\n\
            return ''.join(buf[:length])\n\
    def __setattr__(self, name, value):\n\
        name = name.upper()\n\
        if not name.startswith('CRYPT_'):\n\
            name = 'CRYPT_' + name\n\
        nameVal = globals()[name]\n\
        if isinstance(value, types.IntType) or isinstance(value, CryptHandle):\n\
            cryptSetAttribute(self, nameVal, value)\n\
        else:\n\
            cryptSetAttributeString(self, nameVal, value)\n",
    Py_file_input, globalsDict, moduleDict);


    PyRun_String(
"class CRYPT_QUERY_INFO:\n\
    def __init__(self, algoName, blockSize, minKeySize, keySize, maxKeySize):\n\
        self.algoName = algoName\n\
        self.blockSize = blockSize\n\
        self.minKeySize = minKeySize\n\
        self.keySize = keySize\n\
        self.maxKeySize = maxKeySize\n",
    Py_file_input, globalsDict, moduleDict);

    PyRun_String(
"class CRYPT_OBJECT_INFO:\n\
    def __init__(self, objectType, cryptAlgo, cryptMode, hashAlgo, salt):\n\
        self.objectType = objectType\n\
        self.cryptAlgo = cryptAlgo\n\
        self.cryptMode = cryptMode\n\
        self.hashAlgo = hashAlgo\n\
        self.salt = salt\n",
    Py_file_input, globalsDict, moduleDict);

    cryptHandleClass = PyMapping_GetItemString(moduleDict, "CryptHandle");
    cryptQueryInfoClass = PyMapping_GetItemString(moduleDict, "CRYPT_QUERY_INFO");
    cryptObjectInfoClass = PyMapping_GetItemString(moduleDict, "CRYPT_OBJECT_INFO");

    Py_DECREF(globalsDict);

"""
    pyAfterInts = """
#if PY_MAJOR_VERSION >= 3
    return module;
#endif
}"""

    paramCharPtrTemplate = "%(name)s, # string\n"
    paramIntTemplate = "%(name)s, # integer\n"
    paramIntTypeTemplate = "%(name)s, # %(type)s\n"
    paramEnumTypeTemplate = "%(name)s, # %(type)s\n"
    commentPrefix = "# "  #pad to 2-characters for formatting consistency
    classPrefix = "class crypt:\n"
    classPostfix= ""
    outFile = os.path.join(outDir, "cryptlib.py")
    sFuncs = "\n"
    sInts = "\n"
    sModFuncs = ""
    setupPy = r"""#!/usr/bin/env python

from setuptools import setup, Extension
import sys

if sys.platform == "win32":
    ext = Extension("cryptlib_py",
                    sources=["python.c"],
                    library_dirs=['../binaries'],
                    libraries=['cl32'])
else:
    ext = Extension("cryptlib_py",
                    sources=["python.c"],
                    library_dirs=['..'],
                    libraries=['cl'])

setup(name="cryptlib_py", ext_modules=[ext])
"""

elif language == "net":
    typedefEnumTemplate = "// %(typedefName)s"
    typedefEnumElementTemplate = "public const int %(name)-NPADs = %(value)-VPADs;"

    typedefEnumElementTemplateComment = typedefEnumElementTemplate + " // %(comment)s"
    simpleEnumElementTemplate = "public const int %(name)-NPADs = %(value)-VPADs;"
    simpleEnumElementTemplateComment = simpleEnumElementTemplate + " // %(comment)s"
    defineNPad = "40"
    defineVPad = "4"
    defineTemplate = simpleEnumElementTemplate
    defineTemplateComment = simpleEnumElementTemplateComment
    exceptionPrefix = """
[StructLayout(LayoutKind.Sequential, Pack=0, CharSet=CharSet.Ansi)]
public class CRYPT_QUERY_INFO
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)]public String algoName;
    public int blockSize;
    public int minKeySize;
    public int keySize;
    public int maxKeySize;

    public CRYPT_QUERY_INFO(){}

    public CRYPT_QUERY_INFO(String newAlgoName, int newBlockSize, int newMinKeySize, int newKeySize, int newMaxKeySize)
    {
        algoName = newAlgoName;
        blockSize = newBlockSize;
        minKeySize = newMinKeySize;
        keySize = newKeySize;
        maxKeySize = newMaxKeySize;
    }
}

[StructLayout(LayoutKind.Sequential, Pack=0, CharSet=CharSet.Ansi)]
public class CRYPT_OBJECT_INFO
{
    public int objectType;
    public int cryptAlgo;
    public int cryptMode;
    public int hashAlgo;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=32)]public byte[] salt;
    public int saltSize;

    public CRYPT_OBJECT_INFO()
    {
        salt = new byte[64];
        saltSize = 64;
    }

    public CRYPT_OBJECT_INFO(int newObjectType, int newCryptAlgo, int newCryptMode, int newHashAlgo, byte[] newSalt)
    {
        objectType = newObjectType;
        cryptAlgo = newCryptAlgo;
        cryptMode = newCryptMode;
        hashAlgo = newHashAlgo;
    }
}

public class CryptException : ApplicationException
{
    public int Status { get { return (int)Data["Status"]; } }

    public int ExtraInfo { get { return (int)Data["ExtraInfo"]; } }

    public CryptException(int status)
        : base(convertMessage(status))
    {
        Data.Add("Status", status);
    }

    public CryptException(int status, int extra)
        : base(convertMessage(status))
    {
        Data.Add("Status", status);
        Data.Add("ExtraInfo", extra);
    }

    private static string convertMessage(int status)
    {
        String prefix = Convert.ToString(status) + ": ";
        switch (status)
        {
"""
    exceptionPostfix = """\
            default: return prefix + "Unknown Exception ?!?!";
        }
    }
}"""
    exceptionTemplate = """\
		case crypt.%(name)s: return prefix + "%(comment)s";
"""
    addFunctionWrappers = 1
    wholeFunctionDeclaration = None
    functionDeclaration = "public static "
    returnIntDeclaration = "int "
    returnVoidDeclaration = "void "
    paramsPrefix = "("
    paramsPostfix = ");"
    paramWhiteSpace = "\t\t\t\t\t\t"
    paramVoidPtrTemplate = "byte[] %(name)s,\n"
    addFunctionAlternate = 0
    paramCharPtrTemplate = "String %(name)s,\n"
    paramIntTemplate = "int %(name)s,\n"
    paramIntTypeTemplate = "int %(name)s, // %(type)s\n"
    wrapperLengthTemplate = "%(1)s == null ? 0 : %(1)s.Length, "
    wrapperStringLengthTemplate = "%(1)s == null ? 0 : Encoding.ASCII.GetByteCount(%(1)s), "
    wrapperStringReplace = ("byte[]", "String")
    wrapperStringTemplate = "%(1)s == null ? null : Encoding.ASCII.GetBytes(%(1)s)"
    #ENUM IDIOM
    #paramEnumTypeTemplate = "%(type)s %(name)s,\n"
    #ENUMs as ints
    paramEnumTypeTemplate = "int %(name)s, // %(type)s\n"
    commentPrefix = "//"
    classPrefix = """using System;
using System.Runtime.InteropServices;
using System.Text;

namespace cryptlib
{

public class crypt
{
    """
    classPostfix = """
    /* Helper Functions */

    private static void processStatus(int status)
    {
        if (status < crypt.OK)
            throw new CryptException(status);
    }


    private static void processStatus(int status, int extraInfo)
    {
        if (status < crypt.OK)
            throw new CryptException(status, extraInfo);
    }

    private static void checkIndices(byte[] array, int sequenceOffset, int sequenceLength)
    {
        if (array == null)
        {
            if (sequenceOffset == 0)
                return;
            else
                throw new IndexOutOfRangeException();
        }

        int arrayLength = array.Length;

        if (sequenceOffset < 0 ||
            sequenceOffset >= arrayLength ||
            sequenceOffset + sequenceLength > arrayLength)
            throw new IndexOutOfRangeException();
    }

    private static void getPointer(byte[] buffer, int bufferOffset, ref GCHandle bufferHandle, ref IntPtr bufferPtr)
    {
        if (buffer == null)
            return;
        bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        bufferPtr = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, bufferOffset);
    }

    private static void releasePointer(GCHandle bufferHandle)
    {
        if (bufferHandle.IsAllocated)
            bufferHandle.Free();
    }
}
"""
    sFuncs = None
    sInts = None



s = open(inFile).read()
inFileTabSize = 4

# remove all deprecated functions, (April 2026)
# find the code block starting with C_DECLSPEC_DEPRECATED and ending in C_ATTRIBUTE_DEPRECATED;
i = 0
begin = 0
while i < len(s):
     begin = s.find("C_DECLSPEC_DEPRECATED",begin)
     end = s.find("C_ATTRIBUTE_DEPRECATED",begin)
     if (begin != -1) and (end != -1) :
          # remove this deprecated code block from s
          s = s[:begin] + s[end + 23 :]
     i += 100

#Global variables
#These accumulate information about the types in the input file
#----------------------------------------------------------------
nameSpace = {}  #Accumulate enum'd values here and use them to eval() the RHS of new ones
enumTypes = []  #Accumulate typedef'd enum types here
intTypes = []   #Accumulate typedef'd int types here
structTypes = []#Accumulate typedef'd struct types here
rawTypes = ["char", "int", "void"]
functionNames = [] #Accumulate function names here
rawParamStructsDict = {} #Accumulate function name -> list of ParamStructs (of original c code)
newParamStructsDict = {} #Accumulate function name -> list of ParamStructs (of new java/python code)
newReturnStructsDict = {}  #Accumulate function name -> ParamStruct of return parameter (of new java/python code)
newDiscardedStructsDict = {} #Accumulate function name -> ParamStruct of discarded parameter
lengthIndicesDict = {} #Accumulate function name -> indices in newParamStructs of lengths (used for python)
offsetIndicesDict= {} #Accumulate function name -> indices in newParamStructs of offsets (used for python)
errors = {}     #Dictionary mapping return values to exception objects

print ("Parsing input file and generating %s files..." % language)

#Removing enclosing include guard
#---------------------------------
s = re.search(r"#ifndef _CRYPTLIB_DEFINED\n(.*)\n#endif", s, re.DOTALL).group(1)

#Ignore anything before "#define C_INOUT..."
#--------------------------------------------
#s = s[re.search(r"#define C_INOUT.*?\n", s, re.DOTALL).end() : ]
s = s[re.search(r"\/\* END_OF_PREAMBLE.*?\n", s, re.DOTALL).end() : ]

#Remove all conditionals
#------------------------
while 1:
    endifMatch = re.search(r"#endif.*?\n", s, re.DOTALL)
    if endifMatch == None:
        break
    ifdefIndex = s.rfind("#if", 0, endifMatch.start())
    s = s[ : ifdefIndex] + s[endifMatch.end() : ]
s = re.sub(r'\n\s*/\*.*\*/\s*\n', lambda match: '' if match.group(0).find('CRYPT_') >= 0 else match.group(0), s)

#Delete lines used for extended function and function-parameter checking
#like C_CHECK_RETVAL C_NONNULL_ARG( ( 3 ) ) \
#--------------------------------------------
functionParamterPattern = re.compile(r"((C_CHECK_RETVAL|C_NONNULL_ARG\s*\(\s*\([ \t0-9,]+\s*\)\s*\))\s+(\\\n)?)", re.DOTALL)
while 1:
	deleteExtended = functionParamterPattern.search(s)
	if deleteExtended == None:
		break
	s = s[ : deleteExtended.start(1) ] + s[ deleteExtended.end(1) : ]


#Replace typedef enums
#----------------------
typedefEnumPattern = re.compile(r"typedef[ \t]+enum[ \t]+{([^}]*)}\s*(\w*);", re.DOTALL)   # typedef enum { %1 } %2;

#Find the next typedef enum
typedefEnumMatch = typedefEnumPattern.search(s)
while typedefEnumMatch:

    #Extract its contents and type name
    enumContents, typedefName = typedefEnumMatch.groups()
    enumTypes.append(typedefName)

    #Parse its contents
    enumTuples = parseEnumContents(enumContents, nameSpace, "typedef")

    #Determine the length to pad names to
    namePad = str( max( [len(e[0]) for e in enumTuples] ) )
    valuePad = str( max( [len(e[1]) for e in enumTuples] ) )

    #Construct its output equivalent from language-specific string templates
    newTypedefEnum = typedefEnumTemplate % vars()
    for enumTuple in enumTuples:
        name, value, comment = enumTuple

	# this is the point at which ENUM comments can be cleaned or updated (modified March 2026)
	# needed for python too
        pos = comment.find("*/")
        if pos != -1:
            comment = comment[:pos]
        if language == "java":
            if ("******" in comment) :
                comment = comment.replace("*","") [6:]
            # if comment is empty add a dummy comment for JavaDoc
            if not comment:
                comment = "  -  "
        # end modification

        if not comment:
            paddedTemplate = typedefEnumElementTemplate.replace("NPAD", namePad).replace("VPAD", valuePad)
            newEnum = paddedTemplate % vars()
        else:
            paddedTemplate = typedefEnumElementTemplateComment.replace("NPAD", namePad).replace("VPAD", valuePad)
            newEnum = paddedTemplate % vars()
        if newTypedefEnum:
            newTypedefEnum += "\n"  #Don't always add this or we'll get extraneous newlines in python
        newTypedefEnum += newEnum

    #print ("Output Equivalent of Typedef Enum====\n", newTypedefEnum)
    #input()

    if sInts:
        sInts += newTypedefEnum + "\n"

    #Substitute the output equivalent for the input
    s = s[ : typedefEnumMatch.start()] + newTypedefEnum + s[typedefEnumMatch.end() : ]

    #Get next typedef
    typedefEnumMatch = typedefEnumPattern.search(s, typedefEnumMatch.start() + len(newTypedefEnum))
#print ("ENUMTYPES:\n",enumTypes)

#Replace simple enums
#---------------------
#This works on the theory that we've already replaced all typedef enums with
#something that doesn't use the word "enum", so any remaining occurrences of
#enum will belong to simple enums.

simpleEnumPattern = re.compile(r"enum[ \t]+{([^}]*)};", re.DOTALL)            # enum { %1 };

#Find the next simple enum
simpleEnumMatch = simpleEnumPattern.search(s)
while simpleEnumMatch:

    #Extract its contents
    enumContents = simpleEnumMatch.group(1)

    #Parse its contents
    enumTuples = parseEnumContents(enumContents, nameSpace, "simple")

    #Determine the length to pad names to
    namePad = str( max( [len(e[0]) for e in enumTuples] ) )
    valuePad = str( max( [len(e[1]) for e in enumTuples] ) )

    #Construct its output equivalent from language-specific string templates
    newSimpleEnum = ""
    for enumTuple in enumTuples:
        name, value, comment = enumTuple
        if not comment:
            paddedTemplate = simpleEnumElementTemplate.replace("NPAD", namePad).replace("VPAD", valuePad)
            newEnum = paddedTemplate % vars()
        else:
            paddedTemplate = simpleEnumElementTemplateComment.replace("NPAD", namePad).replace("VPAD", valuePad)
            newEnum = paddedTemplate % vars()
        if newSimpleEnum:
            newSimpleEnum += "\n"   #Don't always add this or we'll get extraneous newlines in python
        newSimpleEnum += newEnum

    #print ("Output Equivalent of Simple Enum====\n", newSimpleEnum)
    #input()

    if sInts:
        sInts += newSimpleEnum + "\n"

    #Substitute the output equivalent for the input
    s = s[ : simpleEnumMatch.start()] + newSimpleEnum + s[simpleEnumMatch.end() : ]

    #Get next typedef
    simpleEnumMatch = simpleEnumPattern.search(s, simpleEnumMatch.start() + len(newSimpleEnum))


#Replace #define'd constants
#----------------------------
definePattern = re.compile(r"#define[ \t]+(\w+)[ \t]+([-\w]+)[ \t]*(/\*.*\*/*)?")    # #define %1 %2 [/*%3*/]

exceptionString = exceptionPrefix

#Find the next #define
defineMatch = definePattern.search(s)
while defineMatch:

    #Parse its contents
    name, value, comment = defineMatch.groups()
    if not name.startswith("CRYPT_"):
        raise "name doesn't start with CRYPT_"+name
    name = name.replace("CRYPT_", "")
    
    # this is the point at which DEFINE comments can be cleaned or updated (modified March 2026)
    # if comment is empty add a dummy comment for JavaDoc
    if not comment and (language == "java"):
        # must be > 4 characters (see line 1034)
        comment = "  -  "
    # end modification

    #Construct its output equivalent from language-specific string templates
    if not comment:
        paddedTemplate = defineTemplate.replace("NPAD", defineNPad).replace("VPAD", defineVPad)
        newDefine =  paddedTemplate % vars()
    else:
        comment = comment[2:-2].strip()
        paddedTemplate = defineTemplateComment.replace("NPAD", defineNPad).replace("VPAD", defineVPad)
        newDefine = paddedTemplate % vars()

    #print ("define: " + newDefine)
    #input()

    if sInts:
        sInts += newDefine + "\n"

    #Substitute the output equivalent for the input
    s = s[ : defineMatch.start()] + newDefine + s[defineMatch.end() : ]

    #Append to exception string if error
    if name.startswith("ERROR_") or name.startswith("ENVELOPE_RESOURCE"):
        exceptionString += exceptionTemplate % vars()

    #Get next #define
    defineMatch = definePattern.search(s, defineMatch.start() + len(newDefine))

exceptionString += exceptionPostfix

#Replace #define'd constants with parenthesis
#--------------------------------------------
definePattern = re.compile(r"#define[ \t]+(\w+)[ \t]+\([ \t]*([-0-9]+)[ \)]*\)[ \t]*(/\*.*\*/*)?")    # #define %1 ( %2 ) [/*%3*/]

exceptionString = exceptionPrefix

#Find the next #define
defineMatch = definePattern.search(s)
while defineMatch:

    #Parse its contents
    name, value, comment = defineMatch.groups()
    if not name.startswith("CRYPT_"):
        raise "name doesn't start with CRYPT_"+name
    name = name.replace("CRYPT_", "")
    
    # this is the point at which DEFINE () comments can be cleaned or updated (modified March 2026)
    # if comment is empty add a dummy comment for JavaDoc
    if not comment and (language == "java"):
        # must be > 4 characters (see line 1085)
        comment = "  -  "
    # end modification
    
    #Construct its output equivalent from language-specific string templates
    if not comment:
        paddedTemplate = defineTemplate.replace("NPAD", defineNPad).replace("VPAD", defineVPad)
        newDefine =  paddedTemplate % vars()
    else:
        comment = comment[2:-2].strip()
        paddedTemplate = defineTemplateComment.replace("NPAD", defineNPad).replace("VPAD", defineVPad)
        newDefine = paddedTemplate % vars()

    #print ("define: " + newDefine)
    #input()

    if sInts:
        sInts += newDefine + "\n"

    #Substitute the output equivalent for the input
    s = s[ : defineMatch.start()] + newDefine + s[defineMatch.end() : ]

    #Append to exception string if error
    if name.startswith("ERROR_") or name.startswith("ENVELOPE_RESOURCE"):
        exceptionString += exceptionTemplate % vars()

    #Get next #define
    defineMatch = definePattern.search(s, defineMatch.start() + len(newDefine))

exceptionString += exceptionPostfix

#Comment out #define'd macros
#-----------------------------
definePattern = re.compile(r"#define[ \t]+[^(]+\([^\)]*\)([^\n]*\\\n)*[^\n]*")
defineMatch = definePattern.search(s)
while defineMatch:
    #print ("defined macros:", defineMatch.group())
    #input()
    define = defineMatch.group()
    newDefine = commentPrefix + "CRYPTLIBCONVERTER - NOT SUPPORTED:\n" + define
    newDefine = newDefine.replace("\n", "\n"+commentPrefix)
    s = s[ : defineMatch.start()] + newDefine + s[defineMatch.end() : ]
    defineMatch = definePattern.search(s, defineMatch.start() + len(newDefine))

#Comment out typedef integer types
#----------------------------------
typedefIntPattern = re.compile(r"typedef[ \t]+int[ \t]+([^ \t]*)[ \t]*;[ \t]*\n")
typedefIntMatch = typedefIntPattern.search(s)
while typedefIntMatch:
    typedefInt = typedefIntMatch.group()
    typedefIntName = typedefIntMatch.group(1)
    intTypes.append(typedefIntName)
    #print ("typedef int:", typedefInt)
    #input()
    newTypedefInt = commentPrefix + "CRYPTLIBCONVERTER - NOT NEEDED: " + typedefInt
    s = s[ : typedefIntMatch.start()] + newTypedefInt + s[typedefIntMatch.end() : ]
    typedefIntMatch = typedefIntPattern.search(s, typedefIntMatch.start() + len(newTypedefInt))
#print ("INTTYPES:\n",intTypes)

#Comment out typedef structs
#----------------------------
typedefStructPattern = re.compile(r"typedef[ \t]+struct[ \t]\{[^}]*}[ \t]*([^;]+);")
typedefStructMatch = typedefStructPattern.search(s)
while typedefStructMatch:
    typedefStruct = typedefStructMatch.group()
    typedefStructName = typedefStructMatch.group(1)
    structTypes.append(typedefStructName)
    #print ("typedef struct:", typedefStructName)
    #input()
    newTypedefStruct = commentPrefix + "CRYPTLIBCONVERTER - NOT SUPPORTED:\n" + typedefStruct
    newTypedefStruct = newTypedefStruct.replace("\n", "\n"+commentPrefix)
    s = s[ : typedefStructMatch.start()] + newTypedefStruct + s[typedefStructMatch.end() : ]
    typedefStructMatch = typedefStructPattern.search(s, typedefStructMatch.start() + len(newTypedefStruct))
#print ("STRUCTTYPES:\n",structTypes)
#input()

#Translate functions
#--------------------
functionPattern = re.compile(r"C_RET[ \t]+([^ \t]+)[ \t]*\(([^\)]*)\);", re.DOTALL)
functionMatch = functionPattern.search(s)
while functionMatch:
    function = functionMatch.group()
    functionName, functionParams = functionMatch.groups()
    if not functionName.startswith("crypt"):
        raise "name doesn't start with crypt"+functionName
    functionName = functionName[len("crypt") : ]
    functionNames.append(functionName)
    #print ("function:", functionName, functionParams)
    #input()

    #Parse its parameters
    paramStructs = parseFunctionParams(functionParams)

    #Add raw list of ParamStructs to dictionary
    #This will be used later, when generating the .c glue code
    rawParamStructsDict[functionName] = paramStructs

    # Since java and python don't have pass-by-reference, what we do is migrate the
    # output int parameter in certain functions into the return value.  If the function
    # creates or returns values such as integers or a (void*, int*) pair, we record
    # the indexes of the parameters to migrate.  We do this in functions like:
    # cryptCreate*()            # return handle of new object
    # cryptGetAttribute()       # return integer value of attribute
    # cryptGetAttributeString() # convert (void*, int*) to python string or java byte array
    # cryptExportKey()          # convert (void*, int*) "
    # cryptCreateSignature()    # convert (void*, int*)
    # cryptKeysetOpen()         # return handle
    # cryptGetPublicKey()       # return handle
    # cryptGetPrivateKey()      # return handle
    # cryptGetCertExtension()   # convert (void*, int) but DISCARD criticalFlag (the int* before)!
    # cryptImportCert()         # return handle
    # cryptExportCert()         # convert (void*, int)
    # cryptCAGetItem()          # return handle
    # cryptCACertManagement()   # return handle
    # cryptPushData()           # return integer (# of bytes copied)
    # cryptPopData()            # convert (void*, int*) even though they're not adjacent
    discardIntIndex = -1
    returnIntIndex = -1
    returnVoidPtrIndex = -1
    discardInLengthIndex1 = -1   # To discard input lengths, since python don't need these
    discardInLengthIndex2 = -1

    #Scan through looking for a void pointer preceded by "keyIDtype"
    #Convert it into a char*
    index = 1
    for p in paramStructs[1:]:
        p2 = paramStructs[index-1]
        if p.isPtr and p.type=="void" and p2.type=="CRYPT_KEYID_TYPE":
            p.type = "char"
        index += 1

    #Scan through looking for output int pointers (or structs)?!
    #If there's two, we will migrate the second occurrence to the return value
    #and discard the first so as to handle cryptGetCertExtension()
    index = 0
    for p in paramStructs:
        if p.isOut and p.isPtr and (p.category=="intType" or p.type=="int" or p.category=="structType"):
            if returnIntIndex != -1:
                if discardIntIndex != -1:
                    raise "Found two returned ints to discard?!"
                discardIntIndex = returnIntIndex
            returnIntIndex = index
        index += 1

    #Record the migrated return value's ParamStruct
    if returnIntIndex != -1:
        newReturnStructsDict[functionName] = paramStructs[returnIntIndex]

    #Return the discarded value's ParamStruct
    if discardIntIndex != -1:
        newDiscardedStructsDict[functionName] = paramStructs[discardIntIndex]

    #Copy the parsed parameters and remove those we're going to migrate or discard
    newParamStructs = [paramStructs[count]
                       for count in range(len(paramStructs))
                       if count not in (discardIntIndex, returnIntIndex)]

    #Indices of input offsets and lengths
    offsetIndices = []
    lengthIndices = []

    #Scan through looking for void pointer, add an int offset
    index = 0
    while 1:
        if index >= len(newParamStructs):
            break
        p = newParamStructs[index]
        if p.isPtr and p.type=="void":
            newp = ParamStruct()
            newp.type = "int"
            newp.isPtr = 0
            newp.isOut = 0
            newp.category = "rawType"
            newp.name = p.name+"Offset"
            newParamStructs = newParamStructs[:index+1] + [newp] + newParamStructs[index+1:]
            offsetIndices.append(index+1)
            if not p.isOut and len(newParamStructs)> index+2 and newParamStructs[index+2].type == "int":
                lengthIndices.append(index+2)
        index += 1

    #Add processed list of ParamStructs to dictionary
    #This will be used later, when generating the .c glue code
    newParamStructsDict[functionName] = newParamStructs
    lengthIndicesDict[functionName] = lengthIndices
    offsetIndicesDict[functionName] = offsetIndices

    #Used for Python
    if wholeFunctionDeclaration:
        newFunction = wholeFunctionDeclaration % functionName
        if sFuncs:
            sFuncs += newFunction + "\n"
        else: assert(0)
        sModFuncs += (moduleFunctionEntry % (functionName, functionName)) + "\n"
    else: #Java

        #Construct new function declaration from language-specific templates
        newFunction = functionDeclaration
        if returnIntIndex != -1:
            if newReturnStructsDict.get(functionName).category == "structType":
                newFunction += newReturnStructsDict.get(functionName).type + " "
            else:
                newFunction += returnIntDeclaration
        else:
            newFunction += returnVoidDeclaration
        newFunction += functionName + paramsPrefix
        if len(newParamStructs):
            newFunction += "\n" + paramWhiteSpace
        if offsetIndices or lengthIndices:
            newFunctionWrapper = newFunction
            newFunctionWrapperArgs = ""
        else:
            newFunctionWrapper = None
        #At this point I'm just getting lazy.  Basically, we automatically generate
        #String wrappers for function that take void* input parameters.  Encrypt and
        #Decrypt take void* in/out parameters.  We should be smart enough to detect
        #these and not generate wrappers for them, but instead, since we don't otherwise
        #differentiate between C_IN and C_INOUT, we just hardcodedly exclude these
        #from having String wrappers
        if offsetIndices and lengthIndices and functionName not in ("Encrypt", "Decrypt"):
            newFunctionStringWrapper = newFunction
            newFunctionStringWrapperArgs = ""
        else:
            newFunctionStringWrapper = None
            newFunctionStringWrapperArgs = ""
        index = 0
        for p in newParamStructs:
            if p.category == "rawType" and p.type == "void" and p.isPtr:
                template = paramVoidPtrTemplate
                previousBufferName = p.name
            elif p.category == "rawType" and p.type == "char" and p.isPtr:
                template = paramCharPtrTemplate
            elif p.category == "rawType" and p.type == "int" and not p.isPtr:
                template = paramIntTemplate
            elif p.category == "intType" and not p.isPtr:
                template = paramIntTypeTemplate
            elif p.category == "enumType" and not p.isPtr:
                template = paramEnumTypeTemplate
            #elif p.category == "structType"
            else:
                raise "Unrecognized parameter type! " + str(p)

            #Strip out commas from the last param template, kind of a hack..
            if index == len(newParamStructs)-1:
                template = template[:].replace(",", "")

            #Update the main function with this new param
            newFunction += template % vars(p)
            newFunction += paramWhiteSpace

            #Update the wrapper function with this new param
            #If this is not a special param...
            if addFunctionWrappers and newFunctionWrapper and \
                index not in offsetIndices and index not in lengthIndices:
                #Determine if this is the last param in the wrapper function
                anyMoreParams = [e for e in range(index+1, len(newParamStructs))
                                 if e not in offsetIndices and e not in lengthIndices]

                #Update the wrapper function's param list
                if not anyMoreParams:
                    template = template[:].replace(",", "")
                newFunctionWrapper += template % vars(p)
                newFunctionWrapper += paramWhiteSpace

                #Update the wrapper function's args that it uses to call the main function
                newFunctionWrapperArgs += p.name
                newFunctionWrapperArgs += ", "

                #Do the same things for the string wrapper
                if newFunctionStringWrapper:
                    newFunctionStringWrapper += template % vars(p)
                    newFunctionStringWrapper += paramWhiteSpace
                    if p.type=="void" and p.isPtr and not p.isOut:
                        newFunctionStringWrapperArgs += wrapperStringTemplate % {"1":p.name};
                    else:
                        newFunctionStringWrapperArgs += p.name
                    newFunctionStringWrapperArgs += ", "
            else:
                #If this is a special param (an offset or length)...
                if index in offsetIndices:
                    newFunctionWrapperArgs += "0, "
                    newFunctionStringWrapperArgs += "0, "
                elif index in lengthIndices:
                    newFunctionWrapperArgs += wrapperLengthTemplate % {"1":previousBufferName}
                    newFunctionStringWrapperArgs += wrapperStringLengthTemplate % {"1":previousBufferName}
            index += 1

        #Add final postfix of ); to the main and wrapper param lists
        newFunction += paramsPostfix
        if newFunctionWrapper:
            newFunctionWrapper += paramsPostfix
        if newFunctionStringWrapper:
            newFunctionStringWrapper += paramsPostfix

        #If this function takes a void* parameter, and we want to convert this
        #into two different versions, duplicate and modify the version here
        if addFunctionAlternate:
            if newFunction.find(paramVoidPtrAlternate[0]) != -1:
                newFunction += "\n" + newFunction.replace(*paramVoidPtrAlternate)

        #If we've prepared a wrapper function to eliminate need for offset[, length]:
        if addFunctionWrappers and newFunctionWrapper:
            newFunctionWrapper = newFunctionWrapper.replace("native ", "")
            newFunctionWrapper = newFunctionWrapper[:-1] + " { return %s(%s); }" % (functionName, newFunctionWrapperArgs[:-2])
            if returnIntIndex == -1:
                newFunctionWrapper = newFunctionWrapper.replace("return ", "")
            #Duplicate and modify the wrapper function
            if addFunctionAlternate:
                newFunctionWrapper2 = newFunctionWrapper.replace(*paramVoidPtrAlternate)
                newFunctionWrapper2 = newFunctionWrapper2.replace("capacity()", "length")
                newFunctionWrapper += "\n" + newFunctionWrapper2
            newFunction += "\n" + newFunctionWrapper
        if addFunctionWrappers and newFunctionStringWrapper:
            newFunctionStringWrapper = newFunctionStringWrapper.replace("native ", "")
            newFunctionStringWrapper = newFunctionStringWrapper[:-1] + " { return %s(%s); }" % (functionName, newFunctionStringWrapperArgs[:-2])
            newFunctionStringWrapper = newFunctionStringWrapper.replace(*wrapperStringReplace)
            if returnIntIndex == -1:
                newFunctionStringWrapper = newFunctionStringWrapper.replace("return ", "")
            newFunction += "\n" + newFunctionStringWrapper

        #Add a special-case string accessor for GetAttributeString() to Java and .NET
        if functionName == "GetAttributeString" and language == "java":
            newFunction += """
public static String GetAttributeString(
                    int cryptHandle, // CRYPT_HANDLE
                    int attributeType // CRYPT_ATTRIBUTE_TYPE
                    ) throws CryptException
                    {
                        int length = GetAttributeString(cryptHandle, attributeType, (byte[])null);
                        byte[] bytes = new byte[length];
                        length = GetAttributeString(cryptHandle, attributeType, bytes);
                        return new String(bytes, 0, length);
                    }
"""
        elif functionName == "GetAttributeString" and language == "net":
            newFunction += """
public static String GetAttributeString(
                    int cryptHandle, // CRYPT_HANDLE
                    int attributeType // CRYPT_ATTRIBUTE_TYPE
                    )
                    {
                        int length = GetAttributeString(cryptHandle, attributeType, null);
                        byte[] bytes = new byte[length];
                        length = GetAttributeString(cryptHandle, attributeType, bytes);
                        return Encoding.ASCII.GetString(bytes, 0, length);
                    }
"""
        #Add special-case functions for cryptAddRandom(), since it allows NULL as a
        #first parameter, and a pollType constant in place of the length
        elif functionName == "AddRandom":
            if language == "java":
                newFunction += """
public static native void AddRandom(
                    int pollType
                    ) throws CryptException;
"""
            elif language == "net":
                newFunction += """
public static void AddRandom(
                    int pollType
                    );
"""

        # Add an extra linebreak between functions
        # This isn't in cryptlib.h, but it makes the converted files more readable
        if s[functionMatch.end()+1] != "\n":
            newFunction += "\n"

        if language == "java" :
             # add the function comments for each functionName from dictionary FunctionComments
             TEXT = ''
             if functionName in FunctionComments.keys():
                  TEXT = FunctionComments[functionName]
             else:     
                  print("No comment for function " + functionName + " available ")
             if TEXT:
                  newFunction = "/** * \n* "  + TEXT + " \n*/\n" + newFunction

        #print(newFunction)
        #input()


    s = s[ : functionMatch.start()] + newFunction + s[functionMatch.end() : ]
    functionMatch = functionPattern.search(s, functionMatch.start() + len(newFunction))

#Translate comments
#-------------------
if language != "java" and language != "net":
    while 1:
        match = re.search(r"/\*(.*?)\*/", s, re.DOTALL)
        if match == None:
            break
        #print (match.group())
        #input()
        commentStrings = (commentPrefix + match.group(1) + "  ").split("\n")
        newComment = commentStrings[0]
        for commentString in commentStrings[1:]:
            if commentString.startswith("\t"):
                newComment += "\n" + commentPrefix + (" " * (inFileTabSize-2)) + commentString[1:]
            elif commentString.startswith("  "):
                newComment += "\n" + commentPrefix + commentString[1:]
            else:
                newComment += "\n" + commentPrefix + commentString
            #print ("building up:\n", newComment)
            #input()
        idx = commentString.find("\n")
        s = s[ : match.start()] + newComment + s[match.end() : ]

#Indent each line by one tab
#---------------------------
s = s.replace("\n", "\n\t")

#Write out file(s)
#Then generate the .h/.c files necessary for language integration
#At this point it's easier to just hardcode constants and have
#separate python and java codepaths
#---------------
if language=="java":

    #Add enclosing class syntax
    #---------------------------
    s = classPrefix + s + classPostfix

    os.chdir(outDir)
    if not os.path.exists("cryptlib"):
        os.mkdir("./cryptlib")
    os.chdir("./cryptlib")

    print ("Writing java files...")
    f = open("crypt.java", "w")
    f.write(s)
    f.close()
    f = open("CryptException.java", "w")
    f.write(exceptionString)
    f.close()
    f = open("CRYPT_QUERY_INFO.java", "w")
    f.write(cryptQueryInfoString)
    f.close()
    f = open("CRYPT_OBJECT_INFO.java", "w")
    f.write(cryptObjectInfoString)
    f.close()


    print ("Compiling java files...")
    print (os.popen("javac -classpath .. crypt.java").read())                    #Compile java file
    print (os.popen("javac -classpath .. CryptException.java").read())           #Compile java file
    print (os.popen("javac -classpath .. CRYPT_QUERY_INFO.java").read())         #Compile java file
    print (os.popen("javac -classpath .. CRYPT_OBJECT_INFO.java").read())        #Compile java file

    os.chdir("..")

    print ("Generating jar file...")
    print (os.popen("jar cf cryptlib.jar cryptlib/crypt.class cryptlib/CryptException.class cryptlib/CRYPT_QUERY_INFO.class cryptlib/CRYPT_OBJECT_INFO.class cryptlib").read())

    print ("Generating JNI header file...")
    print (os.popen("javac -h .  cryptlib/crypt.java ; mv -f cryptlib_crypt.h cryptlib_jni.h").read())    #Generate C header

    s = open("cryptlib_jni.h").read()
    os.remove("cryptlib_jni.h")

    print ("Generating JNI source file...")

    #Now we take cryptlib_jni.h and derive the .c file from it

    #Strip( off headers and footers
    #startIndex = s.find("/*\n * Class")
    #endIndex = s.find("#ifdef", startIndex)
    #s = s[startIndex : endIndex]

    startIndex = s.find("#undef")
    endIndex = s.find("/*\n * Class", startIndex)
    sold = s
    s = s[startIndex : endIndex]

    startIndex = endIndex
    endIndex = sold.find("#ifdef", startIndex)
    s2 = sold[startIndex : endIndex]

    #Add includes and helper functions
    #Note that we assume "cryptlib.h" is one directory behind us
    s = r"""
#include "../crypt.h"

#ifdef USE_JAVA

#include <jni.h>
#include <stdio.h>  //printf
#include <stdlib.h> //malloc, free

%s

/* Helper Functions */

int processStatus(JNIEnv *env, jint status)
{
    jclass exClass;
    jmethodID exConstructor;
    jthrowable obj;

    if (status >= cryptlib_crypt_OK)
        return 1;

    exClass = (*env)->FindClass(env, "cryptlib/CryptException");
    if (exClass == 0)
    {
        printf("java_jni.c:processStatus - no class?!\n");
        return 0;
    }

    exConstructor = (*env)->GetMethodID(env, exClass, "<init>", "(I)V");
    if (exConstructor == 0)
    {
        printf("java_jni.c:processStatus - no constructor?!\n");
        return 0;
    }

    obj = (*env)->NewObject(env, exClass, exConstructor, status);
    if (obj == 0)
    {
        printf("java_jni.c:processStatus - no object?!\n");
        return 0;
    }

    if ((*env)->Throw(env, obj) < 0)
    {
        printf("java_jni.c:processStatus - failed to throw?!\n");
        return 0;
    }
    return 0;
}

jobject processStatusReturnCryptQueryInfo(JNIEnv *env, int status, CRYPT_QUERY_INFO returnValue)
{
    jclass exClass;
    jmethodID exConstructor;
    jstring algoName;
    jobject obj;

    if (status < cryptlib_crypt_OK)
        return NULL;

    exClass = (*env)->FindClass(env, "cryptlib/CRYPT_QUERY_INFO");
    if (exClass == 0)
    {
        printf("java_jni.c:processStatusReturnCryptQueryInfo - no class?!\n");
        return NULL;
    }

    exConstructor = (*env)->GetMethodID(env, exClass, "<init>", "(Ljava/lang/String;IIII)V");
    if (exConstructor == 0)
    {
        printf("java_jni.c:processStatusReturnCryptQueryInfo - no constructor?!\n");
        return NULL;
    }

    algoName = (*env)->NewStringUTF(env, returnValue.algoName);

    obj = (*env)->NewObject(env, exClass, exConstructor, algoName, returnValue.blockSize, returnValue.minKeySize, returnValue.keySize, returnValue.maxKeySize);
    if (obj == 0)
    {
        printf("java_jni.c:processStatusReturnCryptQueryInfo - no object?!\n");
        return NULL;
    }

    return obj;
}

jobject processStatusReturnCryptObjectInfo(JNIEnv *env, int status, CRYPT_OBJECT_INFO returnValue)
{
    jclass exClass;
    jmethodID exConstructor;
    jbyteArray salt;
    jobject obj;

    if (status < cryptlib_crypt_OK)
        return NULL;

    exClass = (*env)->FindClass(env, "cryptlib/CRYPT_OBJECT_INFO");
    if (exClass == 0)
    {
        printf("java_jni.c:processStatusReturnCryptObjectInfo - no class?!\n");
        return NULL;
    }

    exConstructor = (*env)->GetMethodID(env, exClass, "<init>", "(IIII[B)V");
    if (exConstructor == 0)
    {
        printf("java_jni.c:processStatusReturnCryptObjectInfo - no constructor?!\n");
        return NULL;
    }

    salt = (*env)->NewByteArray(env, returnValue.saltSize);
    (*env)->SetByteArrayRegion(env, salt, 0, returnValue.saltSize, returnValue.salt);

    obj = (*env)->NewObject(env, exClass, exConstructor, returnValue.objectType, returnValue.cryptAlgo, returnValue.cryptMode, returnValue.hashAlgo, salt);
    if (obj == 0)
    {
        printf("java_jni.c:processStatusReturnCryptObjectInfo - no object?!\n");
        return NULL;
    }

    return obj;
}

int checkIndicesArray(JNIEnv *env, jbyteArray array, int sequenceOffset, int sequenceLength)
{
    jsize arrayLength;
    jclass exClass;

    if (array == NULL)
    {
        if (sequenceOffset == 0)
            return 1;
        else
        {
            exClass = (*env)->FindClass(env, "java/lang/ArrayIndexOutOfBoundsException");
            if (exClass == 0)
                printf("java_jni.c:checkIndicesArray - no class?!\n");
            else
            if ((*env)->ThrowNew(env, exClass, "") < 0)
                printf("java_jni.c:checkIndicesArray - failed to throw?!\n");
            return 0;
        }
    }

    arrayLength = (*env)->GetArrayLength(env, array);

    if (sequenceOffset < 0 ||
        sequenceOffset >= arrayLength ||
        sequenceOffset + sequenceLength > arrayLength)
    {
        exClass = (*env)->FindClass(env, "java/lang/ArrayIndexOutOfBoundsException");
        if (exClass == 0)
            printf("java_jni.c:checkIndicesArray - no class?!\n");
        else
        if ((*env)->ThrowNew(env, exClass, "") < 0)
            printf("java_jni.c:checkIndicesArray - failed to throw?!\n");
        return 0;
    }
    return 1;
}

int getPointerArray(JNIEnv* env, jbyteArray array, jbyte** bytesPtrPtr)
{
    jboolean isCopy;

    if (array == NULL)
    {
        (*bytesPtrPtr) = NULL;
        return 1;
    }

    (*bytesPtrPtr) = (*env)->GetByteArrayElements(env, array, &isCopy);

    if (*bytesPtrPtr == NULL)
    {
        printf("java_jni.c:getPointer - failed to get elements of array?!\n");
        return 0;
    }
    return 1;
}

void releasePointerArray(JNIEnv* env,jbyteArray array, jbyte* bytesPtr)
{
    if (bytesPtr == NULL)
        return;
    (*env)->ReleaseByteArrayElements(env, array, bytesPtr, 0);
}

int checkIndicesNIO(JNIEnv *env, jobject byteBuffer, int sequenceOffset, int sequenceLength)
{
    jlong byteBufferLength;
    jclass exClass;

    if (byteBuffer == NULL)
    {
        if (sequenceOffset == 0)
            return 1;
        else
        {
            exClass = (*env)->FindClass(env, "java/lang/ArrayIndexOutOfBoundsException");
            if (exClass == 0)
                printf("java_jni.c:checkIndicesNIO - no class?!\n");
            else
            if ((*env)->ThrowNew(env, exClass, "") < 0)
                printf("java_jni.c:checkIndicesNIO - failed to throw?!\n");
            return 0;
        }
    }

    byteBufferLength = (*env)->GetDirectBufferCapacity(env, byteBuffer);
    if (byteBufferLength == -1)
    {
        exClass = (*env)->FindClass(env, "java/lang/UnsupportedOperationException");
        if (exClass == 0)
            printf("java_jni.c:checkIndicesNIO - no class?!\n");
        else
        if ((*env)->ThrowNew(env, exClass,
"Either a non-direct ByteBuffer was passed or your JVM doesn't support JNI access to direct ByteBuffers") < 0)
            printf("java_jni.c:checkIndicesNIO - failed to throw?!\n");
        return 0;
    }

    if (sequenceOffset < 0 ||
        sequenceOffset >= byteBufferLength ||
        sequenceOffset + sequenceLength > byteBufferLength)
    {
        exClass = (*env)->FindClass(env, "java/lang/ArrayIndexOutOfBoundsException");
        if (exClass == 0)
            printf("java_jni.c:checkIndicesNIO - no class?!\n");
        else
        if ((*env)->ThrowNew(env, exClass, "") < 0)
            printf("java_jni.c:checkIndicesNIO - failed to throw?!\n");
        return 0;
    }
    return 1;
}

int getPointerNIO(JNIEnv* env, jobject byteBuffer, jbyte** bytesPtrPtr)
{
    jclass exClass;

    if (byteBuffer == NULL)
    {
        (*bytesPtrPtr) = NULL;
        return 1;
    }

    (*bytesPtrPtr) = (*env)->GetDirectBufferAddress(env, byteBuffer);

    if (*bytesPtrPtr == NULL)
    {
        exClass = (*env)->FindClass(env, "java/lang/UnsupportedOperationException");
        if (exClass == 0)
            printf("java_jni.c:getPointerNIO - no class?!\n");
        else
        if ((*env)->ThrowNew(env, exClass, "Your JVM doesn't support JNI access to direct ByteBuffers") < 0)
            printf("java_jni.c:getPointerNIO - failed to throw?!\n");
        return 0;
    }
    return 1;
}

void releasePointerNIO(JNIEnv* env,jbyteArray array, jbyte* bytesPtr)
{
}

int getPointerString(JNIEnv* env, jstring str, jbyte** bytesPtrPtr)
{
   jboolean isCopy;
   jsize strLength;
   const jbyte* rawBytesPtr;
   jclass exClass;
#ifdef __WINCE__
   int status;
#endif // __WINCE__


   if (str == NULL)
   {
       (*bytesPtrPtr) = NULL;
       return 1;
   }

   rawBytesPtr = (*env)->GetStringUTFChars(env, str, &isCopy);

   if (rawBytesPtr == NULL)
   {
       printf("java_jni.c:getPointerString - failed to get elements of String?!\n");
       return 0;
   }

   strLength = (*env)->GetStringUTFLength(env, str);

#ifdef __WINCE__
   (*bytesPtrPtr) = (jbyte*)malloc(strLength*2+2); // this is unicode, therefore \0 is two bytes long
#else
   (*bytesPtrPtr) = (jbyte*)malloc(strLength+1);
#endif // __WINCE__

   if (*bytesPtrPtr == NULL)
   {
       exClass = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
       if (exClass == 0)
           printf("java_jni.c:getPointerString - no class?!\n");
       else
       if ((*env)->ThrowNew(env, exClass, "") < 0)
           printf("java_jni.c:getPointerString - failed to throw?!\n");
       (*env)->ReleaseStringUTFChars(env, str, rawBytesPtr);
       return 0;
   }

#ifdef __WINCE__
   status = asciiToUnicode (*bytesPtrPtr, strLength*2+2, rawBytesPtr, strLength+1);
   if (status == CRYPT_ERROR_BADDATA) {
       (*env)->ReleaseStringUTFChars(env, str, rawBytesPtr);
       return 0;
   }
#else
   memcpy(*bytesPtrPtr, rawBytesPtr, strLength);
   (*bytesPtrPtr)[strLength] = 0;
#endif // __WINCE__

   (*env)->ReleaseStringUTFChars(env, str, rawBytesPtr);

   return 1;
}

void releasePointerString(JNIEnv* env, jstring str, jbyte* bytesPtr)
{
    if (bytesPtr == NULL)
        return;
    free(bytesPtr);
}

%s

#endif /* USE_JAVA */
""" % (s, s2)

    functionPattern = re.compile(r"JNIEXPORT ([^ \t]+) JNICALL Java_cryptlib_crypt_([^ \t\n]+)\n[ \t]*\(([^\)]*)\);")
    functionMatch = functionPattern.search(s)
    while functionMatch:
        #print (functionMatch.groups())

        #Extract the returnType, name, and prototype for this function
        function = functionMatch.group()
        functionReturnType, functionName, functionPrototype = functionMatch.groups()

        #Handle special-case AddRandom(pollType) function
        if functionName == "AddRandom__I":
            p = ParamStruct()
            p.type = "int"
            p.isPtr = 0
            p.isOut = 0
            p.category = "intType"
            p.name = "NULL"
            p.rawIndex = 0
            p2 = ParamStruct()
            p2.type = "int"
            p2.isPtr = 0
            p2.isOut = 0
            p2.category = "intType"
            p2.name = "pollType"
            p2.rawIndex = 1
            rawParamStructs = [p,p2]
            newParamStructs = [p2,p2]
            voidTag = "Array"
            functionName = functionName.split("__")[0]
            returnName = None
            discardName = None
        else:
            #Strip JNI decoration off function name
            #But record which variety of tagged helper functions to use
            if functionName.find("__") != -1:
                if functionName.find("ByteBuffer") != -1:
                    voidTag = "NIO"
                else:
                    voidTag = "Array"
                functionName = functionName.split("__")[0]

            #Look up the parameters we've previously determined for this function
            rawParamStructs = rawParamStructsDict[functionName]
            newParamStructs = newParamStructsDict[functionName]
            if newReturnStructsDict.get(functionName):
                returnName = newReturnStructsDict.get(functionName).name
                returnType = newReturnStructsDict.get(functionName).type
                returnCategory = newReturnStructsDict.get(functionName).category
            else:
                returnName = None

            if newDiscardedStructsDict.get(functionName):
                discardName = newDiscardedStructsDict.get(functionName).name
            else:
                discardName = None

        #for p in newParamStructs: print ("     "+str(p))

        #Substitute parameter names into the function prototype
        newFunctionParams = expandFunctionPrototype(functionPrototype, newParamStructs)
        startIndex = functionMatch.start(3) - functionMatch.start()
        endIndex = functionMatch.end(3) - functionMatch.start()
        function = function[ : startIndex] + newFunctionParams + function[ endIndex : ]

        newFunctionBody = "\nint status = 0;\n"

        arguments = ""
        for p in rawParamStructs:
            if p.name == returnName or p.name == discardName:
                arguments += "&"
            if p.isPtr and p.type=="void":
                arguments += "%sPtr + %sOffset, " % (p.name, p.name)
            elif p.isPtr and p.type=="char":
                arguments += "%sPtr, " % p.name
            else:
                arguments += p.name + ", "
        arguments = arguments[:-2]

        if returnName:
            if returnCategory == "intType" or returnType=="int":
                newFunctionBody += "jint %s = 0;\n" % returnName
            else:
                newFunctionBody += returnType +" %s;\n" % returnName
        if discardName:
            newFunctionBody += "jint %s = 0;\n" % discardName

        #If we have arrays, add the code to handle them
        arrayNames          = [p.name for p in newParamStructs if p.isPtr]
        charArrayNames      = [p.name for p in newParamStructs if p.isPtr and p.type=="char"]
        voidArrayNames      = [p.name for p in newParamStructs if p.isPtr and p.type=="void"]
        outVoidArrayNames   = [p.name for p in newParamStructs if p.isPtr and p.type=="void" and p.isOut]
        if arrayNames:
            #Declare C pointers to retrieve array contents
            for n in arrayNames:
                newFunctionBody += "jbyte* " + n + "Ptr = 0;\n"
            newFunctionBody += "\n"
            #Retrieve the contents for strings
            #We need to do this first cause in one case this char* is needed as an argument
            if charArrayNames:
                for n in charArrayNames:
                    newFunctionBody += "if (!getPointerString(env, %(n)s, &%(n)sPtr))\n\tgoto finish;\n" % vars()
                newFunctionBody += "\n"
            #Query the length of output data
            #CryptPopData is the only exception that produces output data
            #but doesn't require this cause an explicit length is passed in
            if outVoidArrayNames and functionName.find("PopData") == -1:
                for n in outVoidArrayNames:
                    argumentsWithNull = arguments.replace("%sPtr + %sOffset" % (n,n), "NULL")

		    # Handle four exceptions in which the cryptlib code should not be invoced twice
                    # (Aug 2023, Ralf Senderek)
                    if functionName.find("CreateSignature") != -1:
                         newFunctionBody += "if (signatureMaxLength == 0) \n    if (!processStatus(env, crypt%s(%s)))\n\tgoto finish;\n" % (functionName, argumentsWithNull)
                    elif functionName.find("WrapKey") != -1:
                         newFunctionBody += "if (encryptedKeyMaxLength == 0) \n    if (!processStatus(env, crypt%s(%s)))\n\tgoto finish;\n" % (functionName, argumentsWithNull)
                    else:
                         newFunctionBody += "if (!processStatus(env, crypt%s(%s)))\n\tgoto finish;\n" % (functionName, argumentsWithNull)
                    # end modification

                newFunctionBody += "\n"
            elif functionName.find("PopData") != -1:
                newFunctionBody += "//CryptPopData is a special case that doesn't have the length querying call\n\n"
            if voidArrayNames:
                for n in voidArrayNames:
                    index = [p.name for p in newParamStructs].index(n)
                    if n in outVoidArrayNames:
                        lengthName = returnName
                    else:
                        if len(newParamStructs)<=index+2 or newParamStructs[index+2].category != "rawType" or newParamStructs[index+2].type != "int":
                            lengthName = "1" #CheckSignature and ImportKey don't have a length, so we can't do a good check!
                        else:
                            lengthName = newParamStructs[index+2].name
                    newFunctionBody += "if (!checkIndices%(voidTag)s(env, %(n)s, %(n)sOffset, %(lengthName)s))\n\tgoto finish;\n" % vars()
                newFunctionBody += "\n"
            for n in voidArrayNames:
                newFunctionBody += "if (!getPointer%(voidTag)s(env, %(n)s, &%(n)sPtr))\n\tgoto finish;\n" % vars()

        if newFunctionBody[-2] != "\n":
            newFunctionBody += "\n"
        newFunctionBody += "status = crypt%s(%s);\n\n" % (functionName, arguments)

        if arrayNames:
            newFunctionBody += "finish:\n"
        if arrayNames:
            for n in voidArrayNames:
                newFunctionBody += "releasePointer%(voidTag)s(env, %(n)s, %(n)sPtr);\n" % vars()
            for n in charArrayNames:
                newFunctionBody += "releasePointerString(env, %(n)s, %(n)sPtr);\n" % vars()

        #newFunctionBody += "processStatus(env, status);\n"

        #if returnName:
        #    newFunctionBody += "return(%s);\n" % returnName
        if returnName:
            if returnCategory == "intType" or returnType == "int":
                newFunctionBody += "processStatus(env, status);\n"
                newFunctionBody += "return(%s);\n" % returnName
            elif returnType == "CRYPT_QUERY_INFO":
                newFunctionBody += "return(processStatusReturnCryptQueryInfo(env, status, %s));\n" % returnName
            elif returnType == "CRYPT_OBJECT_INFO":
                newFunctionBody += "return(processStatusReturnCryptObjectInfo(env, status, %s));\n" % returnName
        else:
            newFunctionBody += "processStatus(env, status);\n"

        newFunctionBody = newFunctionBody[:-1] #Strip off last \n
        newFunctionBody = newFunctionBody.replace("\n", "\n\t");
        newFunction = function[:-1]+"\n{" + newFunctionBody + "\n}"

        #Substitute the output equivalent for the input
        s = s[ : functionMatch.start()] + newFunction + s[functionMatch.end() : ]

        #Get next function
        functionMatch = functionPattern.search(s, functionMatch.start() + len(newFunction))

    f = open("java_jni.c", "w")
    f.write(s)
    f.close()



elif language == "python":
    os.chdir(outDir)

    #print (sInts)
    #input()
    #print (sFuncs)
    #input()

    moduleFunctions = ""
    s =         pyBeforeExceptions + exceptionString + pyBeforeFuncs + sFuncs + \
                pyBeforeModuleFunctions + moduleFunctions + sModFuncs + \
                pyBeforeInts + sInts + \
                pyAfterInts

    #print (s)
    #input()
    functionPattern = re.compile(r"static PyObject\* python_crypt([^\(]+)[^{]+{([^}]*)}", re.DOTALL)
    functionMatch = functionPattern.search(s)
    while functionMatch:
        #print (functionMatch.group())
        #print (functionMatch.groups())
        #input()

        #Most of the code in this loop is copied-then-modified from the java code above, ugly..
        voidTag = ""

        #Extract the function name and body
        functionName, functionBody = functionMatch.groups()

        #Look up the parameters we've previously determined for this function
        rawParamStructs = rawParamStructsDict[functionName]
        newParamStructs = newParamStructsDict[functionName]
        lengthIndices = lengthIndicesDict[functionName]
        offsetIndices = offsetIndicesDict[functionName]
        #print (functionName, lengthIndices, offsetIndices)
        #input()
        if newReturnStructsDict.get(functionName):
            returnName = newReturnStructsDict.get(functionName).name
            returnType = newReturnStructsDict.get(functionName).type
            returnCategory = newReturnStructsDict.get(functionName).category
        else:
            returnName = None
        if newDiscardedStructsDict.get(functionName):
            discardName = newDiscardedStructsDict.get(functionName).name
        else:
            discardName = None

        newFunctionBody = "\nint status = 0;\n"

        #Arguments to pass to the C cryptlib function
        arguments = ""
        for p in rawParamStructs:
            if p.name == returnName or p.name == discardName:
                arguments += "&"
            if p.isPtr and p.type=="void":
                arguments += "%sPtr, " % (p.name)
            elif p.isPtr and p.type=="char":
                arguments += "%sPtr, " % p.name
            else:
                arguments += p.name + ", "
        arguments = arguments[:-2]

        if returnName:
            if returnCategory == "intType" or returnType=="int":
                newFunctionBody += "int %s = 0;\n" % returnName
            else:
                newFunctionBody += returnType +" %s;\n" % returnName
        if discardName:
            newFunctionBody += "int %s = 0;\n" % discardName
        #Declare variables to parse args tuple into
        index = 0
        for p in newParamStructs:
            if index not in offsetIndices:       #Python doesn't use the offsets
                if p.isPtr:
                    newFunctionBody += "PyObject* %s = NULL;\n" % p.name
                else:
                    newFunctionBody += "int %s = 0;\n" % p.name
            index += 1

        #If we have arrays, add the code to handle them
        arrayNames          = [p.name for p in newParamStructs if p.isPtr]
        charArrayNames      = [p.name for p in newParamStructs if p.isPtr and p.type=="char"]
        voidArrayNames      = [p.name for p in newParamStructs if p.isPtr and p.type=="void"]
        outVoidArrayNames   = [p.name for p in newParamStructs if p.isPtr and p.type=="void" and p.isOut]
        if arrayNames:
            #Declare C pointers to retrieve array contents
            # make sure that certain pointers have type char instead of unsigned char
            # [namePtr, keyIDPtr, passwordPtr, oidPtr]
            for n in arrayNames:
                if n in ["name", "keyID", "password", "oid"]:
                     newFunctionBody += "char* " + n + "Ptr = 0;\n"
                else:
                     newFunctionBody += "unsigned char* " + n + "Ptr = 0;\n"
            newFunctionBody += "\n"
            #Retrieve the contents for strings
            #We need to do this first cause in one case this char* is needed as an argument

        #Parse the input arguments from the python user
        #Arguments to parse from the python args tuple
        if newParamStructs:
            parseFormatString = ""
            parseArgsList = []
            index = 0
            for p in newParamStructs:
                if index not in lengthIndices and index not in offsetIndices:
                    if p.isPtr and p.type=="char":
                        parseFormatString += "O"
                        parseArgsList.append("&" + p.name)
                    elif p.isPtr and p.type=="void":
                        parseFormatString += "O"
                        parseArgsList.append("&" + p.name)
                    else:
                        parseFormatString += "i"
                        parseArgsList.append("&" + p.name)

                index += 1

            if newFunctionBody[-2] != "\n":
                newFunctionBody += "\n"


            if functionName == "AddRandom":
                newFunctionBody += """\
//Special case to handle SLOWPOLL / FASTPOLL values
if (PyArg_ParseTuple(args, "i", &randomDataLength))
    return processStatus(cryptAddRandom(NULL, randomDataLength));\n\n"""

            newFunctionBody += """\
if (!PyArg_ParseTuple(args, "%s", %s))
    return(NULL);\n\n""" % (parseFormatString, ", ".join(parseArgsList))
            #for p in newParamStructs:
            #    print (p.name, end=" ")

        if arrayNames:
            if charArrayNames:
                for n in charArrayNames:
                    newFunctionBody += "if (!getPointerReadString(%(n)s, &%(n)sPtr))\n\tgoto finish;\n" % vars()
                newFunctionBody += "\n"
            #Query the length of output data
            #CryptPopData is the only exception that produces output data
            #but doesn't require this cause an explicit length is passed in
            if outVoidArrayNames and functionName.find("PopData") == -1:
                for n in outVoidArrayNames:
                    argumentsWithNull = arguments.replace("%sPtr" % (n), "NULL")

		    # Handle four exceptions in which the cryptlib code should not be invoced twice
		    # (Aug 2023, Ralf Senderek)
                    if functionName.find("CreateSignature") != -1:
                         newFunctionBody += "if (signatureMaxLength == 0) \n    if (!processStatusBool(crypt%s(%s)))\n\tgoto finish;\n" % (functionName, argumentsWithNull)
                    elif functionName.find("WrapKey") != -1:
                         newFunctionBody += "if (encryptedKeyMaxLength == 0) \n    if (!processStatusBool(crypt%s(%s)))\n\tgoto finish;\n" % (functionName, argumentsWithNull)
                    else:
                         newFunctionBody += "if (!processStatusBool(crypt%s(%s)))\n\tgoto finish;\n" % (functionName, argumentsWithNull)
                    # end modification

                newFunctionBody += "\n"
            elif functionName.find("PopData") != -1:
                newFunctionBody += "//CryptPopData is a special case that doesn't have the length querying call\n\n"

            #Go through and get the pointers for translated void*
            if voidArrayNames:
                for n in voidArrayNames:
                    index = [p.name for p in newParamStructs].index(n)
                    #Determine the name of its length parameter
                    if n in outVoidArrayNames:
                        lengthName = returnName
                    else:
                        if len(newParamStructs)<=index+2 or newParamStructs[index+2].category != "rawType" or newParamStructs[index+2].type != "int":
                            lengthName = "1" #QueryObject, CheckSignature and ImportKey don't have a length, so we can't do a good check!
                        else:
                            lengthName = newParamStructs[index+2].name
                    if n in outVoidArrayNames and functionName.find("PopData") == -1:
                        newFunctionBody += "if (!getPointerWriteCheckIndices%(voidTag)s(%(n)s, &%(n)sPtr, &%(lengthName)s))\n\tgoto finish;\n" % vars()
                    else:
                        if lengthName == "1": #Handle the #CheckSignature/ImportKey case
                            newFunctionBody += "if (!getPointerReadNoLength%(voidTag)s(%(n)s, &%(n)sPtr))\n\tgoto finish;\n" % vars()
                        else:
                            #If the pointer is C_IN and not C_INOUT
                            #(we check against Encrypt/Decrypt directory for this latter check)
                            #We only need it for reading, not writing
                            if n not in outVoidArrayNames and functionName not in ("Encrypt", "Decrypt"):
                                newFunctionBody += "if (!getPointerRead(%(n)s, &%(n)sPtr, &%(lengthName)s))\n\tgoto finish;\n" % vars()
                            else:
                                newFunctionBody += "if (!getPointerWrite(%(n)s, &%(n)sPtr, &%(lengthName)s))\n\tgoto finish;\n" % vars()

        if newFunctionBody[-2] != "\n":
            newFunctionBody += "\n"
        newFunctionBody += "status = crypt%s(%s);\n\n" % (functionName, arguments)

        if arrayNames:
            newFunctionBody += "finish:\n"
        if arrayNames:
            for n in voidArrayNames:
                newFunctionBody += "releasePointer(%(n)s, %(n)sPtr);\n" % vars()
            for n in charArrayNames:
                newFunctionBody += "releasePointerString(%(n)s, %(n)sPtr);\n" % vars()

        if returnName:
            if returnCategory == "intType":
                newFunctionBody += "return(processStatusReturnCryptHandle(status, %s));" % returnName
            elif returnType == "CRYPT_QUERY_INFO":
                newFunctionBody += "return(processStatusReturnCryptQueryInfo(status, %s));" % returnName
            elif returnType == "CRYPT_OBJECT_INFO":
	        # modified (March 2026, Ralf Senderek)
                newFunctionBody += "if (status == 0)\n\t return(processStatus(status));\nelse\n\t return(processStatusReturnCryptObjectInfo(status, %s));" % returnName
            elif returnType == "int":
                newFunctionBody += "return(processStatusReturnInt(status, %s));" % returnName
        else:
            newFunctionBody += "return(processStatus(status));"

        newFunctionBody = newFunctionBody.replace("\n", "\n\t")

        #Substitute the output equivalent for the input
        s = s[ : functionMatch.start(2)] + newFunctionBody + "\n" + s[functionMatch.end(2) : ]

        #Get next function
        functionMatch = functionPattern.search(s, functionMatch.start() + len(newFunction))

    f = open("python.c", "w")
    f.write(s)
    f.close()


    f = open("setup.py", "w")
    f.write(setupPy)
    f.close()

elif language == "net":
    flagForAddRandomHack = 0

    #functionPattern = re.compile(r"public static ([^ \t]+) ([^ \t\n]+)\([ \t\n]*\(([^\)]*)\) throws CryptException;")
    functionPattern = re.compile(r"public static ([^ \t]+) ([^ \t\n]+)\([ \t\n]*([^\)]*)\);")
    functionMatch = functionPattern.search(s)
    while functionMatch:
        #print (functionMatch.groups())

        #Extract the returnType, name, and prototype for this function
        function = functionMatch.group()
        functionReturnType, functionName, functionPrototype = functionMatch.groups()

        #Handle special-case AddRandom(pollType) function
        if functionName == "AddRandom":
            flagForAddRandomHack += 1
        if flagForAddRandomHack == 2:
            p = ParamStruct()
            p.type = "int"
            p.isPtr = 0
            p.isOut = 0
            p.category = "intType"
            p.name = "IntPtr.Zero"
            p.rawIndex = 0
            p2 = ParamStruct()
            p2.type = "int"
            p2.isPtr = 0
            p2.isOut = 0
            p2.category = "intType"
            p2.name = "pollType"
            p2.rawIndex = 1
            rawParamStructs = [p,p2]
            newParamStructs = [p2,p2]
            voidTag = "Array"
            functionName = functionName.split("__")[0]
            returnName = None
            discardName = None
            flagForAddRandomHack = None
        else:
            #Look up the parameters we've previously determined for this function
            rawParamStructs = rawParamStructsDict[functionName]
            newParamStructs = newParamStructsDict[functionName]
            if newReturnStructsDict.get(functionName):
                returnName = newReturnStructsDict.get(functionName).name
                returnType = newReturnStructsDict.get(functionName).type
                returnCategory = newReturnStructsDict.get(functionName).category
            else:
                returnName = None
            if newDiscardedStructsDict.get(functionName):
                discardName = newDiscardedStructsDict.get(functionName).name
            else:
                discardName = None

        #for p in newParamStructs: print ("     "+str(p))

        #Substitute parameter names into the function prototype
        newFunctionParams = functionPrototype #expandFunctionPrototype(functionPrototype, newParamStructs)
        startIndex = functionMatch.start(3) - functionMatch.start()
        endIndex = functionMatch.end(3) - functionMatch.start()
        function = function[ : startIndex] + newFunctionParams + function[ endIndex : ]

        newFunctionBody = "\n"

        arguments = ""
        for p in rawParamStructs:
            if p.name == returnName or p.name == discardName:
                arguments += "%sPtr, " % p.name
            elif p.isPtr and p.type=="void":
                arguments += "%sPtr, " % p.name
            elif p.isPtr and p.type=="char":
                arguments += "%sPtr, " % p.name
            else:
                arguments += p.name + ", "
        arguments = arguments[:-2]

        if returnName:
            if returnCategory == "structType":
                newFunctionBody += "IntPtr %sPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(%s)));\n" % (returnName, returnType)
                newFunctionBody += "%s %s = new %s();\n" % (returnType, returnName, returnType)
            else:
                newFunctionBody += "IntPtr %sPtr = Marshal.AllocHGlobal(4);\n" % returnName
        if discardName:
            newFunctionBody += "IntPtr %sPtr = Marshal.AllocHGlobal(4);\n" % discardName

        #If we have arrays, add the code to handle them
        arrayNames          = [p.name for p in newParamStructs if p.isPtr]
        charArrayNames      = [p.name for p in newParamStructs if p.isPtr and p.type=="char"]
        voidArrayNames      = [p.name for p in newParamStructs if p.isPtr and p.type=="void"]
        outVoidArrayNames   = [p.name for p in newParamStructs if p.isPtr and p.type=="void" and p.isOut]
        if arrayNames:
            #Declare C pointers to retrieve array contents
            for n in arrayNames:
                #newFunctionBody += "jbyte* " + n + "Ptr = 0;\n"
                newFunctionBody += "GCHandle %sHandle = new GCHandle();\nIntPtr %sPtr = IntPtr.Zero;\n" % (n,n)
                if n in charArrayNames:
                    newFunctionBody += "byte[] %sArray = Encoding.ASCII.GetBytes(%s);\n" % (n,n)

            newFunctionBody += "try\n{\n"

            #Retrieve the contents for strings
            #We need to do this first cause in one case this char* is needed as an argument
            if charArrayNames:
                for n in charArrayNames:
                    newFunctionBody += "getPointer(%(n)sArray, 0, ref %(n)sHandle, ref %(n)sPtr);\n" % vars()
            #Query the length of output data
            #CryptPopData is the only exception that produces output data
            #but doesn't require this cause an explicit length is passed in
            if outVoidArrayNames and functionName.find("PopData") == -1:
                for n in outVoidArrayNames:
                    argumentsWithNull = arguments.replace("%sPtr + %sOffset" % (n,n), "NULL")
                    newFunctionBody += "processStatus(wrapped_%s(%s));\n" % (functionName, argumentsWithNull)
                    newFunctionBody += "int %s = Marshal.ReadInt32(%sPtr);\n" % (returnName, returnName)
            elif functionName.find("PopData") != -1 or functionName.find("PushData") != -1:
                newFunctionBody += "int %s = 0;\n" % returnName
                newFunctionBody += "int status;\n"
            if voidArrayNames:
                for n in voidArrayNames:
                    index = [p.name for p in newParamStructs].index(n)
                    if n in outVoidArrayNames:
                        lengthName = returnName
                    else:
                        if len(newParamStructs)<=index+2 or newParamStructs[index+2].category != "rawType" or newParamStructs[index+2].type != "int":
                            lengthName = "1" #CheckSignature and ImportKey don't have a length, so we can't do a good check!
                        else:
                            lengthName = newParamStructs[index+2].name
                    newFunctionBody += "checkIndices(%(n)s, %(n)sOffset, %(lengthName)s);\n" % vars()
            for n in voidArrayNames:
                newFunctionBody += "getPointer(%(n)s, %(n)sOffset, ref %(n)sHandle, ref %(n)sPtr);\n" % vars()
        elif returnName:
            newFunctionBody += "try\n{\n"

        if functionName.find("PopData") == -1 and functionName.find("PushData") == -1:
                newFunctionBody += "processStatus(wrapped_%s(%s));\n" % (functionName, arguments)
        else:
                newFunctionBody += "status = wrapped_%s(%s);\n" % (functionName, arguments)
                newFunctionBody += "%s = Marshal.ReadInt32(%sPtr);\n" % (returnName, returnName)
                newFunctionBody += "processStatus(status, %s);\n" %returnName

        #if newFunctionBody[-2] != "\n":
        #    newFunctionBody += "\n"
        if returnName:
            if returnCategory == "structType":
                newFunctionBody += "Marshal.PtrToStructure(%sPtr, %s);\n" % (returnName, returnName)
                newFunctionBody += "return %s;\n" % returnName
            else:
                                if functionName.find("PopData") == -1 and functionName.find("PushData") == -1:
                                        newFunctionBody += "return Marshal.ReadInt32(%sPtr);\n" % returnName
                                else:
                                        newFunctionBody += "return %s;\n" %returnName

        if arrayNames or returnName:
            newFunctionBody += "}\nfinally\n{\n"
            if returnName:
                newFunctionBody += "Marshal.FreeHGlobal(%sPtr);\n" % returnName
            if arrayNames:
                for n in voidArrayNames:
                    newFunctionBody += "releasePointer(%(n)sHandle);\n" % vars()
                for n in charArrayNames:
                    newFunctionBody += "releasePointer(%(n)sHandle);\n" % vars()
            newFunctionBody += "}\n"

        #Add tabs to lines inside brackets:
        lines = newFunctionBody.split("\n")
        brackets = 0
        for count in range(len(lines)):
            line = lines[count]
            if line.startswith("}"):
                brackets -= 1
            lines[count] = ("\t" * brackets) + line
            if line.startswith("{"):
                brackets += 1
        newFunctionBody = "\n".join(lines)

        newFunctionBody = newFunctionBody[:-1] #Strip off last \n
        newFunctionBody = newFunctionBody.replace("\n", "\n\t");
        newFunction = function[:-1]+"\n{" + newFunctionBody + "\n}"
        newFunction = newFunction.replace("\n", "\n\t");

        #Substitute the output equivalent for the input
        s = s[ : functionMatch.start()] + newFunction + s[functionMatch.end() : ]

        #Get next function
        functionMatch = functionPattern.search(s, functionMatch.start() + len(newFunction))

    #Add enclosing class syntax
    #---------------------------
    s = classPrefix + s

    for functionName in functionNames:
        rawParamStructs = rawParamStructsDict[functionName]
        parameters = ""
        for p in rawParamStructs:
            if p.isPtr:
                parameters += "IntPtr %s, " % p.name
            else:
                parameters += "int %s, " % p.name
        parameters = parameters[:-2]

        s += '\t[DllImport("cl32.dll", EntryPoint="crypt%s")]\n\tprivate static extern int wrapped_%s(%s);\n\n' % (functionName, functionName, parameters)
        functionNames = [] #Accumulate function names here
        #rawParamStructsDict = {} #Accumulate function name -> list of ParamStructs (of original c code)
        #newParamStructsDict = {} #Accumulate function name -> list of ParamStructs (of new java/python code)



    s += classPostfix + exceptionString + "\n\n}"

    os.chdir(outDir)
    print ("Writing .NET file...")
    f = open("cryptlib.cs", "w")
    f.write(s)
    f.close()


