xref: /btstack/src/hci_dump.c (revision acb15818d50b860e14489f68a6237686091b95b9)
179662672Smatthias.ringwald /*
2a0c35809S[email protected]  * Copyright (C) 2014 BlueKitchen GmbH
31713bceaSmatthias.ringwald  *
41713bceaSmatthias.ringwald  * Redistribution and use in source and binary forms, with or without
51713bceaSmatthias.ringwald  * modification, are permitted provided that the following conditions
61713bceaSmatthias.ringwald  * are met:
71713bceaSmatthias.ringwald  *
81713bceaSmatthias.ringwald  * 1. Redistributions of source code must retain the above copyright
91713bceaSmatthias.ringwald  *    notice, this list of conditions and the following disclaimer.
101713bceaSmatthias.ringwald  * 2. Redistributions in binary form must reproduce the above copyright
111713bceaSmatthias.ringwald  *    notice, this list of conditions and the following disclaimer in the
121713bceaSmatthias.ringwald  *    documentation and/or other materials provided with the distribution.
131713bceaSmatthias.ringwald  * 3. Neither the name of the copyright holders nor the names of
141713bceaSmatthias.ringwald  *    contributors may be used to endorse or promote products derived
151713bceaSmatthias.ringwald  *    from this software without specific prior written permission.
166b64433eSmatthias.ringwald  * 4. Any redistribution, use, or modification is done solely for
176b64433eSmatthias.ringwald  *    personal benefit and not for any commercial purpose or for
186b64433eSmatthias.ringwald  *    monetary gain.
191713bceaSmatthias.ringwald  *
20a0c35809S[email protected]  * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
211713bceaSmatthias.ringwald  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
221713bceaSmatthias.ringwald  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
231713bceaSmatthias.ringwald  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS
241713bceaSmatthias.ringwald  * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
251713bceaSmatthias.ringwald  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
261713bceaSmatthias.ringwald  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
271713bceaSmatthias.ringwald  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
281713bceaSmatthias.ringwald  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
291713bceaSmatthias.ringwald  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
301713bceaSmatthias.ringwald  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
311713bceaSmatthias.ringwald  * SUCH DAMAGE.
321713bceaSmatthias.ringwald  *
33a0c35809S[email protected]  * Please inquire about commercial licensing options at
34a0c35809S[email protected]  * [email protected]
356b64433eSmatthias.ringwald  *
361713bceaSmatthias.ringwald  */
371713bceaSmatthias.ringwald 
38ab2c6ae4SMatthias Ringwald #define __BTSTACK_FILE__ "hci_dump.c"
39ab2c6ae4SMatthias Ringwald 
401713bceaSmatthias.ringwald /*
4179662672Smatthias.ringwald  *  hci_dump.c
4279662672Smatthias.ringwald  *
43fe1ed1b8Smatthias.ringwald  *  Dump HCI trace in various formats:
44fe1ed1b8Smatthias.ringwald  *
45fe1ed1b8Smatthias.ringwald  *  - BlueZ's hcidump format
46fe1ed1b8Smatthias.ringwald  *  - Apple's PacketLogger
47fe1ed1b8Smatthias.ringwald  *  - stdout hexdump
4879662672Smatthias.ringwald  *
4979662672Smatthias.ringwald  *  Created by Matthias Ringwald on 5/26/09.
5079662672Smatthias.ringwald  */
5179662672Smatthias.ringwald 
527907f069SMatthias Ringwald #include "btstack_config.h"
53a1d7dd1fSmatthias.ringwald 
54359cfe47SMatthias Ringwald // enable POSIX functions (needed for -std=c99)
55359cfe47SMatthias Ringwald #define _POSIX_C_SOURCE 200809
56359cfe47SMatthias Ringwald 
5779662672Smatthias.ringwald #include "hci_dump.h"
5879662672Smatthias.ringwald #include "hci.h"
59c6448b67Smatthias.ringwald #include "hci_transport.h"
6056042629SMatthias Ringwald #include "hci_cmd.h"
6182636622SMatthias Ringwald #include "btstack_run_loop.h"
62198a9e1bS[email protected] #include <stdio.h>
6379662672Smatthias.ringwald 
64eb443d3dSMatthias Ringwald #ifdef HAVE_POSIX_FILE_IO
65d5ea8924S[email protected] #include <fcntl.h>        // open
6679662672Smatthias.ringwald #include <unistd.h>       // write
678adf0ddaSmatthias.ringwald #include <time.h>
688b658ebcSmatthias.ringwald #include <sys/time.h>     // for timestamps
69c7b9c559Smatthias.ringwald #include <sys/stat.h>     // for mode flags
7068e27c0fSmatthias.ringwald #endif
7179662672Smatthias.ringwald 
721a1c8389SMatthias Ringwald // BLUEZ hcidump - struct not used directly, but left here as documentation
738b658ebcSmatthias.ringwald typedef struct {
748b658ebcSmatthias.ringwald     uint16_t    len;
758b658ebcSmatthias.ringwald     uint8_t     in;
768b658ebcSmatthias.ringwald     uint8_t     pad;
778b658ebcSmatthias.ringwald     uint32_t    ts_sec;
788b658ebcSmatthias.ringwald     uint32_t    ts_usec;
798b658ebcSmatthias.ringwald     uint8_t     packet_type;
806c5c6faaSmatthias.ringwald }
816c5c6faaSmatthias.ringwald hcidump_hdr;
82f3e036dcSMatthias Ringwald #define HCIDUMP_HDR_SIZE 13
8379662672Smatthias.ringwald 
841a1c8389SMatthias Ringwald // APPLE PacketLogger - struct not used directly, but left here as documentation
858b658ebcSmatthias.ringwald typedef struct {
868b658ebcSmatthias.ringwald     uint32_t    len;
878b658ebcSmatthias.ringwald     uint32_t    ts_sec;
888b658ebcSmatthias.ringwald     uint32_t    ts_usec;
892df12229Smatthias.ringwald     uint8_t     type;   // 0xfc for note
906c5c6faaSmatthias.ringwald }
916c5c6faaSmatthias.ringwald pktlog_hdr;
92f3e036dcSMatthias Ringwald #define PKTLOG_HDR_SIZE 13
938b658ebcSmatthias.ringwald 
948b658ebcSmatthias.ringwald static int dump_file = -1;
95eb443d3dSMatthias Ringwald #ifdef HAVE_POSIX_FILE_IO
968b658ebcSmatthias.ringwald static int dump_format;
97f3e036dcSMatthias Ringwald static uint8_t header_bluez[HCIDUMP_HDR_SIZE];
98f3e036dcSMatthias Ringwald static uint8_t header_packetlogger[PKTLOG_HDR_SIZE];
998adf0ddaSmatthias.ringwald static char time_string[40];
1002992c131Smatthias.ringwald static int  max_nr_packets = -1;
1019ae0c346Smatthias.ringwald static int  nr_packets = 0;
102a1d7dd1fSmatthias.ringwald static char log_message_buffer[256];
10368e27c0fSmatthias.ringwald #endif
1048b658ebcSmatthias.ringwald 
1058a37b10aSMatthias Ringwald // levels: debug, info, error
1068a37b10aSMatthias Ringwald static int log_level_enabled[3] = { 1, 1, 1};
1078a37b10aSMatthias Ringwald 
108a225073eS[email protected] void hci_dump_open(const char *filename, hci_dump_format_t format){
109eb443d3dSMatthias Ringwald #ifdef HAVE_POSIX_FILE_IO
1108b658ebcSmatthias.ringwald     dump_format = format;
1118adf0ddaSmatthias.ringwald     if (dump_format == HCI_DUMP_STDOUT) {
1128adf0ddaSmatthias.ringwald         dump_file = fileno(stdout);
1138adf0ddaSmatthias.ringwald     } else {
114eb443d3dSMatthias Ringwald 
1151e154302SMatthias Ringwald         int oflags = O_WRONLY | O_CREAT | O_TRUNC;
116b52eaea5S[email protected] #ifdef _WIN32
1171e154302SMatthias Ringwald         oflags |= O_BINARY;
118b52eaea5S[email protected] #endif
119eb443d3dSMatthias Ringwald 
1201e154302SMatthias Ringwald         dump_file = open(filename, oflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH );
1211e154302SMatthias Ringwald         if (dump_file < 0){
1221e154302SMatthias Ringwald             printf("hci_dump_open: failed to open file %s\n", filename);
1231e154302SMatthias Ringwald         }
12479662672Smatthias.ringwald     }
125eb443d3dSMatthias Ringwald #else
126d0662982SMatthias Ringwald     UNUSED(filename);
127d0662982SMatthias Ringwald     UNUSED(format);
128d0662982SMatthias Ringwald 
129eb443d3dSMatthias Ringwald     dump_file = 1;
13068e27c0fSmatthias.ringwald #endif
131d9659922Smatthias.ringwald }
13279662672Smatthias.ringwald 
133eb443d3dSMatthias Ringwald #ifdef HAVE_POSIX_FILE_IO
1342992c131Smatthias.ringwald void hci_dump_set_max_packets(int packets){
1352992c131Smatthias.ringwald     max_nr_packets = packets;
1362992c131Smatthias.ringwald }
1377c5f7483Smatthias.ringwald #endif
1382992c131Smatthias.ringwald 
1398a37b10aSMatthias Ringwald static void printf_packet(uint8_t packet_type, uint8_t in, uint8_t * packet, uint16_t len){
140198a9e1bS[email protected]     switch (packet_type){
141198a9e1bS[email protected]         case HCI_COMMAND_DATA_PACKET:
142198a9e1bS[email protected]             printf("CMD => ");
143198a9e1bS[email protected]             break;
144198a9e1bS[email protected]         case HCI_EVENT_PACKET:
145198a9e1bS[email protected]             printf("EVT <= ");
146198a9e1bS[email protected]             break;
147198a9e1bS[email protected]         case HCI_ACL_DATA_PACKET:
148198a9e1bS[email protected]             if (in) {
149198a9e1bS[email protected]                 printf("ACL <= ");
150198a9e1bS[email protected]             } else {
151198a9e1bS[email protected]                 printf("ACL => ");
152198a9e1bS[email protected]             }
153198a9e1bS[email protected]             break;
1541e154302SMatthias Ringwald         case HCI_SCO_DATA_PACKET:
1551e154302SMatthias Ringwald             if (in) {
1561e154302SMatthias Ringwald                 printf("SCO <= ");
1571e154302SMatthias Ringwald             } else {
1581e154302SMatthias Ringwald                 printf("SCO => ");
1591e154302SMatthias Ringwald             }
1601e154302SMatthias Ringwald             break;
161198a9e1bS[email protected]         case LOG_MESSAGE_PACKET:
162198a9e1bS[email protected]             printf("LOG -- %s\n", (char*) packet);
163198a9e1bS[email protected]             return;
164198a9e1bS[email protected]         default:
165198a9e1bS[email protected]             return;
166198a9e1bS[email protected]     }
167198a9e1bS[email protected]     printf_hexdump(packet, len);
168198a9e1bS[email protected] }
169198a9e1bS[email protected] 
170ad6274a7SMatthias Ringwald static void printf_timestamp(void){
1716401061aSMatthias Ringwald #ifdef HAVE_POSIX_FILE_IO
1726401061aSMatthias Ringwald     struct tm* ptm;
1736401061aSMatthias Ringwald     struct timeval curr_time;
1746401061aSMatthias Ringwald     gettimeofday(&curr_time, NULL);
1756401061aSMatthias Ringwald     time_t curr_time_secs = curr_time.tv_sec;
1766401061aSMatthias Ringwald     /* Obtain the time of day, and convert it to a tm struct. */
1776401061aSMatthias Ringwald     ptm = localtime (&curr_time_secs);
1786401061aSMatthias Ringwald     /* assert localtime was successful */
1796401061aSMatthias Ringwald     if (!ptm) return;
1806401061aSMatthias Ringwald     /* Format the date and time, down to a single second. */
1816401061aSMatthias Ringwald     strftime (time_string, sizeof (time_string), "[%Y-%m-%d %H:%M:%S", ptm);
1826401061aSMatthias Ringwald     /* Compute milliseconds from microseconds. */
1836401061aSMatthias Ringwald     uint16_t milliseconds = curr_time.tv_usec / 1000;
1846401061aSMatthias Ringwald     /* Print the formatted time, in seconds, followed by a decimal point and the milliseconds. */
1856401061aSMatthias Ringwald     printf ("%s.%03u] ", time_string, milliseconds);
1866401061aSMatthias Ringwald #else
187ad6274a7SMatthias Ringwald     uint32_t time_ms = btstack_run_loop_get_time_ms();
188ad6274a7SMatthias Ringwald     int      seconds = time_ms / 1000;
189ad6274a7SMatthias Ringwald     int      minutes = seconds / 60;
190f04a0c31SMatthias Ringwald     unsigned int hours   = minutes / 60;
191ad6274a7SMatthias Ringwald 
192f04a0c31SMatthias Ringwald     uint16_t p_ms      = time_ms - (seconds * 1000);
193f04a0c31SMatthias Ringwald     uint16_t p_seconds = seconds - (minutes * 60);
194f04a0c31SMatthias Ringwald     uint16_t p_minutes = minutes - (hours   * 60);
195ad6274a7SMatthias Ringwald     printf("[%02u:%02u:%02u.%03u] ", hours, p_minutes, p_seconds, p_ms);
196ad6274a7SMatthias Ringwald #endif
1976401061aSMatthias Ringwald }
198ad6274a7SMatthias Ringwald 
19979662672Smatthias.ringwald void hci_dump_packet(uint8_t packet_type, uint8_t in, uint8_t *packet, uint16_t len) {
20068e27c0fSmatthias.ringwald 
2018b658ebcSmatthias.ringwald     if (dump_file < 0) return; // not activated yet
2028b658ebcSmatthias.ringwald 
203eb443d3dSMatthias Ringwald #ifdef HAVE_POSIX_FILE_IO
204eb443d3dSMatthias Ringwald 
2052992c131Smatthias.ringwald     // don't grow bigger than max_nr_packets
2062992c131Smatthias.ringwald     if (dump_format != HCI_DUMP_STDOUT && max_nr_packets > 0){
2072992c131Smatthias.ringwald         if (nr_packets >= max_nr_packets){
2082992c131Smatthias.ringwald             lseek(dump_file, 0, SEEK_SET);
209*acb15818SMatthias Ringwald             // avoid -Wunused-result
210*acb15818SMatthias Ringwald             int res = ftruncate(dump_file, 0);
211*acb15818SMatthias Ringwald             UNUSED(res);
2122992c131Smatthias.ringwald             nr_packets = 0;
2132992c131Smatthias.ringwald         }
2142992c131Smatthias.ringwald         nr_packets++;
2152992c131Smatthias.ringwald     }
2162992c131Smatthias.ringwald 
2178b658ebcSmatthias.ringwald     // get time
2188b658ebcSmatthias.ringwald     struct timeval curr_time;
2198b658ebcSmatthias.ringwald     gettimeofday(&curr_time, NULL);
2208b658ebcSmatthias.ringwald 
221*acb15818SMatthias Ringwald     // avoid -Wunused-result
222*acb15818SMatthias Ringwald     int res = 0;
2238b658ebcSmatthias.ringwald     switch (dump_format){
224a9cf4d77S[email protected]         case HCI_DUMP_STDOUT: {
2256401061aSMatthias Ringwald             printf_timestamp();
226198a9e1bS[email protected]             printf_packet(packet_type, in, packet, len);
2278adf0ddaSmatthias.ringwald             break;
228a9cf4d77S[email protected]         }
2290d79c710Smatthias.ringwald 
2308b658ebcSmatthias.ringwald         case HCI_DUMP_BLUEZ:
231f8fbdce0SMatthias Ringwald             little_endian_store_16( header_bluez, 0, 1 + len);
2321a1c8389SMatthias Ringwald             header_bluez[2] = in;
2331a1c8389SMatthias Ringwald             header_bluez[3] = 0;
2347c959318SMatthias Ringwald             little_endian_store_32( header_bluez, 4, (uint32_t) curr_time.tv_sec);
235f8fbdce0SMatthias Ringwald             little_endian_store_32( header_bluez, 8,            curr_time.tv_usec);
2361a1c8389SMatthias Ringwald             header_bluez[12] = packet_type;
237*acb15818SMatthias Ringwald             res = write (dump_file, header_bluez, HCIDUMP_HDR_SIZE);
238*acb15818SMatthias Ringwald             res = write (dump_file, packet, len );
2398b658ebcSmatthias.ringwald             break;
2400d79c710Smatthias.ringwald 
2418b658ebcSmatthias.ringwald         case HCI_DUMP_PACKETLOGGER:
242f8fbdce0SMatthias Ringwald             big_endian_store_32( header_packetlogger, 0, PKTLOG_HDR_SIZE - 4 + len);
2437c959318SMatthias Ringwald             big_endian_store_32( header_packetlogger, 4,  (uint32_t) curr_time.tv_sec);
244f8fbdce0SMatthias Ringwald             big_endian_store_32( header_packetlogger, 8, curr_time.tv_usec);
2458b658ebcSmatthias.ringwald             switch (packet_type){
2468b658ebcSmatthias.ringwald                 case HCI_COMMAND_DATA_PACKET:
2471a1c8389SMatthias Ringwald                     header_packetlogger[12] = 0x00;
2488b658ebcSmatthias.ringwald                     break;
2498b658ebcSmatthias.ringwald                 case HCI_ACL_DATA_PACKET:
2508b658ebcSmatthias.ringwald                     if (in) {
2511a1c8389SMatthias Ringwald                         header_packetlogger[12] = 0x03;
2528b658ebcSmatthias.ringwald                     } else {
2531a1c8389SMatthias Ringwald                         header_packetlogger[12] = 0x02;
2548b658ebcSmatthias.ringwald                     }
2558b658ebcSmatthias.ringwald                     break;
2568d675e3dS[email protected]                 case HCI_SCO_DATA_PACKET:
2578d675e3dS[email protected]                     if (in) {
2581a1c8389SMatthias Ringwald                         header_packetlogger[12] = 0x09;
2598d675e3dS[email protected]                     } else {
2601a1c8389SMatthias Ringwald                         header_packetlogger[12] = 0x08;
2618d675e3dS[email protected]                     }
2628d675e3dS[email protected]                     break;
2638b658ebcSmatthias.ringwald                 case HCI_EVENT_PACKET:
2641a1c8389SMatthias Ringwald                     header_packetlogger[12] = 0x01;
2658b658ebcSmatthias.ringwald                     break;
2660d79c710Smatthias.ringwald                 case LOG_MESSAGE_PACKET:
2671a1c8389SMatthias Ringwald                     header_packetlogger[12] = 0xfc;
2680d79c710Smatthias.ringwald                     break;
2698b658ebcSmatthias.ringwald                 default:
2708b658ebcSmatthias.ringwald                     return;
2718b658ebcSmatthias.ringwald             }
272*acb15818SMatthias Ringwald             res = write (dump_file, &header_packetlogger, PKTLOG_HDR_SIZE);
273*acb15818SMatthias Ringwald             res = write (dump_file, packet, len );
2740d79c710Smatthias.ringwald             break;
2750d79c710Smatthias.ringwald 
2760d79c710Smatthias.ringwald         default:
2770d79c710Smatthias.ringwald             break;
2788b658ebcSmatthias.ringwald     }
279*acb15818SMatthias Ringwald     UNUSED(res);
280eb443d3dSMatthias Ringwald #else
281eb443d3dSMatthias Ringwald 
282ad6274a7SMatthias Ringwald     printf_timestamp();
283eb443d3dSMatthias Ringwald     printf_packet(packet_type, in, packet, len);
284eb443d3dSMatthias Ringwald 
28568e27c0fSmatthias.ringwald #endif
28679662672Smatthias.ringwald }
28779662672Smatthias.ringwald 
2888a37b10aSMatthias Ringwald static int hci_dump_log_level_active(int log_level){
289fa087deaSMatthias Ringwald     if (log_level < HCI_DUMP_LOG_LEVEL_DEBUG) return 0;
290fa087deaSMatthias Ringwald     if (log_level > HCI_DUMP_LOG_LEVEL_ERROR) return 0;
2918a37b10aSMatthias Ringwald     return log_level_enabled[log_level];
2928a37b10aSMatthias Ringwald }
2938a37b10aSMatthias Ringwald 
29494be1a66SMatthias Ringwald void hci_dump_log_va_arg(int log_level, const char * format, va_list argptr){
2956401061aSMatthias Ringwald     if (!hci_dump_log_level_active(log_level)) return;
2966401061aSMatthias Ringwald 
297eb443d3dSMatthias Ringwald #ifdef HAVE_POSIX_FILE_IO
2986401061aSMatthias Ringwald     if (dump_file >= 0){
299eb443d3dSMatthias Ringwald         int len = vsnprintf(log_message_buffer, sizeof(log_message_buffer), format, argptr);
300eb443d3dSMatthias Ringwald         hci_dump_packet(LOG_MESSAGE_PACKET, 0, (uint8_t*) log_message_buffer, len);
301b8ae70b4SMatthias Ringwald         return;
3026401061aSMatthias Ringwald     }
3036401061aSMatthias Ringwald #endif
3046401061aSMatthias Ringwald 
305ad6274a7SMatthias Ringwald     printf_timestamp();
3061df3b679S[email protected]     printf("LOG -- ");
3071df3b679S[email protected]     vprintf(format, argptr);
3081fd51e45S[email protected]     printf("\n");
30994be1a66SMatthias Ringwald }
31094be1a66SMatthias Ringwald 
31194be1a66SMatthias Ringwald void hci_dump_log(int log_level, const char * format, ...){
31294be1a66SMatthias Ringwald     va_list argptr;
31394be1a66SMatthias Ringwald     va_start(argptr, format);
31494be1a66SMatthias Ringwald     hci_dump_log_va_arg(log_level, format, argptr);
3151df3b679S[email protected]     va_end(argptr);
316a1d7dd1fSmatthias.ringwald }
317a1d7dd1fSmatthias.ringwald 
31820ea11b9S[email protected] #ifdef __AVR__
3198a37b10aSMatthias Ringwald void hci_dump_log_P(int log_level, PGM_P format, ...){
3208a37b10aSMatthias Ringwald     if (!hci_dump_log_level_active(log_level)) return;
32120ea11b9S[email protected]     va_list argptr;
32220ea11b9S[email protected]     va_start(argptr, format);
32320ea11b9S[email protected]     printf_P(PSTR("LOG -- "));
32420ea11b9S[email protected]     vfprintf_P(stdout, format, argptr);
32520ea11b9S[email protected]     printf_P(PSTR("\n"));
32620ea11b9S[email protected]     va_end(argptr);
32720ea11b9S[email protected] }
32820ea11b9S[email protected] #endif
32920ea11b9S[email protected] 
33071de195eSMatthias Ringwald void hci_dump_close(void){
331eb443d3dSMatthias Ringwald #ifdef HAVE_POSIX_FILE_IO
33279662672Smatthias.ringwald     close(dump_file);
33368e27c0fSmatthias.ringwald #endif
334eb443d3dSMatthias Ringwald     dump_file = -1;
33579662672Smatthias.ringwald }
33679662672Smatthias.ringwald 
3378a37b10aSMatthias Ringwald void hci_dump_enable_log_level(int log_level, int enable){
338fa087deaSMatthias Ringwald     if (log_level < HCI_DUMP_LOG_LEVEL_DEBUG) return;
339fa087deaSMatthias Ringwald     if (log_level > HCI_DUMP_LOG_LEVEL_ERROR) return;
3408a37b10aSMatthias Ringwald     log_level_enabled[log_level] = enable;
3418a37b10aSMatthias Ringwald }
3428a37b10aSMatthias Ringwald 
343