فهرست منبع

misc/tools/kemi: lua - tools useful for using Kamailio with KEMI Lua scripts

Daniel-Constantin Mierla 4 سال پیش
والد
کامیت
c89d46d886
3فایلهای تغییر یافته به همراه819 افزوده شده و 0 حذف شده
  1. 88 0
      misc/tools/kemi/lua/README.md
  2. 374 0
      misc/tools/kemi/lua/kemiksrlib.py
  3. 357 0
      misc/tools/kemi/lua/kemiluacheck.py

+ 88 - 0
misc/tools/kemi/lua/README.md

@@ -0,0 +1,88 @@
+# Tools For KEMI Lua #
+
+Tools useful for using Kamailio with KEMI Lua scripts.
+
+## kemiksrlib ##
+
+Generates the file `KSR.lua` with skeleton functions in the `KSR` object. The
+function parameters are prefixed with `p_` and the functions return:
+
+  * empty string - in case of functions returning `xval`
+  * `1` - in case of functions returning `int`
+  * `true` - in case of functions returning `int`
+  * nothing - in case of void functions
+  * `nil` - in the other cases
+
+Useful to use for syntax checking and functions prototypes.
+
+Usage:
+
+```
+python3 kemiksrlib.py /path/to/src/kamailio
+```
+
+## kemiluacheck.py ##
+
+Generates the file `KSR.luacheckrc` that can be used with `luacheck` to verify
+if the KEMI Lua script uses undefined symbols (e.g., functions that are not
+in the `KSR` library due to typos).
+
+Usage:
+
+```
+python3 kemiluacheck.py /path/to/src/kamailio
+
+luacheck --config KSR.luacheckrc kamailio-basic-kemi-lua.lua -d
+```
+
+Example output with `KSR` correct functions:
+
+```
+Checking kamailio-basic-kemi-lua.lua              5 warnings
+
+    kamailio-basic-kemi-lua.lua:67:10: unused global variable ksr_request_route
+    kamailio-basic-kemi-lua.lua:389:10: unused global variable ksr_branch_manage
+    kamailio-basic-kemi-lua.lua:398:10: unused global variable ksr_onreply_manage
+    kamailio-basic-kemi-lua.lua:409:10: unused global variable ksr_failure_manage
+    kamailio-basic-kemi-lua.lua:420:10: unused global variable ksr_reply_route
+
+Total: 5 warnings / 0 errors in 1 file
+```
+
+Example output with `KSR.is_CANCLE()` typo:
+
+```
+Checking kamailio-basic-kemi-lua.lua              6 warnings
+
+    kamailio-basic-kemi-lua.lua:67:10: unused global variable ksr_request_route
+    kamailio-basic-kemi-lua.lua:76:5: accessing undefined field is_CANCLE of global KSR
+    kamailio-basic-kemi-lua.lua:389:10: unused global variable ksr_branch_manage
+    kamailio-basic-kemi-lua.lua:398:10: unused global variable ksr_onreply_manage
+    kamailio-basic-kemi-lua.lua:409:10: unused global variable ksr_failure_manage
+    kamailio-basic-kemi-lua.lua:420:10: unused global variable ksr_reply_route
+
+Total: 6 warnings / 0 errors in 1 file
+```
+
+## Other Tools ##
+
+Other external tools useful to check the Lua scripts.
+
+### luac ###
+
+The `luac` (the Lua compiler) can be used with parameter `-p` to check for
+syntax errors (e.g., missing closing quote or parenthesis).:w
+
+Example - when there is a missing closing quite, like:
+
+```
+KSR.hdr.remove("Route);
+```
+
+the check results in:
+
+```
+luac -p kamailio-basic-kemi-lua.lua
+
+luac: kamailio-basic-kemi-lua.lua:100: unfinished string near '"Route);'
+```

+ 374 - 0
misc/tools/kemi/lua/kemiksrlib.py

@@ -0,0 +1,374 @@
+# tool to generate the modules.md content
+#
+
+import os, json, sys, time, fnmatch, re, importlib
+
+PATH_GENERATED_OBJ = "KSR.lua"
+
+class ModuleObjGenerator(object):
+
+    # Contains the output until it should be written to disk
+    markdown_string = ""
+
+    def execute(self, data):
+        # Validate that we got some methods back. 155 is an arbitrary large number.
+        if len(data) < 1:
+            print("ERR: Invalid data")
+            exit()
+
+        functions_parsed = self.parse_function_list(data)
+        self.output_obj(functions_parsed)
+
+        print ("File created successfully at: " + PATH_GENERATED_OBJ)
+
+    def parse_function_list(self, functions):
+        data = {}
+
+        for elem in functions:
+            module = elem["module"]
+
+            # TODO: What about the hdr, pv, x sub-module?
+            if module == "":
+                module = "_core"
+
+            if module not in data:
+                data[module] = []
+
+            data[module].append({"name": elem["name"], "return": elem["ret"], "params": elem["params"]})
+
+        return data
+
+    def output_obj(self, data):
+
+        for key in sorted(data):
+            methods = data[key]
+            # Sort the functions by name alphabetically
+            methods = sorted(methods, key = lambda k: k['name'])
+            self.object_module_content(key, methods)
+        
+        self.obj_write()
+
+        return True
+
+    def object_module_content(self, module, methods):
+        if module == "_core":
+            module_prefix = ""
+        else:
+            module_prefix = module + "."
+
+        for value in methods:
+            self.markdown_string += "local function KSR." + module_prefix + value["name"]
+
+            # Sanitize the return values
+            if value["return"] == "none":
+                return_value = ""
+            elif value["return"] == "void":
+                return_value = ""
+            elif value["return"] == "xval":
+                return_value = " \"\""
+            elif value["return"] == "int":
+                return_value = " 1"
+            elif value["return"] == "bool":
+                return_value = " true"
+            else:
+                return_value = " nil"
+
+            # Sanitize the parameter values
+            if value["params"] == "none":
+                params_value = ""
+            else:
+                params_value = value["params"]
+
+            # Generate the output string for the markdown page
+            self.markdown_string += "(" + params_value + ")\n"
+            self.markdown_string += "\treturn" + return_value + ";\n"
+            self.markdown_string += "end\n\n"
+
+
+        return True
+
+    def obj_write(self):
+        f = open(PATH_GENERATED_OBJ, "w")
+        f.write("local KSR = {};\n\n")
+        f.write(self.markdown_string)
+        f.write("return KSR;\n")
+        f.close()
+        return True
+
+    def read_file_to_string(self, filename):
+        path = self.PATH_MODULES_DOCS + filename
+        if os.path.isfile(path):
+            with open(path, 'r') as myfile:
+                return myfile.read() + "\n"
+        return ""
+
+
+class KemiFileExportParser(object):
+    # These functions are created by a macro so makes the parsing somewhat tricky,
+    # for now they are statically defined
+    macro_functions = {
+        "t_set_auto_inv_100": "int state",
+        "t_set_disable_6xx": "int state",
+        "t_set_disable_failover": "int state",
+        "t_set_no_e2e_cancel_reason": "int state",
+        "t_set_disable_internal_reply": "int state"
+    }
+
+    # These files export the KEMI functions in a special way so we map them manually
+    # TODO: Discuss with @miconda if core/HDR/pv/x should be added as well or not
+    special_exports = [
+        {"filename": "kemi.c", "export": "_sr_kemi_core", "folder": "/src/core/"},
+        {"filename": "kemi.c", "export": "_sr_kemi_hdr", "folder": "/src/core/"},
+        {"filename": "kemi.c", "export": "_sr_kemi_pv", "folder": "/src/core/"},
+        #{"filename": "app_lua_mod.c", "export": "sr_kemi_app_lua_rpc_exports", "folder": "/modules/app_lua/"}
+    ]
+
+    def generate_kemi_export_list(self, source_path):
+        files = self.list_c_files_in_directory(source_path)
+        lists = []
+
+        for file in files:
+            with open(file, 'r', encoding='utf-8', errors='ignore') as f:
+                lines = f.readlines()
+
+            export_name = self.find_c_file_kemi_export(file, lines)
+            if export_name:
+                export_functions = self.extract_c_file_kemi_export_lines(file, lines, export_name)
+                lists = lists + export_functions
+                print ("Found ", len(export_functions), "functions", "Total:", len(lists))
+
+        # Handle some special files separately
+        for elem in self.special_exports:
+            file = source_path + elem["folder"] + elem["filename"]
+            with open(file) as f:
+                lines = f.readlines()
+            lists = lists + self.extract_c_file_kemi_export_lines(file, lines, elem["export"])
+
+        return lists
+
+    def find_c_file_kemi_export(self, filename, lines):
+        param = None
+
+        for line in lines:
+            if line.find("sr_kemi_modules_add") >= 0:
+                line = line.lstrip(" ")
+                line = line.lstrip("\t")
+                if line.find("sr_kemi_modules_add") == 0:
+                    param = line[line.find("(") + 1:line.find(")")]
+                    print ("INFO: ---- Found export", filename, param)
+                    break
+                else:
+                    if line != "int sr_kemi_modules_add(sr_kemi_t *klist)\n":
+                        print ("ERR: Possible error at line: ", filename, line)
+                        exit()
+
+        return param
+
+    def extract_c_file_kemi_export_lines(self, filename, lines, export_name):
+        list_functions = []
+        find_start = True
+
+        for line in lines:
+            if find_start and line.find("static sr_kemi_t " + export_name + "[]") >= 0:
+                find_start = False
+            elif not find_start:
+                if line.find("};") >= 0:
+                    break
+                line = line.lstrip(" \t")
+                line = line.rstrip()
+                list_functions.append(line)
+
+        if len(list_functions) < 1:
+            print ("ERR: Couldn't parse file for exported functions: ", export_name)
+            exit()
+
+        parsed_list = self.parse_kemi_export_c_lines(filename, list_functions)
+
+        return parsed_list
+
+    def parse_kemi_export_c_lines(self, filename, lines):
+        elements = []
+        function_lines = []
+        temp_function = ""
+
+        # We look for str_init which would be the start of each exported function
+        for line in lines:
+            if line.find("str_init") >= 0:
+                if temp_function != "":
+                    function_lines.append(temp_function)
+                    temp_function = ""
+            temp_function += line
+
+        if temp_function != "":
+            function_lines.append(temp_function)
+
+        # Now we parse each exported function to extract its declaration
+        for func in function_lines:
+            function_lines_split = func.split(",{")
+
+            if len(function_lines_split) < 2:
+                print ("ERR: Incorrect function line", func)
+                exit()
+
+            declarations = function_lines_split[0].split(",")
+            params = function_lines_split[1]
+
+            # Extract the content from the definitions
+            val_module = declarations[0]
+            val_module = val_module[val_module.find('("') + 2:val_module.find('")')]
+            val_function = declarations[1]
+            val_function = val_function[val_function.find('("') + 2:val_function.find('")')]
+
+            if declarations[2] == "SR_KEMIP_INT":
+                val_return = "int"
+            elif declarations[2] == "SR_KEMIP_STR":
+                val_return = "string"
+            elif declarations[2] == "SR_KEMIP_NONE":
+                val_return = "void"
+            elif declarations[2] == "SR_KEMIP_BOOL":
+                val_return = "bool"
+            elif declarations[2] == "SR_KEMIP_XVAL":
+                val_return = "xval"
+            else:
+                print("ERR: Invalid return value", declarations[2], func)
+                exit()
+
+            val_c_function = declarations[3].strip()
+
+            # Count how many parameters the KEMI C function expects
+            val_params = []
+            itr = 0
+            for val in params.rstrip("},").split(","):
+                itr += 1
+                # KEMI function has a maximum of 6 params
+                if itr > 6:
+                    break
+                pm = val.strip()
+                if pm == "SR_KEMIP_INT":
+                    val_params.append("int")
+                elif pm == "SR_KEMIP_STR":
+                    val_params.append("str")
+                elif pm == "SR_KEMIP_NONE":
+                    continue
+                else:
+                    print("Invalid return value", declarations[2], func)
+                    exit()
+
+            if itr != 6:
+                print("ERR: Couldn't iterate the params: ", params)
+                exit()
+
+            param_string = self.find_c_function_params(filename, val_c_function, val_return)
+            param_string = self.prettify_params_list(val_function, param_string, val_params)
+
+            elements.append({"module": val_module, "name": val_function, "ret": val_return, "params": param_string})
+
+        return elements
+
+    def prettify_params_list(self, function_name, function_declaration, kemi_types):
+        # Validate the quantity and types of declaration vs export
+        if function_declaration == "" and len(kemi_types) == 0:
+            return ""
+
+        params = function_declaration.split(",")
+
+        if params[0].find("sip_msg_t") >= 0 or params[0].find("struct sip_msg") >= 0:
+            params.pop(0)
+
+        if len(params) != len(kemi_types):
+            print("ERR: Mismatching quantity of params. Declaration for", function_name, ":", function_declaration, "KEMI:", kemi_types)
+            exit()
+
+        for declared, type in zip(params, kemi_types):
+            declared = declared.replace("*", "")
+            declared = declared.strip().split(" ")[0]
+            if declared != type:
+                print("ERR: Mismatching type of params for", function_name, ":", function_declaration, " | ", kemi_types, " | Declared: ", declared, " Type: ", type)
+                exit()
+
+        param_string = ""
+
+        for param in params:
+            param = param.strip()
+            param = param.replace("*", "")
+            if param[:3] == "str" or param[:3] == "int":
+                temp = param.split(" ")
+                param = "p_" + temp[1]
+            param_string += param + ", "
+
+        # Clean up the presentation of the params
+        param_string = param_string.rstrip(", ")
+        return param_string
+
+    def find_c_function_params(self, filename, function_name, return_type):
+        # First we try with the same file to find the declaration
+        param_string = self.search_file_for_function_declaration(filename, function_name, return_type)
+        # If we couldn't find it, let's try all files in the same folder as the first file
+        if param_string:
+            return param_string
+        else:
+            files = self.list_c_files_in_directory(os.path.dirname(filename))
+            for file in files:
+                param_string = self.search_file_for_function_declaration(file, function_name, return_type)
+                if param_string:
+                    return param_string
+
+        if function_name in self.macro_functions:
+            return self.macro_functions[function_name]
+
+        print("ERR: Couldn't find the function declaration", filename, function_name, return_type)
+        exit()
+
+    def search_file_for_function_declaration(self, filename, function_name, return_type):
+        # print "Searching file", filename, "for", function_name
+        with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
+            lines = f.readlines()
+
+        param_string = None
+        found = False
+        temp_string = ""
+        return_match = return_type
+
+        # KEMI has some magic where most functions actually return INTs but KEMI maps them to void/bool
+        if return_type == "void" or return_type == "bool":
+            return_match = "int"
+
+        if return_type == "xval":
+            return_match = "sr_kemi_xval_t([ \t])*\*"
+
+        # Look for declarations in format:    static? return_type function_name(
+        r = re.compile("^(?:static )?" + return_match + "[ \t]*(" + function_name + ")[ \t]*\(")
+        for line in lines:
+            m = r.match(line)
+            if m:
+                found = True
+            if found:
+                temp_string += line
+                if line.find("{") >= 0:
+                    param_string = temp_string[temp_string.find('(') + 1:temp_string.find(')')]
+                    break
+
+        return param_string
+
+    def list_c_files_in_directory(self, path):
+        matches = []
+        for root, dirnames, filenames in os.walk(path):
+            for filename in fnmatch.filter(filenames, '*.c'):
+                matches.append(os.path.join(root, filename))
+        return matches
+
+
+if __name__ == "__main__":
+    try:
+        if not os.path.isdir(sys.argv[1]):
+            raise Exception('Not a valid directory')
+    except:
+        print("Please provide the path to the Kamailio src folder as the first argument")
+        exit()
+
+    print("Parsing the source")
+    parser = KemiFileExportParser()
+    data = parser.generate_kemi_export_list(sys.argv[1].rstrip("/"))
+    fgen = ModuleObjGenerator()
+    fgen.execute(data)
+    print("Done")

+ 357 - 0
misc/tools/kemi/lua/kemiluacheck.py

@@ -0,0 +1,357 @@
+# tool to generate the modules.md content
+#
+
+import os, json, sys, time, fnmatch, re, importlib
+
+PATH_GENERATED_OBJ = "KSR.luacheckrc"
+
+class ModuleObjGenerator(object):
+
+    # Contains the output until it should be written to disk
+    markdown_string = ""
+
+    def execute(self, data):
+        # Validate that we got some methods back. 155 is an arbitrary large number.
+        if len(data) < 1:
+            print("ERR: Invalid data")
+            exit()
+
+        functions_parsed = self.parse_function_list(data)
+        self.output_obj(functions_parsed)
+
+        print ("Markdown doc created successfully at " + PATH_GENERATED_OBJ)
+
+    def parse_function_list(self, functions):
+        data = {}
+
+        for elem in functions:
+            module = elem["module"]
+
+            # TODO: What about the hdr, pv, x sub-module?
+            if module == "":
+                module = "_core"
+
+            if module not in data:
+                data[module] = []
+
+            data[module].append({"name": elem["name"], "return": elem["ret"], "params": elem["params"]})
+
+        return data
+
+    def output_obj(self, data):
+
+        for key in sorted(data):
+            methods = data[key]
+            # Sort the functions by name alphabetically
+            methods = sorted(methods, key = lambda k: k['name'])
+            self.object_module_content(key, methods)
+
+        self.obj_write()
+
+        return True
+
+    def object_module_content(self, module, methods):
+        if module != "_core":
+            self.markdown_string += "\t\t\t\t" + module + " = {\n\t\t\t\t\tfields= {\n"
+
+        for value in methods:
+            if module != "_core":
+                self.markdown_string += "\t\t\t\t\t\t" + value["name"] + " = {},\n"
+            else:
+                self.markdown_string += "\t\t\t\t" + value["name"] + " = {},\n"
+
+        if module != "_core":
+            self.markdown_string += "\t\t\t\t\t},\n\t\t\t\t},\n"
+        return True
+
+    def obj_write(self):
+        f = open(PATH_GENERATED_OBJ, "w")
+        f.write("stds.KSR = {\n\tread_globals = {\n\t\tKSR = {\n\t\t\tfields = {\n")
+        f.write(self.markdown_string)
+        f.write("\t\t\t\tx = {\n\t\t\t\t\tfields= {\n")
+        f.write("\t\t\t\t\t\tdrop= {},\n")
+        f.write("\t\t\t\t\t\texit= {},\n")
+        f.write("\t\t\t\t\t\tmodf= {},\n")
+        f.write("\t\t\t\t\t},\n\t\t\t\t},\n")
+        f.write("\t\t\t}\n\t\t}\n\t}\n}\n")
+        f.write("\nstd = \"min+KSR\"\n")
+        f.close()
+        return True
+
+    def read_file_to_string(self, filename):
+        path = self.PATH_MODULES_DOCS + filename
+        if os.path.isfile(path):
+            with open(path, 'r') as myfile:
+                return myfile.read() + "\n"
+        return ""
+
+
+class KemiFileExportParser(object):
+    # These functions are created by a macro so makes the parsing somewhat tricky,
+    # for now they are statically defined
+    macro_functions = {
+        "t_set_auto_inv_100": "int state",
+        "t_set_disable_6xx": "int state",
+        "t_set_disable_failover": "int state",
+        "t_set_no_e2e_cancel_reason": "int state",
+        "t_set_disable_internal_reply": "int state"
+    }
+
+    # These files export the KEMI functions in a special way so we map them manually
+    # TODO: Discuss with @miconda if core/HDR/pv/x should be added as well or not
+    special_exports = [
+        {"filename": "kemi.c", "export": "_sr_kemi_core", "folder": "/src/core/"},
+        {"filename": "kemi.c", "export": "_sr_kemi_hdr", "folder": "/src/core/"},
+        {"filename": "kemi.c", "export": "_sr_kemi_pv", "folder": "/src/core/"},
+        #{"filename": "app_lua_mod.c", "export": "sr_kemi_app_lua_rpc_exports", "folder": "/modules/app_lua/"}
+    ]
+
+    def generate_kemi_export_list(self, source_path):
+        files = self.list_c_files_in_directory(source_path)
+        lists = []
+
+        for file in files:
+            with open(file, 'r', encoding='utf-8', errors='ignore') as f:
+                lines = f.readlines()
+
+            export_name = self.find_c_file_kemi_export(file, lines)
+            if export_name:
+                export_functions = self.extract_c_file_kemi_export_lines(file, lines, export_name)
+                lists = lists + export_functions
+                print ("Found ", len(export_functions), "functions", "Total:", len(lists))
+
+        # Handle some special files separately
+        for elem in self.special_exports:
+            file = source_path + elem["folder"] + elem["filename"]
+            with open(file) as f:
+                lines = f.readlines()
+            lists = lists + self.extract_c_file_kemi_export_lines(file, lines, elem["export"])
+
+        return lists
+
+    def find_c_file_kemi_export(self, filename, lines):
+        param = None
+
+        for line in lines:
+            if line.find("sr_kemi_modules_add") >= 0:
+                line = line.lstrip(" ")
+                line = line.lstrip("\t")
+                if line.find("sr_kemi_modules_add") == 0:
+                    param = line[line.find("(") + 1:line.find(")")]
+                    print ("INFO: ---- Found export", filename, param)
+                    break
+                else:
+                    if line != "int sr_kemi_modules_add(sr_kemi_t *klist)\n":
+                        print ("ERR: Possible error at line: ", filename, line)
+                        exit()
+
+        return param
+
+    def extract_c_file_kemi_export_lines(self, filename, lines, export_name):
+        list_functions = []
+        find_start = True
+
+        for line in lines:
+            if find_start and line.find("static sr_kemi_t " + export_name + "[]") >= 0:
+                find_start = False
+            elif not find_start:
+                if line.find("};") >= 0:
+                    break
+                line = line.lstrip(" \t")
+                line = line.rstrip()
+                list_functions.append(line)
+
+        if len(list_functions) < 1:
+            print ("ERR: Couldn't parse file for exported functions: ", export_name)
+            exit()
+
+        parsed_list = self.parse_kemi_export_c_lines(filename, list_functions)
+
+        return parsed_list
+
+    def parse_kemi_export_c_lines(self, filename, lines):
+        elements = []
+        function_lines = []
+        temp_function = ""
+
+        # We look for str_init which would be the start of each exported function
+        for line in lines:
+            if line.find("str_init") >= 0:
+                if temp_function != "":
+                    function_lines.append(temp_function)
+                    temp_function = ""
+            temp_function += line
+
+        if temp_function != "":
+            function_lines.append(temp_function)
+
+        # Now we parse each exported function to extract its declaration
+        for func in function_lines:
+            function_lines_split = func.split(",{")
+
+            if len(function_lines_split) < 2:
+                print ("ERR: Incorrect function line", func)
+                exit()
+
+            declarations = function_lines_split[0].split(",")
+            params = function_lines_split[1]
+
+            # Extract the content from the definitions
+            val_module = declarations[0]
+            val_module = val_module[val_module.find('("') + 2:val_module.find('")')]
+            val_function = declarations[1]
+            val_function = val_function[val_function.find('("') + 2:val_function.find('")')]
+
+            if declarations[2] == "SR_KEMIP_INT":
+                val_return = "int"
+            elif declarations[2] == "SR_KEMIP_STR":
+                val_return = "string"
+            elif declarations[2] == "SR_KEMIP_NONE":
+                val_return = "void"
+            elif declarations[2] == "SR_KEMIP_BOOL":
+                val_return = "bool"
+            elif declarations[2] == "SR_KEMIP_XVAL":
+                val_return = "xval"
+            else:
+                print("ERR: Invalid return value", declarations[2], func)
+                exit()
+
+            val_c_function = declarations[3].strip()
+
+            # Count how many parameters the KEMI C function expects
+            val_params = []
+            itr = 0
+            for val in params.rstrip("},").split(","):
+                itr += 1
+                # KEMI function has a maximum of 6 params
+                if itr > 6:
+                    break
+                pm = val.strip()
+                if pm == "SR_KEMIP_INT":
+                    val_params.append("int")
+                elif pm == "SR_KEMIP_STR":
+                    val_params.append("str")
+                elif pm == "SR_KEMIP_NONE":
+                    continue
+                else:
+                    print("Invalid return value", declarations[2], func)
+                    exit()
+
+            if itr != 6:
+                print("ERR: Couldn't iterate the params: ", params)
+                exit()
+
+            param_string = self.find_c_function_params(filename, val_c_function, val_return)
+            param_string = self.prettify_params_list(val_function, param_string, val_params)
+
+            elements.append({"module": val_module, "name": val_function, "ret": val_return, "params": param_string})
+
+        return elements
+
+    def prettify_params_list(self, function_name, function_declaration, kemi_types):
+        # Validate the quantity and types of declaration vs export
+        if function_declaration == "" and len(kemi_types) == 0:
+            return ""
+
+        params = function_declaration.split(",")
+
+        if params[0].find("sip_msg_t") >= 0 or params[0].find("struct sip_msg") >= 0:
+            params.pop(0)
+
+        if len(params) != len(kemi_types):
+            print("ERR: Mismatching quantity of params. Declaration for", function_name, ":", function_declaration, "KEMI:", kemi_types)
+            exit()
+
+        for declared, type in zip(params, kemi_types):
+            declared = declared.replace("*", "")
+            declared = declared.strip().split(" ")[0]
+            if declared != type:
+                print("ERR: Mismatching type of params for", function_name, ":", function_declaration, " | ", kemi_types, " | Declared: ", declared, " Type: ", type)
+                exit()
+
+        param_string = ""
+
+        for param in params:
+            param = param.strip()
+            param = param.replace("*", "")
+            if param[:3] == "str":
+                temp = param.split(" ")
+                param = "p_" + temp[1]
+            param_string += param + ", "
+
+        # Clean up the presentation of the params
+        param_string = param_string.rstrip(", ")
+        return param_string
+
+    def find_c_function_params(self, filename, function_name, return_type):
+        # First we try with the same file to find the declaration
+        param_string = self.search_file_for_function_declaration(filename, function_name, return_type)
+        # If we couldn't find it, let's try all files in the same folder as the first file
+        if param_string:
+            return param_string
+        else:
+            files = self.list_c_files_in_directory(os.path.dirname(filename))
+            for file in files:
+                param_string = self.search_file_for_function_declaration(file, function_name, return_type)
+                if param_string:
+                    return param_string
+
+        if function_name in self.macro_functions:
+            return self.macro_functions[function_name]
+
+        print("ERR: Couldn't find the function declaration", filename, function_name, return_type)
+        exit()
+
+    def search_file_for_function_declaration(self, filename, function_name, return_type):
+        # print "Searching file", filename, "for", function_name
+        with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
+            lines = f.readlines()
+
+        param_string = None
+        found = False
+        temp_string = ""
+        return_match = return_type
+
+        # KEMI has some magic where most functions actually return INTs but KEMI maps them to void/bool
+        if return_type == "void" or return_type == "bool":
+            return_match = "int"
+
+        if return_type == "xval":
+            return_match = "sr_kemi_xval_t([ \t])*\*"
+
+        # Look for declarations in format:    static? return_type function_name(
+        r = re.compile("^(?:static )?" + return_match + "[ \t]*(" + function_name + ")[ \t]*\(")
+        for line in lines:
+            m = r.match(line)
+            if m:
+                found = True
+            if found:
+                temp_string += line
+                if line.find("{") >= 0:
+                    param_string = temp_string[temp_string.find('(') + 1:temp_string.find(')')]
+                    break
+
+        return param_string
+
+    def list_c_files_in_directory(self, path):
+        matches = []
+        for root, dirnames, filenames in os.walk(path):
+            for filename in fnmatch.filter(filenames, '*.c'):
+                matches.append(os.path.join(root, filename))
+        return matches
+
+
+if __name__ == "__main__":
+    try:
+        if not os.path.isdir(sys.argv[1]):
+            raise Exception('Not a valid directory')
+    except:
+        print("Please provide the path to the Kamailio src folder as the first argument")
+        exit()
+
+    print("Parsing the source")
+    parser = KemiFileExportParser()
+    data = parser.generate_kemi_export_list(sys.argv[1].rstrip("/"))
+    fgen = ModuleObjGenerator()
+    fgen.execute(data)
+    print("Done")