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

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

usb: update copyrights

The data was generated by a script, guided manually. If you feel your
name is missing somewhere, please add it!

The semi-automated process was roughly:

1) Changes per file and author (limited to our team) were counted
2) Trivial numbers were thrown away
3) Authors were sorted by lines added to file
4) All previous copyrights were replaced by the newly generated one
5) Hunks changing only year were discarded

It seems that a lot of my copyrights were added. It is due to me being
both sticking my nose everywhere and lazy to update the copyright right
away :)

  • Property mode set to 100644
File size: 15.3 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
54uint32_t usb_hid_translate_data_reverse(usb_hid_report_field_t *item,
55 int32_t value);
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
78 * @param report_id
79 * @param type
80 * @return Number of items in specified report
81 */
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
102 * @param report_id
103 * @param type
104 * @return Number of items in specified report
105 */
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 {
[d861c22]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.
131 */
[5a6cc679]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;
[07525cd]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
[8242dd86]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) {
[07525cd]160
[069b80d]161 if (USB_HID_ITEM_FLAG_VARIABLE(item->item_flags) == 0) {
162 /* array */
[f3b39b4]163 item->value =
164 usb_hid_translate_data(item, data);
[07525cd]165
[5499a8b]166 item->usage = USB_HID_EXTENDED_USAGE(
[069b80d]167 item->usages[item->value -
168 item->physical_minimum]);
[f3b39b4]169
[8242dd86]170 item->usage_page =
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(
[8242dd86]176 item->collection_path,
177 USB_HID_TAG_CLASS_GLOBAL,
178 item->usage_page);
[f3b39b4]179
[069b80d]180 usb_hid_report_set_last_item(
[8242dd86]181 item->collection_path,
[5499a8b]182 USB_HID_TAG_CLASS_LOCAL, item->usage);
[069b80d]183 } else {
184 /* variable item */
[8242dd86]185 item->value = usb_hid_translate_data(item,
[feeac0d]186 data);
187 }
[fad14d7]188 }
189 }
[07525cd]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) {
[60a228f]216 resolution = 1;
[069b80d]217 } else {
[6283cefb]218 resolution = (item->logical_maximum - item->logical_minimum) /
219 ((item->physical_maximum - item->physical_minimum) *
[069b80d]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
[069b80d]257 return (int) (((value - item->logical_minimum) / resolution) +
258 item->physical_minimum);
[c7a2e7e]259}
[0bd4810c]260
[a76b01b4]261
[5499a8b]262/* OUTPUT API */
[57d9c05e]263
[a694a58]264/**
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 */
[5499a8b]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 */
[5a6cc679]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;
[07525cd]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;
[069b80d]341 report_des = usb_hid_report_find_description(report, report_id,
342 USB_HID_REPORT_TYPE_OUTPUT);
[07525cd]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) {
[574f276]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;
[07525cd]355
[a1732929]356 usb_log_debug("\ttranslated value: %x", value);
[841e6e5]357
[069b80d]358 if ((offset / 8) == ((offset + length - 1) / 8)) {
359 if (((size_t) (offset / 8) >= size) ||
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);
[07525cd]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;
[f3b39b4]378 tmp_value = tmp_value &
[069b80d]379 ((1 << (8 - (offset % 8))) - 1);
[f3b39b4]380
[069b80d]381 tmp_value = tmp_value << (offset % 8);
[07525cd]382
[069b80d]383 mask = ~(((1 << (8 - (offset % 8))) - 1)
384 << (offset % 8));
[f3b39b4]385
386 buffer[i] = (buffer[i] & mask) |
[069b80d]387 tmp_value;
388 } else if (i == ((offset + length - 1) / 8)) {
[07525cd]389
[f3b39b4]390 value = value >> (length -
[069b80d]391 ((offset + length) % 8));
[f3b39b4]392
393 value = value & ((1 << (length -
[069b80d]394 ((offset + length) % 8))) - 1);
[07525cd]395
[f3b39b4]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 }
[07525cd]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 */
[5499a8b]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;
432 item->physical_maximum = item->logical_maximum;
433 }
434
[069b80d]435 /* variable item */
436 if (item->physical_maximum == item->physical_minimum) {
[9be8669]437 resolution = 1;
[069b80d]438 } else {
[9be8669]439 resolution = (item->logical_maximum - item->logical_minimum) /
440 ((item->physical_maximum - item->physical_minimum) *
[069b80d]441 (usb_pow(10, (item->unit_exponent))));
[841e6e5]442 }
443
[5499a8b]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), "
448 "ret(%x)\n", value, resolution, item->physical_minimum,
449 item->logical_minimum, ret);
[9be8669]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;
469
[069b80d]470 if (!(new_report_item = malloc(sizeof(usb_hid_report_item_t)))) {
[64dbc83]471 return NULL;
472 }
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.
487 * @param path Usage path specifying which fields wa are interested in.
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 */
[e50cd7f]493usb_hid_report_field_t *usb_hid_report_get_sibling(usb_hid_report_t *report,
[069b80d]494 usb_hid_report_field_t *field, usb_hid_report_path_t *path, int flags,
495 usb_hid_report_type_t type)
[e50cd7f]496{
[f3b39b4]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;
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) {
[f3b39b4]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;
555
[069b80d]556 if (report_id > 0) {
[0dd3e49]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;
[0dd3e49]563 }
[069b80d]564 } else {
[b72efe8]565 report_it = report->reports.head.next;
[cfbbe1d3]566 }
567
[069b80d]568 while (report_it != &report->reports.head) {
[1d10ca1]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 }
596
597 report_item->usages_count = 0;
598 memset(report_item->usages, 0, USB_HID_MAX_USAGES);
599
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.