Browse code

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

Daniel-Constantin Mierla authored on 23/11/2020 13:21:12
Showing 3 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,88 @@
1
+# Tools For KEMI Lua #
2
+
3
+Tools useful for using Kamailio with KEMI Lua scripts.
4
+
5
+## kemiksrlib ##
6
+
7
+Generates the file `KSR.lua` with skeleton functions in the `KSR` object. The
8
+function parameters are prefixed with `p_` and the functions return:
9
+
10
+  * empty string - in case of functions returning `xval`
11
+  * `1` - in case of functions returning `int`
12
+  * `true` - in case of functions returning `int`
13
+  * nothing - in case of void functions
14
+  * `nil` - in the other cases
15
+
16
+Useful to use for syntax checking and functions prototypes.
17
+
18
+Usage:
19
+
20
+```
21
+python3 kemiksrlib.py /path/to/src/kamailio
22
+```
23
+
24
+## kemiluacheck.py ##
25
+
26
+Generates the file `KSR.luacheckrc` that can be used with `luacheck` to verify
27
+if the KEMI Lua script uses undefined symbols (e.g., functions that are not
28
+in the `KSR` library due to typos).
29
+
30
+Usage:
31
+
32
+```
33
+python3 kemiluacheck.py /path/to/src/kamailio
34
+
35
+luacheck --config KSR.luacheckrc kamailio-basic-kemi-lua.lua -d
36
+```
37
+
38
+Example output with `KSR` correct functions:
39
+
40
+```
41
+Checking kamailio-basic-kemi-lua.lua              5 warnings
42
+
43
+    kamailio-basic-kemi-lua.lua:67:10: unused global variable ksr_request_route
44
+    kamailio-basic-kemi-lua.lua:389:10: unused global variable ksr_branch_manage
45
+    kamailio-basic-kemi-lua.lua:398:10: unused global variable ksr_onreply_manage
46
+    kamailio-basic-kemi-lua.lua:409:10: unused global variable ksr_failure_manage
47
+    kamailio-basic-kemi-lua.lua:420:10: unused global variable ksr_reply_route
48
+
49
+Total: 5 warnings / 0 errors in 1 file
50
+```
51
+
52
+Example output with `KSR.is_CANCLE()` typo:
53
+
54
+```
55
+Checking kamailio-basic-kemi-lua.lua              6 warnings
56
+
57
+    kamailio-basic-kemi-lua.lua:67:10: unused global variable ksr_request_route
58
+    kamailio-basic-kemi-lua.lua:76:5: accessing undefined field is_CANCLE of global KSR
59
+    kamailio-basic-kemi-lua.lua:389:10: unused global variable ksr_branch_manage
60
+    kamailio-basic-kemi-lua.lua:398:10: unused global variable ksr_onreply_manage
61
+    kamailio-basic-kemi-lua.lua:409:10: unused global variable ksr_failure_manage
62
+    kamailio-basic-kemi-lua.lua:420:10: unused global variable ksr_reply_route
63
+
64
+Total: 6 warnings / 0 errors in 1 file
65
+```
66
+
67
+## Other Tools ##
68
+
69
+Other external tools useful to check the Lua scripts.
70
+
71
+### luac ###
72
+
73
+The `luac` (the Lua compiler) can be used with parameter `-p` to check for
74
+syntax errors (e.g., missing closing quote or parenthesis).:w
75
+
76
+Example - when there is a missing closing quite, like:
77
+
78
+```
79
+KSR.hdr.remove("Route);
80
+```
81
+
82
+the check results in:
83
+
84
+```
85
+luac -p kamailio-basic-kemi-lua.lua
86
+
87
+luac: kamailio-basic-kemi-lua.lua:100: unfinished string near '"Route);'
88
+```
0 89
\ No newline at end of file
1 90
new file mode 100644
... ...
@@ -0,0 +1,374 @@
1
+# tool to generate the modules.md content
2
+#
3
+
4
+import os, json, sys, time, fnmatch, re, importlib
5
+
6
+PATH_GENERATED_OBJ = "KSR.lua"
7
+
8
+class ModuleObjGenerator(object):
9
+
10
+    # Contains the output until it should be written to disk
11
+    markdown_string = ""
12
+
13
+    def execute(self, data):
14
+        # Validate that we got some methods back. 155 is an arbitrary large number.
15
+        if len(data) < 1:
16
+            print("ERR: Invalid data")
17
+            exit()
18
+
19
+        functions_parsed = self.parse_function_list(data)
20
+        self.output_obj(functions_parsed)
21
+
22
+        print ("File created successfully at: " + PATH_GENERATED_OBJ)
23
+
24
+    def parse_function_list(self, functions):
25
+        data = {}
26
+
27
+        for elem in functions:
28
+            module = elem["module"]
29
+
30
+            # TODO: What about the hdr, pv, x sub-module?
31
+            if module == "":
32
+                module = "_core"
33
+
34
+            if module not in data:
35
+                data[module] = []
36
+
37
+            data[module].append({"name": elem["name"], "return": elem["ret"], "params": elem["params"]})
38
+
39
+        return data
40
+
41
+    def output_obj(self, data):
42
+
43
+        for key in sorted(data):
44
+            methods = data[key]
45
+            # Sort the functions by name alphabetically
46
+            methods = sorted(methods, key = lambda k: k['name'])
47
+            self.object_module_content(key, methods)
48
+        
49
+        self.obj_write()
50
+
51
+        return True
52
+
53
+    def object_module_content(self, module, methods):
54
+        if module == "_core":
55
+            module_prefix = ""
56
+        else:
57
+            module_prefix = module + "."
58
+
59
+        for value in methods:
60
+            self.markdown_string += "local function KSR." + module_prefix + value["name"]
61
+
62
+            # Sanitize the return values
63
+            if value["return"] == "none":
64
+                return_value = ""
65
+            elif value["return"] == "void":
66
+                return_value = ""
67
+            elif value["return"] == "xval":
68
+                return_value = " \"\""
69
+            elif value["return"] == "int":
70
+                return_value = " 1"
71
+            elif value["return"] == "bool":
72
+                return_value = " true"
73
+            else:
74
+                return_value = " nil"
75
+
76
+            # Sanitize the parameter values
77
+            if value["params"] == "none":
78
+                params_value = ""
79
+            else:
80
+                params_value = value["params"]
81
+
82
+            # Generate the output string for the markdown page
83
+            self.markdown_string += "(" + params_value + ")\n"
84
+            self.markdown_string += "\treturn" + return_value + ";\n"
85
+            self.markdown_string += "end\n\n"
86
+
87
+
88
+        return True
89
+
90
+    def obj_write(self):
91
+        f = open(PATH_GENERATED_OBJ, "w")
92
+        f.write("local KSR = {};\n\n")
93
+        f.write(self.markdown_string)
94
+        f.write("return KSR;\n")
95
+        f.close()
96
+        return True
97
+
98
+    def read_file_to_string(self, filename):
99
+        path = self.PATH_MODULES_DOCS + filename
100
+        if os.path.isfile(path):
101
+            with open(path, 'r') as myfile:
102
+                return myfile.read() + "\n"
103
+        return ""
104
+
105
+
106
+class KemiFileExportParser(object):
107
+    # These functions are created by a macro so makes the parsing somewhat tricky,
108
+    # for now they are statically defined
109
+    macro_functions = {
110
+        "t_set_auto_inv_100": "int state",
111
+        "t_set_disable_6xx": "int state",
112
+        "t_set_disable_failover": "int state",
113
+        "t_set_no_e2e_cancel_reason": "int state",
114
+        "t_set_disable_internal_reply": "int state"
115
+    }
116
+
117
+    # These files export the KEMI functions in a special way so we map them manually
118
+    # TODO: Discuss with @miconda if core/HDR/pv/x should be added as well or not
119
+    special_exports = [
120
+        {"filename": "kemi.c", "export": "_sr_kemi_core", "folder": "/src/core/"},
121
+        {"filename": "kemi.c", "export": "_sr_kemi_hdr", "folder": "/src/core/"},
122
+        {"filename": "kemi.c", "export": "_sr_kemi_pv", "folder": "/src/core/"},
123
+        #{"filename": "app_lua_mod.c", "export": "sr_kemi_app_lua_rpc_exports", "folder": "/modules/app_lua/"}
124
+    ]
125
+
126
+    def generate_kemi_export_list(self, source_path):
127
+        files = self.list_c_files_in_directory(source_path)
128
+        lists = []
129
+
130
+        for file in files:
131
+            with open(file, 'r', encoding='utf-8', errors='ignore') as f:
132
+                lines = f.readlines()
133
+
134
+            export_name = self.find_c_file_kemi_export(file, lines)
135
+            if export_name:
136
+                export_functions = self.extract_c_file_kemi_export_lines(file, lines, export_name)
137
+                lists = lists + export_functions
138
+                print ("Found ", len(export_functions), "functions", "Total:", len(lists))
139
+
140
+        # Handle some special files separately
141
+        for elem in self.special_exports:
142
+            file = source_path + elem["folder"] + elem["filename"]
143
+            with open(file) as f:
144
+                lines = f.readlines()
145
+            lists = lists + self.extract_c_file_kemi_export_lines(file, lines, elem["export"])
146
+
147
+        return lists
148
+
149
+    def find_c_file_kemi_export(self, filename, lines):
150
+        param = None
151
+
152
+        for line in lines:
153
+            if line.find("sr_kemi_modules_add") >= 0:
154
+                line = line.lstrip(" ")
155
+                line = line.lstrip("\t")
156
+                if line.find("sr_kemi_modules_add") == 0:
157
+                    param = line[line.find("(") + 1:line.find(")")]
158
+                    print ("INFO: ---- Found export", filename, param)
159
+                    break
160
+                else:
161
+                    if line != "int sr_kemi_modules_add(sr_kemi_t *klist)\n":
162
+                        print ("ERR: Possible error at line: ", filename, line)
163
+                        exit()
164
+
165
+        return param
166
+
167
+    def extract_c_file_kemi_export_lines(self, filename, lines, export_name):
168
+        list_functions = []
169
+        find_start = True
170
+
171
+        for line in lines:
172
+            if find_start and line.find("static sr_kemi_t " + export_name + "[]") >= 0:
173
+                find_start = False
174
+            elif not find_start:
175
+                if line.find("};") >= 0:
176
+                    break
177
+                line = line.lstrip(" \t")
178
+                line = line.rstrip()
179
+                list_functions.append(line)
180
+
181
+        if len(list_functions) < 1:
182
+            print ("ERR: Couldn't parse file for exported functions: ", export_name)
183
+            exit()
184
+
185
+        parsed_list = self.parse_kemi_export_c_lines(filename, list_functions)
186
+
187
+        return parsed_list
188
+
189
+    def parse_kemi_export_c_lines(self, filename, lines):
190
+        elements = []
191
+        function_lines = []
192
+        temp_function = ""
193
+
194
+        # We look for str_init which would be the start of each exported function
195
+        for line in lines:
196
+            if line.find("str_init") >= 0:
197
+                if temp_function != "":
198
+                    function_lines.append(temp_function)
199
+                    temp_function = ""
200
+            temp_function += line
201
+
202
+        if temp_function != "":
203
+            function_lines.append(temp_function)
204
+
205
+        # Now we parse each exported function to extract its declaration
206
+        for func in function_lines:
207
+            function_lines_split = func.split(",{")
208
+
209
+            if len(function_lines_split) < 2:
210
+                print ("ERR: Incorrect function line", func)
211
+                exit()
212
+
213
+            declarations = function_lines_split[0].split(",")
214
+            params = function_lines_split[1]
215
+
216
+            # Extract the content from the definitions
217
+            val_module = declarations[0]
218
+            val_module = val_module[val_module.find('("') + 2:val_module.find('")')]
219
+            val_function = declarations[1]
220
+            val_function = val_function[val_function.find('("') + 2:val_function.find('")')]
221
+
222
+            if declarations[2] == "SR_KEMIP_INT":
223
+                val_return = "int"
224
+            elif declarations[2] == "SR_KEMIP_STR":
225
+                val_return = "string"
226
+            elif declarations[2] == "SR_KEMIP_NONE":
227
+                val_return = "void"
228
+            elif declarations[2] == "SR_KEMIP_BOOL":
229
+                val_return = "bool"
230
+            elif declarations[2] == "SR_KEMIP_XVAL":
231
+                val_return = "xval"
232
+            else:
233
+                print("ERR: Invalid return value", declarations[2], func)
234
+                exit()
235
+
236
+            val_c_function = declarations[3].strip()
237
+
238
+            # Count how many parameters the KEMI C function expects
239
+            val_params = []
240
+            itr = 0
241
+            for val in params.rstrip("},").split(","):
242
+                itr += 1
243
+                # KEMI function has a maximum of 6 params
244
+                if itr > 6:
245
+                    break
246
+                pm = val.strip()
247
+                if pm == "SR_KEMIP_INT":
248
+                    val_params.append("int")
249
+                elif pm == "SR_KEMIP_STR":
250
+                    val_params.append("str")
251
+                elif pm == "SR_KEMIP_NONE":
252
+                    continue
253
+                else:
254
+                    print("Invalid return value", declarations[2], func)
255
+                    exit()
256
+
257
+            if itr != 6:
258
+                print("ERR: Couldn't iterate the params: ", params)
259
+                exit()
260
+
261
+            param_string = self.find_c_function_params(filename, val_c_function, val_return)
262
+            param_string = self.prettify_params_list(val_function, param_string, val_params)
263
+
264
+            elements.append({"module": val_module, "name": val_function, "ret": val_return, "params": param_string})
265
+
266
+        return elements
267
+
268
+    def prettify_params_list(self, function_name, function_declaration, kemi_types):
269
+        # Validate the quantity and types of declaration vs export
270
+        if function_declaration == "" and len(kemi_types) == 0:
271
+            return ""
272
+
273
+        params = function_declaration.split(",")
274
+
275
+        if params[0].find("sip_msg_t") >= 0 or params[0].find("struct sip_msg") >= 0:
276
+            params.pop(0)
277
+
278
+        if len(params) != len(kemi_types):
279
+            print("ERR: Mismatching quantity of params. Declaration for", function_name, ":", function_declaration, "KEMI:", kemi_types)
280
+            exit()
281
+
282
+        for declared, type in zip(params, kemi_types):
283
+            declared = declared.replace("*", "")
284
+            declared = declared.strip().split(" ")[0]
285
+            if declared != type:
286
+                print("ERR: Mismatching type of params for", function_name, ":", function_declaration, " | ", kemi_types, " | Declared: ", declared, " Type: ", type)
287
+                exit()
288
+
289
+        param_string = ""
290
+
291
+        for param in params:
292
+            param = param.strip()
293
+            param = param.replace("*", "")
294
+            if param[:3] == "str" or param[:3] == "int":
295
+                temp = param.split(" ")
296
+                param = "p_" + temp[1]
297
+            param_string += param + ", "
298
+
299
+        # Clean up the presentation of the params
300
+        param_string = param_string.rstrip(", ")
301
+        return param_string
302
+
303
+    def find_c_function_params(self, filename, function_name, return_type):
304
+        # First we try with the same file to find the declaration
305
+        param_string = self.search_file_for_function_declaration(filename, function_name, return_type)
306
+        # If we couldn't find it, let's try all files in the same folder as the first file
307
+        if param_string:
308
+            return param_string
309
+        else:
310
+            files = self.list_c_files_in_directory(os.path.dirname(filename))
311
+            for file in files:
312
+                param_string = self.search_file_for_function_declaration(file, function_name, return_type)
313
+                if param_string:
314
+                    return param_string
315
+
316
+        if function_name in self.macro_functions:
317
+            return self.macro_functions[function_name]
318
+
319
+        print("ERR: Couldn't find the function declaration", filename, function_name, return_type)
320
+        exit()
321
+
322
+    def search_file_for_function_declaration(self, filename, function_name, return_type):
323
+        # print "Searching file", filename, "for", function_name
324
+        with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
325
+            lines = f.readlines()
326
+
327
+        param_string = None
328
+        found = False
329
+        temp_string = ""
330
+        return_match = return_type
331
+
332
+        # KEMI has some magic where most functions actually return INTs but KEMI maps them to void/bool
333
+        if return_type == "void" or return_type == "bool":
334
+            return_match = "int"
335
+
336
+        if return_type == "xval":
337
+            return_match = "sr_kemi_xval_t([ \t])*\*"
338
+
339
+        # Look for declarations in format:    static? return_type function_name(
340
+        r = re.compile("^(?:static )?" + return_match + "[ \t]*(" + function_name + ")[ \t]*\(")
341
+        for line in lines:
342
+            m = r.match(line)
343
+            if m:
344
+                found = True
345
+            if found:
346
+                temp_string += line
347
+                if line.find("{") >= 0:
348
+                    param_string = temp_string[temp_string.find('(') + 1:temp_string.find(')')]
349
+                    break
350
+
351
+        return param_string
352
+
353
+    def list_c_files_in_directory(self, path):
354
+        matches = []
355
+        for root, dirnames, filenames in os.walk(path):
356
+            for filename in fnmatch.filter(filenames, '*.c'):
357
+                matches.append(os.path.join(root, filename))
358
+        return matches
359
+
360
+
361
+if __name__ == "__main__":
362
+    try:
363
+        if not os.path.isdir(sys.argv[1]):
364
+            raise Exception('Not a valid directory')
365
+    except:
366
+        print("Please provide the path to the Kamailio src folder as the first argument")
367
+        exit()
368
+
369
+    print("Parsing the source")
370
+    parser = KemiFileExportParser()
371
+    data = parser.generate_kemi_export_list(sys.argv[1].rstrip("/"))
372
+    fgen = ModuleObjGenerator()
373
+    fgen.execute(data)
374
+    print("Done")
0 375
new file mode 100644
... ...
@@ -0,0 +1,357 @@
1
+# tool to generate the modules.md content
2
+#
3
+
4
+import os, json, sys, time, fnmatch, re, importlib
5
+
6
+PATH_GENERATED_OBJ = "KSR.luacheckrc"
7
+
8
+class ModuleObjGenerator(object):
9
+
10
+    # Contains the output until it should be written to disk
11
+    markdown_string = ""
12
+
13
+    def execute(self, data):
14
+        # Validate that we got some methods back. 155 is an arbitrary large number.
15
+        if len(data) < 1:
16
+            print("ERR: Invalid data")
17
+            exit()
18
+
19
+        functions_parsed = self.parse_function_list(data)
20
+        self.output_obj(functions_parsed)
21
+
22
+        print ("Markdown doc created successfully at " + PATH_GENERATED_OBJ)
23
+
24
+    def parse_function_list(self, functions):
25
+        data = {}
26
+
27
+        for elem in functions:
28
+            module = elem["module"]
29
+
30
+            # TODO: What about the hdr, pv, x sub-module?
31
+            if module == "":
32
+                module = "_core"
33
+
34
+            if module not in data:
35
+                data[module] = []
36
+
37
+            data[module].append({"name": elem["name"], "return": elem["ret"], "params": elem["params"]})
38
+
39
+        return data
40
+
41
+    def output_obj(self, data):
42
+
43
+        for key in sorted(data):
44
+            methods = data[key]
45
+            # Sort the functions by name alphabetically
46
+            methods = sorted(methods, key = lambda k: k['name'])
47
+            self.object_module_content(key, methods)
48
+
49
+        self.obj_write()
50
+
51
+        return True
52
+
53
+    def object_module_content(self, module, methods):
54
+        if module != "_core":
55
+            self.markdown_string += "\t\t\t\t" + module + " = {\n\t\t\t\t\tfields= {\n"
56
+
57
+        for value in methods:
58
+            if module != "_core":
59
+                self.markdown_string += "\t\t\t\t\t\t" + value["name"] + " = {},\n"
60
+            else:
61
+                self.markdown_string += "\t\t\t\t" + value["name"] + " = {},\n"
62
+
63
+        if module != "_core":
64
+            self.markdown_string += "\t\t\t\t\t},\n\t\t\t\t},\n"
65
+        return True
66
+
67
+    def obj_write(self):
68
+        f = open(PATH_GENERATED_OBJ, "w")
69
+        f.write("stds.KSR = {\n\tread_globals = {\n\t\tKSR = {\n\t\t\tfields = {\n")
70
+        f.write(self.markdown_string)
71
+        f.write("\t\t\t\tx = {\n\t\t\t\t\tfields= {\n")
72
+        f.write("\t\t\t\t\t\tdrop= {},\n")
73
+        f.write("\t\t\t\t\t\texit= {},\n")
74
+        f.write("\t\t\t\t\t\tmodf= {},\n")
75
+        f.write("\t\t\t\t\t},\n\t\t\t\t},\n")
76
+        f.write("\t\t\t}\n\t\t}\n\t}\n}\n")
77
+        f.write("\nstd = \"min+KSR\"\n")
78
+        f.close()
79
+        return True
80
+
81
+    def read_file_to_string(self, filename):
82
+        path = self.PATH_MODULES_DOCS + filename
83
+        if os.path.isfile(path):
84
+            with open(path, 'r') as myfile:
85
+                return myfile.read() + "\n"
86
+        return ""
87
+
88
+
89
+class KemiFileExportParser(object):
90
+    # These functions are created by a macro so makes the parsing somewhat tricky,
91
+    # for now they are statically defined
92
+    macro_functions = {
93
+        "t_set_auto_inv_100": "int state",
94
+        "t_set_disable_6xx": "int state",
95
+        "t_set_disable_failover": "int state",
96
+        "t_set_no_e2e_cancel_reason": "int state",
97
+        "t_set_disable_internal_reply": "int state"
98
+    }
99
+
100
+    # These files export the KEMI functions in a special way so we map them manually
101
+    # TODO: Discuss with @miconda if core/HDR/pv/x should be added as well or not
102
+    special_exports = [
103
+        {"filename": "kemi.c", "export": "_sr_kemi_core", "folder": "/src/core/"},
104
+        {"filename": "kemi.c", "export": "_sr_kemi_hdr", "folder": "/src/core/"},
105
+        {"filename": "kemi.c", "export": "_sr_kemi_pv", "folder": "/src/core/"},
106
+        #{"filename": "app_lua_mod.c", "export": "sr_kemi_app_lua_rpc_exports", "folder": "/modules/app_lua/"}
107
+    ]
108
+
109
+    def generate_kemi_export_list(self, source_path):
110
+        files = self.list_c_files_in_directory(source_path)
111
+        lists = []
112
+
113
+        for file in files:
114
+            with open(file, 'r', encoding='utf-8', errors='ignore') as f:
115
+                lines = f.readlines()
116
+
117
+            export_name = self.find_c_file_kemi_export(file, lines)
118
+            if export_name:
119
+                export_functions = self.extract_c_file_kemi_export_lines(file, lines, export_name)
120
+                lists = lists + export_functions
121
+                print ("Found ", len(export_functions), "functions", "Total:", len(lists))
122
+
123
+        # Handle some special files separately
124
+        for elem in self.special_exports:
125
+            file = source_path + elem["folder"] + elem["filename"]
126
+            with open(file) as f:
127
+                lines = f.readlines()
128
+            lists = lists + self.extract_c_file_kemi_export_lines(file, lines, elem["export"])
129
+
130
+        return lists
131
+
132
+    def find_c_file_kemi_export(self, filename, lines):
133
+        param = None
134
+
135
+        for line in lines:
136
+            if line.find("sr_kemi_modules_add") >= 0:
137
+                line = line.lstrip(" ")
138
+                line = line.lstrip("\t")
139
+                if line.find("sr_kemi_modules_add") == 0:
140
+                    param = line[line.find("(") + 1:line.find(")")]
141
+                    print ("INFO: ---- Found export", filename, param)
142
+                    break
143
+                else:
144
+                    if line != "int sr_kemi_modules_add(sr_kemi_t *klist)\n":
145
+                        print ("ERR: Possible error at line: ", filename, line)
146
+                        exit()
147
+
148
+        return param
149
+
150
+    def extract_c_file_kemi_export_lines(self, filename, lines, export_name):
151
+        list_functions = []
152
+        find_start = True
153
+
154
+        for line in lines:
155
+            if find_start and line.find("static sr_kemi_t " + export_name + "[]") >= 0:
156
+                find_start = False
157
+            elif not find_start:
158
+                if line.find("};") >= 0:
159
+                    break
160
+                line = line.lstrip(" \t")
161
+                line = line.rstrip()
162
+                list_functions.append(line)
163
+
164
+        if len(list_functions) < 1:
165
+            print ("ERR: Couldn't parse file for exported functions: ", export_name)
166
+            exit()
167
+
168
+        parsed_list = self.parse_kemi_export_c_lines(filename, list_functions)
169
+
170
+        return parsed_list
171
+
172
+    def parse_kemi_export_c_lines(self, filename, lines):
173
+        elements = []
174
+        function_lines = []
175
+        temp_function = ""
176
+
177
+        # We look for str_init which would be the start of each exported function
178
+        for line in lines:
179
+            if line.find("str_init") >= 0:
180
+                if temp_function != "":
181
+                    function_lines.append(temp_function)
182
+                    temp_function = ""
183
+            temp_function += line
184
+
185
+        if temp_function != "":
186
+            function_lines.append(temp_function)
187
+
188
+        # Now we parse each exported function to extract its declaration
189
+        for func in function_lines:
190
+            function_lines_split = func.split(",{")
191
+
192
+            if len(function_lines_split) < 2:
193
+                print ("ERR: Incorrect function line", func)
194
+                exit()
195
+
196
+            declarations = function_lines_split[0].split(",")
197
+            params = function_lines_split[1]
198
+
199
+            # Extract the content from the definitions
200
+            val_module = declarations[0]
201
+            val_module = val_module[val_module.find('("') + 2:val_module.find('")')]
202
+            val_function = declarations[1]
203
+            val_function = val_function[val_function.find('("') + 2:val_function.find('")')]
204
+
205
+            if declarations[2] == "SR_KEMIP_INT":
206
+                val_return = "int"
207
+            elif declarations[2] == "SR_KEMIP_STR":
208
+                val_return = "string"
209
+            elif declarations[2] == "SR_KEMIP_NONE":
210
+                val_return = "void"
211
+            elif declarations[2] == "SR_KEMIP_BOOL":
212
+                val_return = "bool"
213
+            elif declarations[2] == "SR_KEMIP_XVAL":
214
+                val_return = "xval"
215
+            else:
216
+                print("ERR: Invalid return value", declarations[2], func)
217
+                exit()
218
+
219
+            val_c_function = declarations[3].strip()
220
+
221
+            # Count how many parameters the KEMI C function expects
222
+            val_params = []
223
+            itr = 0
224
+            for val in params.rstrip("},").split(","):
225
+                itr += 1
226
+                # KEMI function has a maximum of 6 params
227
+                if itr > 6:
228
+                    break
229
+                pm = val.strip()
230
+                if pm == "SR_KEMIP_INT":
231
+                    val_params.append("int")
232
+                elif pm == "SR_KEMIP_STR":
233
+                    val_params.append("str")
234
+                elif pm == "SR_KEMIP_NONE":
235
+                    continue
236
+                else:
237
+                    print("Invalid return value", declarations[2], func)
238
+                    exit()
239
+
240
+            if itr != 6:
241
+                print("ERR: Couldn't iterate the params: ", params)
242
+                exit()
243
+
244
+            param_string = self.find_c_function_params(filename, val_c_function, val_return)
245
+            param_string = self.prettify_params_list(val_function, param_string, val_params)
246
+
247
+            elements.append({"module": val_module, "name": val_function, "ret": val_return, "params": param_string})
248
+
249
+        return elements
250
+
251
+    def prettify_params_list(self, function_name, function_declaration, kemi_types):
252
+        # Validate the quantity and types of declaration vs export
253
+        if function_declaration == "" and len(kemi_types) == 0:
254
+            return ""
255
+
256
+        params = function_declaration.split(",")
257
+
258
+        if params[0].find("sip_msg_t") >= 0 or params[0].find("struct sip_msg") >= 0:
259
+            params.pop(0)
260
+
261
+        if len(params) != len(kemi_types):
262
+            print("ERR: Mismatching quantity of params. Declaration for", function_name, ":", function_declaration, "KEMI:", kemi_types)
263
+            exit()
264
+
265
+        for declared, type in zip(params, kemi_types):
266
+            declared = declared.replace("*", "")
267
+            declared = declared.strip().split(" ")[0]
268
+            if declared != type:
269
+                print("ERR: Mismatching type of params for", function_name, ":", function_declaration, " | ", kemi_types, " | Declared: ", declared, " Type: ", type)
270
+                exit()
271
+
272
+        param_string = ""
273
+
274
+        for param in params:
275
+            param = param.strip()
276
+            param = param.replace("*", "")
277
+            if param[:3] == "str":
278
+                temp = param.split(" ")
279
+                param = "p_" + temp[1]
280
+            param_string += param + ", "
281
+
282
+        # Clean up the presentation of the params
283
+        param_string = param_string.rstrip(", ")
284
+        return param_string
285
+
286
+    def find_c_function_params(self, filename, function_name, return_type):
287
+        # First we try with the same file to find the declaration
288
+        param_string = self.search_file_for_function_declaration(filename, function_name, return_type)
289
+        # If we couldn't find it, let's try all files in the same folder as the first file
290
+        if param_string:
291
+            return param_string
292
+        else:
293
+            files = self.list_c_files_in_directory(os.path.dirname(filename))
294
+            for file in files:
295
+                param_string = self.search_file_for_function_declaration(file, function_name, return_type)
296
+                if param_string:
297
+                    return param_string
298
+
299
+        if function_name in self.macro_functions:
300
+            return self.macro_functions[function_name]
301
+
302
+        print("ERR: Couldn't find the function declaration", filename, function_name, return_type)
303
+        exit()
304
+
305
+    def search_file_for_function_declaration(self, filename, function_name, return_type):
306
+        # print "Searching file", filename, "for", function_name
307
+        with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
308
+            lines = f.readlines()
309
+
310
+        param_string = None
311
+        found = False
312
+        temp_string = ""
313
+        return_match = return_type
314
+
315
+        # KEMI has some magic where most functions actually return INTs but KEMI maps them to void/bool
316
+        if return_type == "void" or return_type == "bool":
317
+            return_match = "int"
318
+
319
+        if return_type == "xval":
320
+            return_match = "sr_kemi_xval_t([ \t])*\*"
321
+
322
+        # Look for declarations in format:    static? return_type function_name(
323
+        r = re.compile("^(?:static )?" + return_match + "[ \t]*(" + function_name + ")[ \t]*\(")
324
+        for line in lines:
325
+            m = r.match(line)
326
+            if m:
327
+                found = True
328
+            if found:
329
+                temp_string += line
330
+                if line.find("{") >= 0:
331
+                    param_string = temp_string[temp_string.find('(') + 1:temp_string.find(')')]
332
+                    break
333
+
334
+        return param_string
335
+
336
+    def list_c_files_in_directory(self, path):
337
+        matches = []
338
+        for root, dirnames, filenames in os.walk(path):
339
+            for filename in fnmatch.filter(filenames, '*.c'):
340
+                matches.append(os.path.join(root, filename))
341
+        return matches
342
+
343
+
344
+if __name__ == "__main__":
345
+    try:
346
+        if not os.path.isdir(sys.argv[1]):
347
+            raise Exception('Not a valid directory')
348
+    except:
349
+        print("Please provide the path to the Kamailio src folder as the first argument")
350
+        exit()
351
+
352
+    print("Parsing the source")
353
+    parser = KemiFileExportParser()
354
+    data = parser.generate_kemi_export_list(sys.argv[1].rstrip("/"))
355
+    fgen = ModuleObjGenerator()
356
+    fgen.execute(data)
357
+    print("Done")