source: mainline/uspace/lib/usbhid/src/hidparser.c@ 41bbab6

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 41bbab6 was ae3a941, checked in by Ondřej Hlavatý <aearsis@…>, 7 years ago

usb: cstyle

  • Property mode set to 100644
File size: 15.2 KB
RevLine 
[bf2063e9]1/*
[2571089]2 * Copyright (c) 2011 Matej Klonfar
[e0a5d4c]3 * Copyright (c) 2018 Ondrej Hlavaty
[bf2063e9]4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
[160b75e]30/** @addtogroup libusbhid
[bf2063e9]31 * @{
32 */
33/** @file
[5499a8b]34 * USB HID report data parser implementation.
[bf2063e9]35 */
[faa44e58]36#include <usb/hid/hidparser.h>
[bf2063e9]37#include <errno.h>
[976f546]38#include <stdio.h>
[38d150e]39#include <stdlib.h>
[e24e7b1]40#include <mem.h>
[b7d9606]41#include <usb/debug.h>
[681f24b3]42#include <assert.h>
[6283cefb]43#include <bitops.h>
44#include <macros.h>
[976f546]45
[a76b01b4]46
[57d9c05e]47/*
48 * Data translation private functions
49 */
[1553cbf]50uint32_t usb_hid_report_tag_data_uint32(const uint8_t *data, size_t size);
[5499a8b]51
[cfbbe1d3]52int usb_hid_translate_data(usb_hid_report_field_t *item, const uint8_t *data);
[5499a8b]53
[ae3a941]54uint32_t usb_hid_translate_data_reverse(usb_hid_report_field_t *item,
55 int32_t value);
[5499a8b]56
[a76b01b4]57
[2571089]58
[069b80d]59static int usb_pow(int a, int b)
[fad14d7]60{
[069b80d]61 switch (b) {
[5499a8b]62 case 0:
63 return 1;
64 break;
65 case 1:
66 return a;
67 break;
68 default:
[069b80d]69 return a * usb_pow(a, b - 1);
[5499a8b]70 break;
[fad14d7]71 }
72}
[a76b01b4]73
[976f546]74
[3a6e423]75/** Returns size of report of specified report id and type in items
76 *
77 * @param parser Opaque report parser structure
[ae3a941]78 * @param report_id
[3a6e423]79 * @param type
80 * @return Number of items in specified report
81 */
[ae3a941]82size_t usb_hid_report_size(usb_hid_report_t *report, uint8_t report_id,
[069b80d]83 usb_hid_report_type_t type)
[3a6e423]84{
85 usb_hid_report_description_t *report_des;
86
[069b80d]87 if (report == NULL) {
[3a6e423]88 return 0;
89 }
90
[069b80d]91 report_des = usb_hid_report_find_description(report, report_id, type);
92 if (report_des == NULL) {
[3a6e423]93 return 0;
[069b80d]94 } else {
[3a6e423]95 return report_des->item_length;
96 }
97}
[1eee99f2]98
99/** Returns size of report of specified report id and type in bytes
100 *
101 * @param parser Opaque report parser structure
[ae3a941]102 * @param report_id
[1eee99f2]103 * @param type
104 * @return Number of items in specified report
105 */
[ae3a941]106size_t usb_hid_report_byte_size(usb_hid_report_t *report, uint8_t report_id,
[069b80d]107 usb_hid_report_type_t type)
[1eee99f2]108{
109 usb_hid_report_description_t *report_des;
110
[069b80d]111 if (report == NULL) {
[1eee99f2]112 return 0;
113 }
114
[069b80d]115 report_des = usb_hid_report_find_description(report, report_id, type);
116 if (report_des == NULL) {
[1eee99f2]117 return 0;
[069b80d]118 } else {
[ae3a941]119 return ((report_des->bit_length + 7) / 8);
[1eee99f2]120 }
121}
[a76b01b4]122
[c7a2e7e]123
[fad14d7]124/** Parse and act upon a HID report.
125 *
126 * @see usb_hid_parse_report_descriptor
127 *
128 * @param parser Opaque HID report parser structure.
129 * @param data Data for the report.
130 * @return Error code.
[ae3a941]131 */
132errno_t usb_hid_parse_report(const usb_hid_report_t *report, const uint8_t *data,
[069b80d]133 size_t size, uint8_t *report_id)
[fad14d7]134{
[175ad13e]135 usb_hid_report_description_t *report_des;
136 usb_hid_report_type_t type = USB_HID_REPORT_TYPE_INPUT;
[ae3a941]137
[069b80d]138 if (report == NULL) {
[55e388a1]139 return EINVAL;
140 }
[c156c2d]141
[069b80d]142 if (report->use_report_ids != 0) {
[cfbbe1d3]143 *report_id = data[0];
[069b80d]144 } else {
[cfbbe1d3]145 *report_id = 0;
[c156c2d]146 }
147
[ae3a941]148 report_des = usb_hid_report_find_description(report, *report_id,
[069b80d]149 type);
[8242dd86]150
[069b80d]151 if (report_des == NULL) {
[14e1bcc]152 return EINVAL;
153 }
[c156c2d]154
[175ad13e]155 /* read data */
[feeac0d]156 list_foreach(report_des->report_items, ritems_link,
157 usb_hid_report_field_t, item) {
[fad14d7]158
[069b80d]159 if (USB_HID_ITEM_FLAG_CONSTANT(item->item_flags) == 0) {
[ae3a941]160
[069b80d]161 if (USB_HID_ITEM_FLAG_VARIABLE(item->item_flags) == 0) {
162 /* array */
[ae3a941]163 item->value =
164 usb_hid_translate_data(item, data);
165
[5499a8b]166 item->usage = USB_HID_EXTENDED_USAGE(
[069b80d]167 item->usages[item->value -
168 item->physical_minimum]);
[f3b39b4]169
[ae3a941]170 item->usage_page =
[8242dd86]171 USB_HID_EXTENDED_USAGE_PAGE(
[069b80d]172 item->usages[item->value -
173 item->physical_minimum]);
[3a6e423]174
[069b80d]175 usb_hid_report_set_last_item(
[ae3a941]176 item->collection_path,
177 USB_HID_TAG_CLASS_GLOBAL,
[8242dd86]178 item->usage_page);
[f3b39b4]179
[069b80d]180 usb_hid_report_set_last_item(
[ae3a941]181 item->collection_path,
[5499a8b]182 USB_HID_TAG_CLASS_LOCAL, item->usage);
[069b80d]183 } else {
184 /* variable item */
[ae3a941]185 item->value = usb_hid_translate_data(item,
[feeac0d]186 data);
187 }
[fad14d7]188 }
189 }
[ae3a941]190
[fad14d7]191 return EOK;
192}
193
[a76b01b4]194
[57d9c05e]195/**
[c156c2d]196 * Translate data from the report as specified in report descriptor item
[57d9c05e]197 *
198 * @param item Report descriptor item with definition of translation
199 * @param data Data to translate
200 * @return Translated data
201 */
[cfbbe1d3]202int usb_hid_translate_data(usb_hid_report_field_t *item, const uint8_t *data)
[c7a2e7e]203{
[069b80d]204 /* now only short tags are allowed */
205 if (item->size > 32) {
[fad14d7]206 return 0;
207 }
208
[069b80d]209 if ((item->physical_minimum == 0) && (item->physical_maximum == 0)) {
[fad14d7]210 item->physical_minimum = item->logical_minimum;
[6283cefb]211 item->physical_maximum = item->logical_maximum;
[fad14d7]212 }
213
[6283cefb]214 int resolution;
[069b80d]215 if (item->physical_maximum == item->physical_minimum) {
[ae3a941]216 resolution = 1;
[069b80d]217 } else {
[ae3a941]218 resolution = (item->logical_maximum - item->logical_minimum) /
219 ((item->physical_maximum - item->physical_minimum) *
220 (usb_pow(10, (item->unit_exponent))));
[60a228f]221 }
[bda06a3]222
[6283cefb]223 int32_t value = 0;
224
225 /* First, skip all bytes we don't care */
226 data += item->offset / 8;
227
228 int bits = item->size;
229 int taken = 0;
230
231 /* Than we take the higher bits from the LSB */
232 const unsigned bit_offset = item->offset % 8;
233 const int lsb_bits = min(bits, 8);
234
235 value |= (*data >> bit_offset) & BIT_RRANGE(uint8_t, lsb_bits);
236 bits -= lsb_bits;
237 taken += lsb_bits;
238 data++;
239
240 /* Then there may be bytes, which we take as a whole. */
241 while (bits > 8) {
242 value |= *data << taken;
243 taken += 8;
244 bits -= 8;
245 data++;
246 }
247
248 /* And, finally, lower bits from HSB. */
249 if (bits > 0) {
250 value |= (*data & BIT_RRANGE(uint8_t, bits)) << taken;
[175ad13e]251 }
[fad14d7]252
[069b80d]253 if ((item->logical_minimum < 0) || (item->logical_maximum < 0)) {
[1553cbf]254 value = USB_HID_UINT32_TO_INT32(value, item->size);
[fad14d7]255 }
256
[ae3a941]257 return (int) (((value - item->logical_minimum) / resolution) +
[069b80d]258 item->physical_minimum);
[c7a2e7e]259}
[0bd4810c]260
[a76b01b4]261
[5499a8b]262/* OUTPUT API */
[57d9c05e]263
[ae3a941]264/**
[a694a58]265 * Allocates output report buffer for output report
[57d9c05e]266 *
[a694a58]267 * @param parser Report parsed structure
268 * @param size Size of returned buffer
269 * @param report_id Report id of created output report
270 * @return Returns allocated output buffer for specified output
[57d9c05e]271 */
[ae3a941]272uint8_t *usb_hid_report_output(usb_hid_report_t *report, size_t *size,
[069b80d]273 uint8_t report_id)
[57d9c05e]274{
[069b80d]275 if (report == NULL) {
[57d9c05e]276 *size = 0;
277 return NULL;
278 }
279
[175ad13e]280 usb_hid_report_description_t *report_des = NULL;
[b72efe8]281
[feeac0d]282 list_foreach(report->reports, reports_link,
[1e371cf]283 usb_hid_report_description_t, rdes) {
284 if ((rdes->report_id == report_id) &&
285 (rdes->type == USB_HID_REPORT_TYPE_OUTPUT)) {
286 report_des = rdes;
[a694a58]287 break;
[c156c2d]288 }
[175ad13e]289 }
[841e6e5]290
[069b80d]291 if (report_des == NULL) {
[175ad13e]292 *size = 0;
293 return NULL;
[069b80d]294 } else {
295 *size = (report_des->bit_length + (8 - 1)) / 8;
[175ad13e]296 uint8_t *ret = malloc((*size) * sizeof(uint8_t));
297 memset(ret, 0, (*size) * sizeof(uint8_t));
298 return ret;
[57d9c05e]299 }
300}
301
302
303/** Frees output report buffer
304 *
305 * @param output Output report buffer
[a694a58]306 * @return void
[57d9c05e]307 */
308void usb_hid_report_output_free(uint8_t *output)
309{
[069b80d]310 if (output != NULL) {
311 free(output);
[57d9c05e]312 }
313}
314
[175ad13e]315/** Makes the output report buffer for data given in the report structure
[57d9c05e]316 *
[a694a58]317 * @param parser Opaque report parser structure
318 * @param path Usage path specifing which parts of output will be set
319 * @param flags Usage path structure comparison flags
320 * @param buffer Output buffer
321 * @param size Size of output buffer
322 * @return Error code
[57d9c05e]323 */
[ae3a941]324errno_t usb_hid_report_output_translate(usb_hid_report_t *report,
[069b80d]325 uint8_t report_id, uint8_t *buffer, size_t size)
[57d9c05e]326{
[069b80d]327 int32_t value = 0;
[841e6e5]328 int offset;
329 int length;
330 int32_t tmp_value;
[ae3a941]331
[069b80d]332 if (report == NULL) {
[57d9c05e]333 return EINVAL;
334 }
335
[069b80d]336 if (report->use_report_ids != 0) {
[feeac0d]337 buffer[0] = report_id;
[bda06a3]338 }
339
[175ad13e]340 usb_hid_report_description_t *report_des;
[ae3a941]341 report_des = usb_hid_report_find_description(report, report_id,
[069b80d]342 USB_HID_REPORT_TYPE_OUTPUT);
[ae3a941]343
[069b80d]344 if (report_des == NULL) {
[175ad13e]345 return EINVAL;
346 }
[57d9c05e]347
[feeac0d]348 list_foreach(report_des->report_items, ritems_link,
349 usb_hid_report_field_t, report_item) {
[ae3a941]350 value = usb_hid_translate_data_reverse(report_item,
[069b80d]351 report_item->value);
[841e6e5]352
[574f276]353 offset = report_des->bit_length - report_item->offset - 1;
354 length = report_item->size;
[ae3a941]355
[a1732929]356 usb_log_debug("\ttranslated value: %x", value);
[841e6e5]357
[069b80d]358 if ((offset / 8) == ((offset + length - 1) / 8)) {
[ae3a941]359 if (((size_t) (offset / 8) >= size) ||
[069b80d]360 ((size_t) (offset + length - 1) / 8) >= size) {
[9be8669]361 break; // TODO ErrorCode
[841e6e5]362 }
[069b80d]363 size_t shift = 8 - offset % 8 - length;
[feeac0d]364 value = value << shift;
[069b80d]365 value = value & (((1 << length) - 1) << shift);
[ae3a941]366
[9be8669]367 uint8_t mask = 0;
368 mask = 0xff - (((1 << length) - 1) << shift);
[069b80d]369 buffer[offset / 8] = (buffer[offset / 8] & mask) |
370 value;
371 } else {
[9be8669]372 int i = 0;
373 uint8_t mask = 0;
[069b80d]374 for (i = (offset / 8);
375 i <= ((offset + length - 1) / 8); i++) {
376 if (i == (offset / 8)) {
[9be8669]377 tmp_value = value;
[ae3a941]378 tmp_value = tmp_value &
[069b80d]379 ((1 << (8 - (offset % 8))) - 1);
[f3b39b4]380
[069b80d]381 tmp_value = tmp_value << (offset % 8);
[ae3a941]382
[069b80d]383 mask = ~(((1 << (8 - (offset % 8))) - 1)
384 << (offset % 8));
[f3b39b4]385
[ae3a941]386 buffer[i] = (buffer[i] & mask) |
[069b80d]387 tmp_value;
388 } else if (i == ((offset + length - 1) / 8)) {
[ae3a941]389
390 value = value >> (length -
[069b80d]391 ((offset + length) % 8));
[f3b39b4]392
[ae3a941]393 value = value & ((1 << (length -
[069b80d]394 ((offset + length) % 8))) - 1);
[ae3a941]395
396 mask = (1 << (length -
[069b80d]397 ((offset + length) % 8))) - 1;
[f3b39b4]398
[9be8669]399 buffer[i] = (buffer[i] & mask) | value;
[069b80d]400 } else {
401 buffer[i] = value & (0xff << i);
[175ad13e]402 }
[841e6e5]403 }
[9be8669]404 }
[841e6e5]405
[069b80d]406 /* reset value */
[cfbbe1d3]407 report_item->value = 0;
[57d9c05e]408 }
[ae3a941]409
[57d9c05e]410 return EOK;
411}
412
[a76b01b4]413
[841e6e5]414/**
[a694a58]415 * Translate given data for putting them into the outoput report
416 * @param item Report item structure
417 * @param value Value to translate
418 * @return ranslated value
[841e6e5]419 */
[ae3a941]420uint32_t usb_hid_translate_data_reverse(usb_hid_report_field_t *item,
[069b80d]421 int value)
[841e6e5]422{
[069b80d]423 int ret = 0;
[841e6e5]424 int resolution;
425
[069b80d]426 if (USB_HID_ITEM_FLAG_CONSTANT(item->item_flags)) {
[4172db4a]427 return item->logical_minimum;
[70a71e5]428 }
429
[069b80d]430 if ((item->physical_minimum == 0) && (item->physical_maximum == 0)) {
[cfbbe1d3]431 item->physical_minimum = item->logical_minimum;
[ae3a941]432 item->physical_maximum = item->logical_maximum;
[cfbbe1d3]433 }
[ae3a941]434
[069b80d]435 /* variable item */
436 if (item->physical_maximum == item->physical_minimum) {
[ae3a941]437 resolution = 1;
[069b80d]438 } else {
[ae3a941]439 resolution = (item->logical_maximum - item->logical_minimum) /
440 ((item->physical_maximum - item->physical_minimum) *
441 (usb_pow(10, (item->unit_exponent))));
[841e6e5]442 }
443
[ae3a941]444 ret = ((value - item->physical_minimum) * resolution) +
[069b80d]445 item->logical_minimum;
[5499a8b]446
[069b80d]447 usb_log_debug("\tvalue(%x), resolution(%x), phymin(%x) logmin(%x), "
[ae3a941]448 "ret(%x)\n", value, resolution, item->physical_minimum,
[069b80d]449 item->logical_minimum, ret);
[ae3a941]450
[069b80d]451 if ((item->logical_minimum < 0) || (item->logical_maximum < 0)) {
[1553cbf]452 return USB_HID_INT32_TO_UINT32(ret, item->size);
453 }
[069b80d]454
455 return (int32_t) 0 + ret;
[841e6e5]456}
457
[a76b01b4]458
[5499a8b]459/**
460 * Clones given state table
461 *
462 * @param item State table to clone
463 * @return Pointer to the cloned item
464 */
465usb_hid_report_item_t *usb_hid_report_item_clone(
[069b80d]466 const usb_hid_report_item_t *item)
[64dbc83]467{
468 usb_hid_report_item_t *new_report_item;
[ae3a941]469
[069b80d]470 if (!(new_report_item = malloc(sizeof(usb_hid_report_item_t)))) {
[64dbc83]471 return NULL;
[ae3a941]472 }
[64dbc83]473 memcpy(new_report_item,item, sizeof(usb_hid_report_item_t));
474 link_initialize(&(new_report_item->link));
475
476 return new_report_item;
477}
478
[a76b01b4]479
[5499a8b]480/**
481 * Function for sequence walking through the report. Returns next field in the
482 * report or the first one when no field is given.
483 *
484 * @param report Searched report structure
485 * @param field Current field. If NULL is given, the first one in the report
486 * is returned. Otherwise the next one i nthe list is returned.
[ae3a941]487 * @param path Usage path specifying which fields wa are interested in.
[5499a8b]488 * @param flags Flags defining mode of usage paths comparison
489 * @param type Type of report we search.
490 * @retval NULL if no field is founded
491 * @retval Pointer to the founded report structure when founded
492 */
[ae3a941]493usb_hid_report_field_t *usb_hid_report_get_sibling(usb_hid_report_t *report,
494 usb_hid_report_field_t *field, usb_hid_report_path_t *path, int flags,
[069b80d]495 usb_hid_report_type_t type)
[e50cd7f]496{
[ae3a941]497 usb_hid_report_description_t *report_des =
[069b80d]498 usb_hid_report_find_description(report, path->report_id, type);
[5499a8b]499
[e50cd7f]500 link_t *field_it;
[ae3a941]501
[069b80d]502 if (report_des == NULL) {
[e50cd7f]503 return NULL;
504 }
505
[069b80d]506 if (field == NULL) {
[b72efe8]507 field_it = report_des->report_items.head.next;
[069b80d]508 } else {
[b72efe8]509 field_it = field->ritems_link.next;
[e50cd7f]510 }
511
[069b80d]512 while (field_it != &report_des->report_items.head) {
[ae3a941]513 field = list_get_instance(field_it, usb_hid_report_field_t,
[069b80d]514 ritems_link);
[f3b39b4]515
[069b80d]516 if (USB_HID_ITEM_FLAG_CONSTANT(field->item_flags) == 0) {
517 usb_hid_report_path_append_item(field->collection_path,
518 field->usage_page, field->usage);
[5499a8b]519
[069b80d]520 if (usb_hid_report_compare_usage_path(
[3ca4ae9]521 field->collection_path, path, flags) == 0) {
[f3b39b4]522 usb_hid_report_remove_last_item(
[069b80d]523 field->collection_path);
[c7c0984a]524 return field;
525 }
[069b80d]526 usb_hid_report_remove_last_item(field->collection_path);
[e50cd7f]527 }
528 field_it = field_it->next;
529 }
530
531 return NULL;
532}
[cfbbe1d3]533
[a76b01b4]534
[5499a8b]535/**
[1d10ca1]536 * Returns next report_id of report of specified type. If zero is given than
537 * first report_id of specified type is returned (0 is not legal value for
538 * repotr_id)
[5499a8b]539 *
[1d10ca1]540 * @param report_id Current report_id, 0 if there is no current report_id
[5499a8b]541 * @param type Type of searched report
542 * @param report Report structure inwhich we search
543 * @retval 0 if report structure is null or there is no specified report
544 * @retval report_id otherwise
545 */
[069b80d]546uint8_t usb_hid_get_next_report_id(usb_hid_report_t *report, uint8_t report_id,
547 usb_hid_report_type_t type)
[cfbbe1d3]548{
[069b80d]549 if (report == NULL) {
[cfbbe1d3]550 return 0;
551 }
552
553 usb_hid_report_description_t *report_des;
554 link_t *report_it;
[ae3a941]555
[069b80d]556 if (report_id > 0) {
[ae3a941]557 report_des = usb_hid_report_find_description(report, report_id,
[069b80d]558 type);
559 if (report_des == NULL) {
[0dd3e49]560 return 0;
[069b80d]561 } else {
[b72efe8]562 report_it = report_des->reports_link.next;
[ae3a941]563 }
[069b80d]564 } else {
[b72efe8]565 report_it = report->reports.head.next;
[cfbbe1d3]566 }
567
[069b80d]568 while (report_it != &report->reports.head) {
[ae3a941]569 report_des = list_get_instance(report_it,
[069b80d]570 usb_hid_report_description_t, reports_link);
[5499a8b]571
[069b80d]572 if (report_des->type == type) {
[cfbbe1d3]573 return report_des->report_id;
574 }
[0c904a3]575
576 report_it = report_it->next;
[cfbbe1d3]577 }
578
579 return 0;
580}
581
[a76b01b4]582
[5499a8b]583/**
584 * Reset all local items in given state table
585 *
586 * @param report_item State table containing current state of report
587 * descriptor parsing
588 *
589 * @return void
590 */
[2020927]591void usb_hid_report_reset_local_items(usb_hid_report_item_t *report_item)
592{
[069b80d]593 if (report_item == NULL) {
[2020927]594 return;
595 }
[ae3a941]596
[2020927]597 report_item->usages_count = 0;
598 memset(report_item->usages, 0, USB_HID_MAX_USAGES);
[ae3a941]599
[2020927]600 report_item->extended_usage_page = 0;
601 report_item->usage_minimum = 0;
602 report_item->usage_maximum = 0;
603 report_item->designator_index = 0;
604 report_item->designator_minimum = 0;
605 report_item->designator_maximum = 0;
606 report_item->string_index = 0;
607 report_item->string_minimum = 0;
608 report_item->string_maximum = 0;
609}
[069b80d]610
[976f546]611/**
612 * @}
[c7a2e7e]613 */
Note: See TracBrowser for help on using the repository browser.