1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
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  *
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 #define LOG_TAG "vehicle-hal-tool"
18 
19 #include <inttypes.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <hardware/hardware.h>
23 #include <hardware/vehicle.h>
24 
25 #include <cutils/log.h>
26 
usage()27 void usage() {
28     printf("Usage: "
29             "./vehicle-hal-tool [-l] [-m -p -t [-v]]\n"
30             "-l - List properties\n"
31             "-m - Mode (cannot be used with -l). Accepted strings: get, set or sub.\n"
32             "-p - Property (only used with -m)\n"
33             "-t - Type (only used with -m)\n"
34             "-w - Wait time in seconds (only used with -m set to sub)\n"
35             "-v - Value to which vehicle_prop_value is set\n"
36             "Depending on the type pass the value:\n"
37             "Int: pass a quoted integer\n"
38             "Float: pass a quoted float\n"
39             "Int array: pass a quoted space delimited int array, eg: \"1 2 3 4\" for\n:"
40             "setting int32_array's all 4 elements (see VEHICLE_VALUE_TYPE_INT32_VEC4\n"
41             "String: pass a normal string\n\n"
42             "The configurations to use the tool are as follows:\n"
43             "List Properties\n"
44             "---------------\n"
45             "./vehicle-hal-tool -l \n"
46             "Lists the various properties defined in HAL implementation. Use this to check if "
47             "the HAL implementation is correctly set up and exposing the capabilities correctly.\n"
48 
49             "Get Properties\n"
50             "---------------\n"
51             "./vehicle-hal-tool -m get -p <prop> -t <type> [-v <vehicle_prop_value>]\n"
52             "Example: ./vehicle-hal-tool -m get -p 1028 -t 3 # VEHICLE_PROPERTY_DRIVING_STATUS\n"
53             "./vehicle-hal-tool -m get -p 257 -t 1 # VEHICLE_PROPERTY_INFO_MAKE\n"
54             "./vehicle-hal-tool -m get -p 2049 -t 19 -v \"3 0 0 0\"\n"
55             "                                 # VEHICLE_PROPERTY_RADIO_PRESET\n"
56             "with preset value set to 3.\n\n"
57             "Set properties\n"
58             "--------------\n"
59             "./vehicle-hal-tool -m set -p 10 -t 1 -v random_property\n"
60             "Set properties may not be applicable to most properties\n\n"
61             "Subscribe properties\n"
62             "--------------------\n"
63             "Subscribes to be notified about a property change (depending on whether\n"
64             "it is a on change property or a continuous property) for seconds provided\n"
65             "as -w paramter.\n"
66             "./vehicle-hal-tool -m sub -p 1028 -w 10\n"
67     );
68 }
69 
list_all_properties(vehicle_hw_device_t * device)70 void list_all_properties(vehicle_hw_device_t *device) {
71     int num_configs = -1;
72     const vehicle_prop_config_t *configs = device->list_properties(device, &num_configs);
73     if (num_configs < 0) {
74         printf("List configs error. %d", num_configs);
75         exit(1);
76     }
77 
78     printf("Listing configs\n--------------------\n");
79     int i = 0;
80     for (i = 0; i < num_configs; i++) {
81         const vehicle_prop_config_t *config_temp = configs + i;
82         printf("Property ID: %d\n"
83                "Property config_flags: %d\n"
84                "Property change mode: %d\n"
85                "Property min sample rate: %f\n"
86                "Property max sample rate: %f\n",
87                config_temp->prop, config_temp->config_flags, config_temp->change_mode,
88                config_temp->min_sample_rate, config_temp->max_sample_rate);
89     }
90 }
91 
print_property(const vehicle_prop_value_t * data)92 static void print_property(const vehicle_prop_value_t *data) {
93     switch (data->value_type) {
94         case VEHICLE_VALUE_TYPE_STRING:
95             printf("Value type: STRING\n Size: %d\n", data->value.str_value.len);
96             // This implementation only supports ASCII.
97             char *ascii_out = (char *) malloc((data->value.str_value.len + 1) * sizeof(char));
98             memcpy(ascii_out, data->value.str_value.data, data->value.str_value.len);
99             ascii_out[data->value.str_value.len] = '\0';
100             printf("Value Type: STRING %s\n", ascii_out);
101             free(ascii_out);
102             break;
103         case VEHICLE_VALUE_TYPE_BYTES:
104             printf("Value type: BYTES\n Size: %d", data->value.bytes_value.len);
105             for (int i = 0; i < data->value.bytes_value.len; i++) {
106                 if ((i % 16) == 0) {
107                     printf("\n %04X: ", i);
108                 }
109                 printf("%02X ", data->value.bytes_value.data[i]);
110             }
111             printf("\n");
112             break;
113         case VEHICLE_VALUE_TYPE_BOOLEAN:
114             printf("Value type: BOOLEAN\nValue: %d\n", data->value.boolean_value);
115             break;
116         case VEHICLE_VALUE_TYPE_ZONED_BOOLEAN:
117             printf("Value type: ZONED_BOOLEAN\nZone: %d\n", data->zone);
118             printf("Value: %d\n", data->value.boolean_value);
119             break;
120         case VEHICLE_VALUE_TYPE_INT64:
121             printf("Value type: INT64\nValue: %" PRId64 "\n", data->value.int64_value);
122             break;
123         case VEHICLE_VALUE_TYPE_FLOAT:
124             printf("Value type: FLOAT\nValue: %f\n", data->value.float_value);
125             break;
126         case VEHICLE_VALUE_TYPE_FLOAT_VEC2:
127             printf("Value type: FLOAT_VEC2\nValue[0]: %f ", data->value.float_array[0]);
128             printf("Value[1]: %f\n", data->value.float_array[1]);
129             break;
130         case VEHICLE_VALUE_TYPE_FLOAT_VEC3:
131             printf("Value type: FLOAT_VEC3\nValue[0]: %f ", data->value.float_array[0]);
132             printf("Value[1]: %f ", data->value.float_array[1]);
133             printf("Value[2]: %f\n", data->value.float_array[2]);
134             break;
135         case VEHICLE_VALUE_TYPE_FLOAT_VEC4:
136             printf("Value type: FLOAT_VEC4\nValue[0]: %f ", data->value.float_array[0]);
137             printf("Value[1]: %f ", data->value.float_array[1]);
138             printf("Value[2]: %f ", data->value.float_array[2]);
139             printf("Value[3]: %f\n", data->value.float_array[3]);
140             break;
141         case VEHICLE_VALUE_TYPE_INT32:
142             printf("Value type: INT32\nValue: %d\n", data->value.int32_value);
143             break;
144         case VEHICLE_VALUE_TYPE_INT32_VEC2:
145             printf("Value type: INT32_VEC2\nValue[0]: %d ", data->value.int32_array[0]);
146             printf("Value[1]: %d\n", data->value.int32_array[1]);
147             break;
148         case VEHICLE_VALUE_TYPE_INT32_VEC3:
149             printf("Value type: INT32_VEC3\nValue[0]: %d ", data->value.int32_array[0]);
150             printf("Value[1]: %d ", data->value.int32_array[1]);
151             printf("Value[2]: %d\n", data->value.int32_array[2]);
152             break;
153         case VEHICLE_VALUE_TYPE_INT32_VEC4:
154             printf("Value type: INT32_VEC4\nValue[0]: %d ", data->value.int32_array[0]);
155             printf("Value[1]: %d ", data->value.int32_array[1]);
156             printf("Value[2]: %d ", data->value.int32_array[2]);
157             printf("Value[3]: %d\n", data->value.int32_array[3]);
158             break;
159         case VEHICLE_VALUE_TYPE_ZONED_FLOAT:
160             printf("Value type: ZONED_FLOAT\nZone: %d ", data->zone);
161             printf("Value: %f\n", data->value.float_value);
162             break;
163         case VEHICLE_VALUE_TYPE_ZONED_FLOAT_VEC2:
164             printf("Value type: ZONED_FLOAT_VEC2\nZone: %d ", data->zone);
165             printf("Value[0]: %f", data->value.float_array[0]);
166             printf("Value[1]: %f\n", data->value.float_array[1]);
167             break;
168         case VEHICLE_VALUE_TYPE_ZONED_FLOAT_VEC3:
169             printf("Value type: ZONED_FLOAT_VEC3\nZone: %d ", data->zone);
170             printf("Value[0]: %f ", data->value.float_array[0]);
171             printf("Value[1]: %f ", data->value.float_array[1]);
172             printf("Value[2]: %f\n", data->value.float_array[2]);
173             break;
174         case VEHICLE_VALUE_TYPE_ZONED_FLOAT_VEC4:
175             printf("Value type: ZONED_FLOAT_VEC4\nZone: %d ", data->zone);
176             printf("Value[0]: %f ", data->value.float_array[0]);
177             printf("Value[1]: %f ", data->value.float_array[1]);
178             printf("Value[2]: %f ", data->value.float_array[2]);
179             printf("Value[3]: %f\n", data->value.float_array[3]);
180             break;
181         case VEHICLE_VALUE_TYPE_ZONED_INT32:
182             printf("Value type: ZONED_INT32\nZone: %d ", data->zone);
183             printf("Value: %d\n", data->value.int32_value);
184             break;
185         case VEHICLE_VALUE_TYPE_ZONED_INT32_VEC2:
186             printf("Value type: ZONED_INT32_VEC2\nZone: %d ", data->zone);
187             printf("Value[0]: %d ", data->value.int32_array[0]);
188             printf("Value[1]: %d\n", data->value.int32_array[1]);
189             break;
190         case VEHICLE_VALUE_TYPE_ZONED_INT32_VEC3:
191             printf("Value type: ZONED_INT32_VEC3\nZone: %d ", data->zone);
192             printf("Value[0]: %d ", data->value.int32_array[0]);
193             printf("Value[1]: %d ", data->value.int32_array[1]);
194             printf("Value[2]: %d\n", data->value.int32_array[2]);
195             break;
196         case VEHICLE_VALUE_TYPE_ZONED_INT32_VEC4:
197             printf("Value type: ZONED_INT32_VEC4\nZone: %d ", data->zone);
198             printf("Value[0]: %d ", data->value.int32_array[0]);
199             printf("Value[1]: %d ", data->value.int32_array[1]);
200             printf("Value[2]: %d ", data->value.int32_array[2]);
201             printf("Value[3]: %d\n", data->value.int32_array[3]);
202             break;
203         default:
204             printf("Value type not yet handled: %d.\n", data->value_type);
205     }
206 }
207 
get_property(vehicle_hw_device_t * device,int32_t property,int32_t type,char * value_string)208 void get_property(
209     vehicle_hw_device_t *device, int32_t property, int32_t type, char *value_string) {
210     vehicle_prop_value_t *data = (vehicle_prop_value_t *) malloc (sizeof(vehicle_prop_value_t));
211 
212     // Parse the string according to type.
213     if (value_string != NULL && strlen(value_string) > 0) {
214         switch (type) {
215             case VEHICLE_VALUE_TYPE_INT32:
216                 sscanf(value_string, "%d", &(data->value.int32_value));
217                 break;
218             case VEHICLE_VALUE_TYPE_INT32_VEC4:
219             {
220                 int32_t vec[4];
221                 sscanf(value_string, "%d %d %d %d", &vec[0], &vec[1], &vec[2], &vec[3]);
222                 memcpy(data->value.int32_array, vec, sizeof(vec));
223                 break;
224             }
225             default:
226                 printf("%s Setting value type not supported: %d\n", __func__, type);
227                 exit(1);
228         }
229     }
230 
231     data->prop = property;
232     int ret_code = device->get(device, data);
233     if (ret_code != 0) {
234         printf("Cannot get property: %d\n", ret_code);
235         exit(1);
236     }
237 
238     // We simply convert the data into the type mentioned by the result of the
239     // get call.
240     printf("Get output\n------------\n");
241     print_property(data);
242     free(data);
243 }
244 
set_property(vehicle_hw_device_t * device,int32_t property,int32_t type,char * data)245 void set_property(vehicle_hw_device_t *device,
246                   int32_t property,
247                   int32_t type,
248                   char *data) {
249     vehicle_prop_value_t vehicle_data;
250     vehicle_data.prop = property;
251     vehicle_data.value_type = type;
252     int32_t zone = 0;
253     float value = 0.0;
254     switch (type) {
255         case VEHICLE_VALUE_TYPE_STRING:
256             // TODO: Make the code generic to UTF8 characters.
257             vehicle_data.value.str_value.len = strlen(data);
258             vehicle_data.value.str_value.data =
259                 (uint8_t *) malloc (strlen(data) * sizeof(uint8_t));
260             memcpy(vehicle_data.value.str_value.data, data, strlen(data) + 1);
261             break;
262         case VEHICLE_VALUE_TYPE_BYTES: {
263                 int len = strlen(data);
264                 int numBytes = (len + 1) / 3;
265                 uint8_t *buf = calloc(numBytes, sizeof(uint8_t));
266                 char *byte = strtok(data, " ");
267                 for (int i = 0; byte != NULL && i < numBytes; i++) {
268                     buf[i] = strtol(data, NULL, 16);
269                     byte = strtok(NULL, " ");
270                 }
271                 vehicle_data.value.bytes_value.len = numBytes;
272                 vehicle_data.value.bytes_value.data = buf;
273             }
274             break;
275         case VEHICLE_VALUE_TYPE_BOOLEAN:
276             vehicle_data.value.boolean_value = atoi(data);
277             break;
278         case VEHICLE_VALUE_TYPE_ZONED_BOOLEAN:
279             sscanf(data, "%d %d", &vehicle_data.zone,
280                 &vehicle_data.value.boolean_value);
281             break;
282         case VEHICLE_VALUE_TYPE_INT64:
283             vehicle_data.value.int64_value = atoi(data);
284             break;
285         case VEHICLE_VALUE_TYPE_FLOAT:
286             vehicle_data.value.float_value = atof(data);
287             break;
288         case VEHICLE_VALUE_TYPE_FLOAT_VEC2:
289             sscanf(data, "%f %f", &vehicle_data.value.float_array[0],
290                 &vehicle_data.value.float_array[1]);
291             break;
292         case VEHICLE_VALUE_TYPE_FLOAT_VEC3:
293             sscanf(data, "%f %f %f", &vehicle_data.value.float_array[0],
294                 &vehicle_data.value.float_array[1],
295                 &vehicle_data.value.float_array[2]);
296             break;
297         case VEHICLE_VALUE_TYPE_FLOAT_VEC4:
298             sscanf(data, "%f %f %f %f", &vehicle_data.value.float_array[0],
299                 &vehicle_data.value.float_array[1],
300                 &vehicle_data.value.float_array[2],
301                 &vehicle_data.value.float_array[3]);
302             break;
303         case VEHICLE_VALUE_TYPE_INT32:
304             vehicle_data.value.int32_value = atoi(data);
305             break;
306         case VEHICLE_VALUE_TYPE_INT32_VEC2:
307             sscanf(data, "%d %d", &vehicle_data.value.int32_array[0],
308                 &vehicle_data.value.int32_array[1]);
309             break;
310         case VEHICLE_VALUE_TYPE_INT32_VEC3:
311             sscanf(data, "%d %d %d", &vehicle_data.value.int32_array[0],
312                 &vehicle_data.value.int32_array[1],
313                 &vehicle_data.value.int32_array[2]);
314             break;
315         case VEHICLE_VALUE_TYPE_INT32_VEC4:
316             sscanf(data, "%d %d %d %d", &vehicle_data.value.int32_array[0],
317                 &vehicle_data.value.int32_array[1],
318                 &vehicle_data.value.int32_array[2],
319                 &vehicle_data.value.int32_array[3]);
320             break;
321         case VEHICLE_VALUE_TYPE_ZONED_FLOAT:
322             sscanf(data, "%d %f", &zone, &value);
323             vehicle_data.zone = zone;
324             vehicle_data.value.float_value = value;
325             break;
326         case VEHICLE_VALUE_TYPE_ZONED_FLOAT_VEC2:
327             sscanf(data, "%d %f %f", &vehicle_data.zone,
328                 &vehicle_data.value.float_array[0],
329                 &vehicle_data.value.float_array[1]);
330             break;
331         case VEHICLE_VALUE_TYPE_ZONED_FLOAT_VEC3:
332             sscanf(data, "%d %f %f %f", &vehicle_data.zone,
333                 &vehicle_data.value.float_array[0],
334                 &vehicle_data.value.float_array[1],
335                 &vehicle_data.value.float_array[2]);
336             break;
337         case VEHICLE_VALUE_TYPE_ZONED_FLOAT_VEC4:
338             sscanf(data, "%d %f %f %f %f", &vehicle_data.zone,
339                 &vehicle_data.value.float_array[0],
340                 &vehicle_data.value.float_array[1],
341                 &vehicle_data.value.float_array[2],
342                 &vehicle_data.value.float_array[3]);
343             break;
344         case VEHICLE_VALUE_TYPE_ZONED_INT32:
345             sscanf(data, "%d %d", &vehicle_data.zone,
346                 &vehicle_data.value.int32_value);
347             break;
348         case VEHICLE_VALUE_TYPE_ZONED_INT32_VEC2:
349             sscanf(data, "%d %d %d", &vehicle_data.zone,
350                 &vehicle_data.value.int32_array[0],
351                 &vehicle_data.value.int32_array[1]);
352             break;
353         case VEHICLE_VALUE_TYPE_ZONED_INT32_VEC3:
354             sscanf(data, "%d %d %d %d", &vehicle_data.zone,
355                 &vehicle_data.value.int32_array[0],
356                 &vehicle_data.value.int32_array[1],
357                 &vehicle_data.value.int32_array[2]);
358             break;
359         case VEHICLE_VALUE_TYPE_ZONED_INT32_VEC4:
360             sscanf(data, "%d %d %d %d %d", &vehicle_data.zone,
361                 &vehicle_data.value.int32_array[0],
362                 &vehicle_data.value.int32_array[1],
363                 &vehicle_data.value.int32_array[2],
364                 &vehicle_data.value.int32_array[3]);
365             break;
366         default:
367             printf("set_property: Value type not yet handled: %d\n", type);
368             exit(1);
369     }
370     printf("Setting Property id: %d\n", vehicle_data.prop);
371     print_property(&vehicle_data);
372 
373     int ret_code = device->set(device, &vehicle_data);
374     if (ret_code != 0) {
375         printf("Cannot set property: %d\n", ret_code);
376         exit(1);
377     }
378 }
379 
vehicle_event_callback(const vehicle_prop_value_t * event_data)380 int vehicle_event_callback(const vehicle_prop_value_t *event_data) {
381     // Print what we got.
382     printf("Got some value from callback property: %d\n", event_data->prop);
383     printf("Timestamp: %" PRId64 "\n", event_data->timestamp);
384     print_property(event_data);
385     return 0;
386 }
387 
vehicle_error_callback(int32_t error_code,int32_t property,int32_t operation)388 int vehicle_error_callback(int32_t error_code, int32_t property, int32_t operation) {
389     // Print what we got.
390     printf("Error code obtained: %d\n", error_code);
391     return 0;
392 }
393 
subscribe_to_property(vehicle_hw_device_t * device,int32_t prop,float sample_rate,uint32_t wait_in_seconds)394 void subscribe_to_property(
395     vehicle_hw_device_t *device,
396     int32_t prop,
397     float sample_rate,
398     uint32_t wait_in_seconds) {
399     // Init the device with a callback.
400     int ret_code = device->subscribe(device, prop, 0, 0);
401     if (ret_code != 0) {
402         printf("Could not subscribe: %d\n", ret_code);
403         exit(1);
404     }
405 
406     // Callbacks will happen on one of the threads created by the HAL hence we
407     // can simply sleep here and see the output.
408     sleep(wait_in_seconds);
409 
410     // Unsubscribe and uninit.
411     ret_code = device->unsubscribe(device, prop);
412     if (ret_code != 0) {
413         printf("Error unsubscribing the HAL, still continuining to uninit HAL ...");
414     }
415 }
416 
main(int argc,char * argv[])417 int main(int argc, char* argv[]) {
418     // Open the vehicle module and just ask for the list of properties.
419     const hw_module_t *hw_module = NULL;
420     int ret_code = hw_get_module(VEHICLE_HARDWARE_MODULE_ID, &hw_module);
421     if (ret_code != 0) {
422         printf("Cannot open the hw module. Does the HAL exist? %d\n", ret_code);
423         return -1;
424     }
425 
426     vehicle_module_t *vehicle_module = (vehicle_module_t *)(hw_module);
427     hw_device_t *device = NULL;
428     ret_code = vehicle_module->common.methods->open(hw_module, NULL, &device);
429     if (!device) {
430         printf("Cannot open the hw device: %d\n", ret_code);
431         return -1;
432     }
433     vehicle_hw_device_t *vehicle_device = (vehicle_hw_device_t *) (device);
434     printf("HAL Loaded!\n");
435 
436     vehicle_device->init(vehicle_device, vehicle_event_callback, vehicle_error_callback);
437 
438     // If this is a list properties command - we check for -l command.
439     int list_properties = 0;
440     // Type of the property (see #defines in vehicle.h).
441     int property = -1;
442     // Type of the value of the property (see enum vehicle_value_type).
443     int type = -1;
444     // Whether the mode is "get" or "set".
445     char mode[100] = "";
446     // Actual value as a string representation (supports only PODs for now).
447     // TODO: Support structures and complex types in the tool.
448     char value[100] = "";
449     // Wait time for the subscribe type of calls.
450     // We keep a default in case the user does not specify one.
451     int wait_time_in_sec = 10;
452     // Sample rate for subscribe type of calls.
453     // Default value is 0 for onchange type of properties.
454     int sample_rate = 0;
455     // Int array string which represents the vehicle_value_t in array of
456     // numbers. See vehicle_prop_value_t.value.int32_array.
457     char int_array_string[1000]; int_array_string[0] = '\0';
458 
459     int opt;
460     while ((opt = getopt(argc, argv, "lm:p:t:v:w:s:")) != -1) {
461         switch (opt) {
462             case 'l':
463                 list_properties = 1;
464                 break;
465             case 'm':
466                 strcpy(mode, optarg);
467                 break;
468             case 'p':
469                 property = atoi(optarg);
470                 break;
471             case 't':
472                 type = atoi(optarg);
473                 break;
474             case 'v':
475                 strcpy(value, optarg);
476                 break;
477             case 'w':
478                 wait_time_in_sec = atoi(optarg);
479                 break;
480             case 's':
481                 sample_rate = atoi(optarg);
482                 break;
483         }
484     }
485 
486     // We should have atleast one of list properties or mode (for get or set).
487     if (!list_properties &&
488         !(!strcmp(mode, "get") || !strcmp(mode, "set") || !strcmp(mode, "sub"))) {
489         usage();
490         exit(1);
491     }
492 
493     if (list_properties) {
494         printf("Listing properties...\n");
495         list_all_properties(vehicle_device);
496     } else if (!strcmp(mode, "get")) {
497         printf("Getting property ...\n");
498         if (property == -1) {
499             printf("Use -p to pass a valid Property.\n");
500             usage();
501             exit(1);
502         }
503 
504         int32_t int_array_list[4];
505         int count = -1;
506         if (strlen(int_array_string) > 0) {
507             count = sscanf(int_array_string, "%d%d%d%d",
508                    &int_array_list[0], &int_array_list[1], &int_array_list[2], &int_array_list[3]);
509         }
510 
511         get_property(vehicle_device, property, type, value);
512     } else if (!strcmp(mode, "set")) {
513         printf("Setting property ...\n");
514         if (property == -1 || type == -1) {
515             printf("Use -p to pass a valid Property and -t to pass a valid Type.\n");
516             usage();
517             exit(1);
518         }
519         set_property(vehicle_device, property, type, value);
520     } else if (!strcmp(mode, "sub")) {
521         printf("Subscribing property ...\n");
522         if (property == -1 || wait_time_in_sec <= 0) {
523             printf("Use -p to pass a valid property and -w to pass a valid wait time(s)\n");
524             usage();
525             exit(1);
526         }
527         subscribe_to_property(vehicle_device, property, sample_rate, wait_time_in_sec);
528     }
529 
530     ret_code = vehicle_device->release(vehicle_device);
531     if (ret_code != 0) {
532         printf("Error uniniting HAL, exiting anyways.");
533     }
534     return 0;
535 }
536