jvpp: fix memory allocation for variable lenght messages (VPP-841)
[vpp.git] / src / vpp-api / java / jvpp / gen / jvppgen / jvpp_c_gen.py
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2016 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 # l
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #
16
17 import os, util
18 from string import Template
19
20 import jni_gen
21
22
23 def is_manually_generated(f_name, plugin_name):
24     return f_name in {'control_ping_reply'}
25
26
27 class_reference_template = Template("""jclass ${ref_name}Class;
28 """)
29
30 find_class_invocation_template = Template("""
31     ${ref_name}Class = (jclass)(*env)->NewGlobalRef(env, (*env)->FindClass(env, "io/fd/vpp/jvpp/${plugin_name}/dto/${class_name}"));
32     if ((*env)->ExceptionCheck(env)) {
33         (*env)->ExceptionDescribe(env);
34         return JNI_ERR;
35     }""")
36
37 find_class_template = Template("""
38     ${ref_name}Class = (jclass)(*env)->NewGlobalRef(env, (*env)->FindClass(env, "${class_name}"));
39     if ((*env)->ExceptionCheck(env)) {
40         (*env)->ExceptionDescribe(env);
41         return JNI_ERR;
42     }""")
43
44 delete_class_invocation_template = Template("""
45     if (${ref_name}Class) {
46         (*env)->DeleteGlobalRef(env, ${ref_name}Class);
47     }""")
48
49 class_cache_template = Template("""
50 $class_references
51 static int cache_class_references(JNIEnv* env) {
52     $find_class_invocations
53     return 0;
54 }
55
56 static void delete_class_references(JNIEnv* env) {
57     $delete_class_invocations
58 }""")
59
60
61 def generate_class_cache(func_list, plugin_name):
62     class_references = []
63     find_class_invocations = []
64     delete_class_invocations = []
65     for f in func_list:
66         c_name = f['name']
67         class_name = util.underscore_to_camelcase_upper(c_name)
68         ref_name = util.underscore_to_camelcase(c_name)
69
70         if util.is_ignored(c_name) or util.is_control_ping(class_name):
71             continue
72
73         if util.is_reply(class_name):
74             class_references.append(class_reference_template.substitute(
75                 ref_name=ref_name))
76             find_class_invocations.append(find_class_invocation_template.substitute(
77                 plugin_name=plugin_name,
78                 ref_name=ref_name,
79                 class_name=class_name))
80             delete_class_invocations.append(delete_class_invocation_template.substitute(ref_name=ref_name))
81         elif util.is_notification(c_name):
82             class_references.append(class_reference_template.substitute(
83                 ref_name=util.add_notification_suffix(ref_name)))
84             find_class_invocations.append(find_class_invocation_template.substitute(
85                 plugin_name=plugin_name,
86                 ref_name=util.add_notification_suffix(ref_name),
87                 class_name=util.add_notification_suffix(class_name)))
88             delete_class_invocations.append(delete_class_invocation_template.substitute(
89                 ref_name=util.add_notification_suffix(ref_name)))
90
91     # add exception class to class cache
92     ref_name = 'callbackException'
93     class_name = 'io/fd/vpp/jvpp/VppCallbackException'
94     class_references.append(class_reference_template.substitute(
95             ref_name=ref_name))
96     find_class_invocations.append(find_class_template.substitute(
97             ref_name=ref_name,
98             class_name=class_name))
99     delete_class_invocations.append(delete_class_invocation_template.substitute(ref_name=ref_name))
100
101     return class_cache_template.substitute(
102         class_references="".join(class_references), find_class_invocations="".join(find_class_invocations),
103         delete_class_invocations="".join(delete_class_invocations))
104
105
106 # TODO: cache method and field identifiers to achieve better performance
107 # https://jira.fd.io/browse/HONEYCOMB-42
108 request_class_template = Template("""
109     jclass requestClass = (*env)->FindClass(env, "io/fd/vpp/jvpp/${plugin_name}/dto/${java_name_upper}");""")
110
111 request_field_identifier_template = Template("""
112     jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${object_name}Class, "${field_name}", "${jni_signature}");
113     ${jni_type} ${field_reference_name} = (*env)->Get${jni_getter}(env, ${object_name}, ${field_reference_name}FieldId);
114     """)
115
116 jni_msg_size_template = Template(""" + ${array_length}*sizeof(${element_type})""")
117
118 jni_impl_template = Template("""
119 /**
120  * JNI binding for sending ${c_name} message.
121  * Generated based on $inputfile preparsed data:
122 $api_data
123  */
124 JNIEXPORT jint JNICALL Java_io_fd_vpp_jvpp_${plugin_name}_JVpp${java_plugin_name}Impl_${field_name}0
125 (JNIEnv * env, jclass clazz$args) {
126     ${plugin_name}_main_t *plugin_main = &${plugin_name}_main;
127     vl_api_${c_name}_t * mp;
128     u32 my_context_id = vppjni_get_context_id (&jvpp_main);
129     $request_class
130
131     $jni_identifiers
132
133     // create message:
134     mp = vl_msg_api_alloc(${msg_size});
135     memset (mp, 0, ${msg_size});
136     mp->_vl_msg_id = ntohs (get_message_id(env, "${c_name}_${crc}"));
137     mp->client_index = plugin_main->my_client_index;
138     mp->context = clib_host_to_net_u32 (my_context_id);
139
140     $msg_initialization
141
142     // send message:
143     vl_msg_api_send_shmem (plugin_main->vl_input_queue, (u8 *)&mp);
144     if ((*env)->ExceptionCheck(env)) {
145         return JNI_ERR;
146     }
147     return my_context_id;
148 }""")
149
150 def generate_jni_impl(func_list, plugin_name, inputfile):
151     jni_impl = []
152     for f in func_list:
153         f_name = f['name']
154         camel_case_function_name = util.underscore_to_camelcase(f_name)
155         if is_manually_generated(f_name, plugin_name) or util.is_reply(camel_case_function_name) \
156                 or util.is_ignored(f_name) or util.is_just_notification(f_name):
157             continue
158
159         arguments = ''
160         request_class = ''
161         jni_identifiers = ''
162         msg_initialization = ''
163         f_name_uppercase = f_name.upper()
164         msg_size = 'sizeof(*mp)'
165
166         if f['args']:
167             arguments = ', jobject request'
168             camel_case_function_name_upper = util.underscore_to_camelcase_upper(f_name)
169
170             request_class = request_class_template.substitute(
171                     java_name_upper=camel_case_function_name_upper,
172                     plugin_name=plugin_name)
173
174             for t in zip(f['types'], f['args'], f['lengths'], f['arg_types']):
175                 field_name = util.underscore_to_camelcase(t[1])
176                 is_variable_len_array = t[2][1]
177                 if is_variable_len_array:
178                     msg_size += jni_msg_size_template.substitute(array_length=util.underscore_to_camelcase(t[2][0]),
179                                                                  element_type=t[3])
180                 jni_identifiers += jni_gen.jni_request_identifiers_for_type(field_type=t[0],
181                                                                             field_reference_name=field_name,
182                                                                             field_name=field_name)
183                 msg_initialization += jni_gen.jni_request_binding_for_type(field_type=t[0], c_name=t[1],
184                                                                            field_reference_name=field_name,
185                                                                            field_length=t[2][0],
186                                                                            is_variable_len_array=is_variable_len_array)
187
188         jni_impl.append(jni_impl_template.substitute(
189                 inputfile=inputfile,
190                 api_data=util.api_message_to_javadoc(f),
191                 field_reference_name=camel_case_function_name,
192                 field_name=camel_case_function_name,
193                 c_name_uppercase=f_name_uppercase,
194                 c_name=f_name,
195                 crc=f['crc'],
196                 plugin_name=plugin_name,
197                 java_plugin_name=plugin_name.title(),
198                 request_class=request_class,
199                 jni_identifiers=jni_identifiers,
200                 msg_size=msg_size,
201                 msg_initialization=msg_initialization,
202                 args=arguments))
203
204     return "\n".join(jni_impl)
205
206 # code fragment for checking result of the operation before sending request reply
207 callback_err_handler_template = Template("""
208     // for negative result don't send callback message but send error callback
209     if (mp->retval<0) {
210         call_on_error("${handler_name}", mp->context, mp->retval, plugin_main->callbackClass, plugin_main->callbackObject, callbackExceptionClass);
211         return;
212     }
213     if (mp->retval == VNET_API_ERROR_IN_PROGRESS) {
214         clib_warning("Result in progress");
215         return;
216     }
217 """)
218
219 msg_handler_template = Template("""
220 /**
221  * Handler for ${handler_name} message.
222  * Generated based on $inputfile preparsed data:
223 $api_data
224  */
225 static void vl_api_${handler_name}_t_handler (vl_api_${handler_name}_t * mp)
226 {
227     ${plugin_name}_main_t *plugin_main = &${plugin_name}_main;
228     JNIEnv *env = jvpp_main.jenv;
229
230     $err_handler
231
232     jmethodID constructor = (*env)->GetMethodID(env, ${class_ref_name}Class, "<init>", "()V");
233     jmethodID callbackMethod = (*env)->GetMethodID(env, plugin_main->callbackClass, "on${dto_name}", "(Lio/fd/vpp/jvpp/${plugin_name}/dto/${dto_name};)V");
234
235     jobject dto = (*env)->NewObject(env, ${class_ref_name}Class, constructor);
236     $dto_setters
237
238     (*env)->CallVoidMethod(env, plugin_main->callbackObject, callbackMethod, dto);
239     // free DTO as per http://stackoverflow.com/questions/1340938/memory-leak-when-calling-java-code-from-c-using-jni
240     (*env)->DeleteLocalRef(env, dto);
241 }""")
242
243
244 def generate_msg_handlers(func_list, plugin_name, inputfile):
245     handlers = []
246     for f in func_list:
247         handler_name = f['name']
248         dto_name = util.underscore_to_camelcase_upper(handler_name)
249         ref_name = util.underscore_to_camelcase(handler_name)
250
251         if is_manually_generated(handler_name, plugin_name) or util.is_ignored(handler_name):
252             continue
253
254         if not util.is_reply(dto_name) and not util.is_notification(handler_name):
255             continue
256
257         if util.is_notification(handler_name):
258             dto_name = util.add_notification_suffix(dto_name)
259             ref_name = util.add_notification_suffix(ref_name)
260
261         dto_setters = ''
262         err_handler = ''
263         # dto setters
264         for t in zip(f['types'], f['args'], f['lengths']):
265             c_name = t[1]
266             java_name = util.underscore_to_camelcase(c_name)
267             field_length = t[2][0]
268             is_variable_len_array = t[2][1]
269             length_field_type = None
270             if is_variable_len_array:
271                 length_field_type = f['types'][f['args'].index(field_length)]
272             dto_setters += jni_gen.jni_reply_handler_for_type(handler_name=handler_name, ref_name=ref_name,
273                                                               field_type=t[0], c_name=t[1],
274                                                               field_reference_name=java_name,
275                                                               field_name=java_name, field_length=field_length,
276                                                               is_variable_len_array=is_variable_len_array,
277                                                               length_field_type=length_field_type)
278
279             # for retval don't generate setters and generate retval check
280             if util.is_retval_field(c_name):
281                 err_handler = callback_err_handler_template.substitute(
282                     handler_name=handler_name
283                 )
284                 continue
285
286         handlers.append(msg_handler_template.substitute(
287             inputfile=inputfile,
288             api_data=util.api_message_to_javadoc(f),
289             handler_name=handler_name,
290             plugin_name=plugin_name,
291             dto_name=dto_name,
292             class_ref_name=ref_name,
293             dto_setters=dto_setters,
294             err_handler=err_handler))
295
296     return "\n".join(handlers)
297
298
299 handler_registration_template = Template("""_(${name}_${crc}, ${name}) \\
300 """)
301
302
303 def generate_handler_registration(func_list):
304     handler_registration = ["#define foreach_api_reply_handler \\\n"]
305     for f in func_list:
306         name = f['name']
307         camelcase_name = util.underscore_to_camelcase(f['name'])
308
309         if (not util.is_reply(camelcase_name) and not util.is_notification(name)) or util.is_ignored(name) \
310                 or util.is_control_ping(camelcase_name):
311             continue
312
313         handler_registration.append(handler_registration_template.substitute(
314             name=name,
315             crc=f['crc']))
316
317     return "".join(handler_registration)
318
319
320 api_verification_template = Template("""_(${name}_${crc}) \\
321 """)
322
323
324 def generate_api_verification(func_list):
325     api_verification = ["#define foreach_supported_api_message \\\n"]
326     for f in func_list:
327         name = f['name']
328
329         if util.is_ignored(name):
330             continue
331
332         api_verification.append(api_verification_template.substitute(
333             name=name,
334             crc=f['crc']))
335
336     return "".join(api_verification)
337
338
339 jvpp_c_template = Template("""/**
340  * This file contains JNI bindings for jvpp Java API.
341  * It was generated by jvpp_c_gen.py based on $inputfile
342  * (python representation of api file generated by vppapigen).
343  */
344
345 // JAVA class reference cache
346 $class_cache
347
348 // List of supported API messages used for verification
349 $api_verification
350
351 // JNI bindings
352 $jni_implementations
353
354 // Message handlers
355 $msg_handlers
356
357 // Registration of message handlers in vlib
358 $handler_registration
359 """)
360
361 def generate_jvpp(func_list, plugin_name, inputfile, path):
362     """ Generates jvpp C file """
363     print "Generating jvpp C"
364
365     class_cache = generate_class_cache(func_list, plugin_name)
366     jni_impl = generate_jni_impl(func_list, plugin_name, inputfile)
367     msg_handlers = generate_msg_handlers(func_list, plugin_name, inputfile)
368     handler_registration = generate_handler_registration(func_list)
369     api_verification = generate_api_verification(func_list)
370
371     jvpp_c_file = open("%s/jvpp_%s_gen.h" % (path, plugin_name), 'w')
372     jvpp_c_file.write(jvpp_c_template.substitute(
373             inputfile=inputfile,
374             class_cache=class_cache,
375             api_verification=api_verification,
376             jni_implementations=jni_impl,
377             msg_handlers=msg_handlers,
378             handler_registration=handler_registration))
379     jvpp_c_file.flush()
380     jvpp_c_file.close()
381