1 /*
2  * Copyright (C) 2018 Knowles Electronics
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 #include <stdio.h>
18 #include <stdlib.h>
19 #include <poll.h>
20 #include <string.h>
21 #include <errno.h>
22 #include <time.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <cutils/properties.h>
28 
29 #define LOG_TAG "ia_crash_event_logger"
30 #include <cutils/log.h>
31 #include <cutils/uevent.h>
32 #include "crash_analyzer.h"
33 
34 #define UEVENT_MSG_LEN          (1024)
35 #define BUF_SIZE                (4096)
36 #define CRASH_LOGGER_DEV        "/dev/crashdump"
37 #define REGDUMP_LOGGER_DEV	"/dev/regdump"
38 #define CRASH_DUMP_FILE_PREFIX  "/data/data/dump_crash_"
39 #define REG_ACCESS_FILE_PREFIX	"/data/data/dump_reg_access_history_"
40 #define CRASH_REASON_FILE_PREFIX  "/data/data/dump_crash_reason_"
41 #define SSR_CRASH_REASON_PREFIX  "ia_dump_crash_reason_"
42 #define BIN_EXTN                ".bin"
43 #define TXT_EXTN                ".txt"
44 #define MAX_FILENAME_LEN        512
45 #define MAX_TIMESTR_LEN         64
46 #define CRASH_DUMP_ANALYZER_MAX_STR_LEN  512
47 
48 #define SSR_RAMDUMP_PREFIX      "ramdump_audio_codec_"
49 #define SSR_CRASH_FILE_PREFIX   "ia_crash_dump_"
50 #define SSR_REG_FILE_PREFIX     "ia_reg_access_history_"
51 #define SSR_DUMP_PATH           "/data/vendor/ssrdump/"
52 
53 int g_exit_socket[2];
54 
sigint_handler(int sig __unused)55 void sigint_handler(int sig __unused) {
56     ALOGE("Interrupted, setting the exit condition");
57     if (g_exit_socket[0] >= 0)
58         write(g_exit_socket[0], "T", 1);
59 }
60 
61 char *crash_dump_split_file_names[] =
62     {"/data/data/dump_debug_CM4_",
63     "/data/data/dump_debug_HMD_",
64     "/data/data/dump_debug_DMX_",
65     "/data/data/dump_crash_CM4_",
66     "/data/data/dump_crash_HMD_",
67     "/data/data/dump_crash_DMX_",
68     "/data/data/dump_crash_SSP_RAM0_",
69     "/data/data/dump_crash_SSP_RAM1_",
70     "/data/data/dump_crash_SSP_ROM0_",
71     CRASH_REASON_FILE_PREFIX
72     };
73 
74 char *ssr_crash_dump_split_file_names[] = {
75     "ia_dump_debug_CM4_",
76     "ia_dump_debug_HMD_",
77     "ia_dump_debug_DMX_",
78     "ia_dump_crash_CM4_",
79     "ia_dump_crash_HMD_",
80     "ia_dump_crash_DMX_",
81     "ia_dump_crash_SSP_RAM0_",
82     "ia_dump_crash_SSP_RAM1_",
83     "ia_dump_crash_SSP_ROM0_",
84     SSR_CRASH_REASON_PREFIX
85 };
86 
dump_crash_reason(const unsigned char * crash_dump_buf,const int crash_dump_len,const unsigned char * crash_reason_buf,const int crash_reason_len,const char * time_stamp,bool is_ssr)87 void dump_crash_reason(const unsigned char *crash_dump_buf,
88                        const int crash_dump_len,
89                        const unsigned char *crash_reason_buf,
90                        const int crash_reason_len,
91                        const char *time_stamp, bool is_ssr)
92 {
93 
94     FILE *out_fp = NULL;
95     char file_name[MAX_FILENAME_LEN] = {0};
96     char crash_dump_analyzer_str[CRASH_DUMP_ANALYZER_MAX_STR_LEN] = {0};
97     int len = 0;
98     const char *crash_dump_title = " crash_analysis:";
99 
100     if (is_ssr) {
101         snprintf(file_name, MAX_FILENAME_LEN, "%s%s%s%s%s",
102                  SSR_DUMP_PATH, SSR_RAMDUMP_PREFIX,
103                  SSR_CRASH_REASON_PREFIX,
104                  time_stamp, BIN_EXTN);
105     } else {
106         snprintf(file_name, MAX_FILENAME_LEN, "%s%s%s",
107                  CRASH_REASON_FILE_PREFIX, time_stamp,
108                  TXT_EXTN);
109     }
110 
111     out_fp = fopen(file_name, "w");
112     if (out_fp == NULL) {
113         ALOGE("Failed to open %s for writting", file_name);
114         goto exit;
115     }
116 
117     len = strnlen((const char *)crash_reason_buf, crash_reason_len);
118 
119     if (fwrite(crash_reason_buf, 1, len, out_fp) != len) {
120         ALOGE("%s: ERROR writing to CRASH REASON FILE", __func__);
121         goto exit;
122     }
123 
124     len = analyse_crash_info(
125             crash_dump_buf, crash_dump_len, crash_dump_analyzer_str,
126             CRASH_DUMP_ANALYZER_MAX_STR_LEN);
127     if (len > 0) {
128         fwrite(crash_dump_title, 1, strlen(crash_dump_title), out_fp);
129         fwrite(crash_dump_analyzer_str, 1,
130                 strlen(crash_dump_analyzer_str), out_fp);
131     }
132     ALOGI("Crash logs saved to %s", file_name);
133 exit:
134     if (out_fp != NULL) {
135         fclose(out_fp);
136     }
137 
138 }
139 
split_crash_dump_buffer(unsigned char * buf,const int len,const char * time_stamp)140 int split_crash_dump_buffer(unsigned char *buf, const int len,
141                             const char* time_stamp)
142 {
143 
144     unsigned int file_index = 0, size = 0, tot_len = 0, flen = 0;
145     int fcount = 0;
146     unsigned char *ptr = NULL;
147     FILE *fp = NULL;
148     char file_name[MAX_FILENAME_LEN] = {0};
149     int number_crashdump_files = sizeof(crash_dump_split_file_names) /
150             sizeof(crash_dump_split_file_names[0]);
151 
152     if (buf == NULL || time_stamp == NULL || len <= 0) {
153         ALOGE("%s: Bad parameters", __func__);
154         return -1;
155     }
156 
157     while ((tot_len + STEP_LENGTH - 1 < len) &&
158            (fcount++ < number_crashdump_files)) {
159         file_index = buf[tot_len];
160 
161         size = buf[tot_len + 8] |
162                buf[tot_len + 9] << 8 |
163                buf[tot_len + 10] << 16 |
164                buf[tot_len + 11] << 24;
165 
166         tot_len += STEP_LENGTH;
167 
168         if (file_index >= number_crashdump_files || size > len - tot_len) {
169             continue;
170         }
171 
172         /* Some special handling is needed for crash reason file */
173         if (!strcmp(crash_dump_split_file_names[file_index],
174                 CRASH_REASON_FILE_PREFIX)) {
175             dump_crash_reason(buf, len, buf + tot_len, size, time_stamp,
176                               false);
177         }
178         else {
179             snprintf(file_name, MAX_FILENAME_LEN, "%s%s%s",
180                      crash_dump_split_file_names[file_index],
181                      time_stamp, BIN_EXTN);
182 
183             fp = fopen(file_name, "w+");
184 
185             ptr = buf + tot_len;
186 
187             flen = fwrite(ptr , 1, size, fp);
188             tot_len += size;
189             fclose(fp);
190             ALOGI("Crash logs saved to %s", file_name);
191         }
192     }
193     return 0;
194 }
195 
split_crash_dump_file(const char * crash_dump_filename,const char * time_stamp)196 int split_crash_dump_file (const char* crash_dump_filename,
197                         const char* time_stamp)
198 {
199     int fd, fil_len;
200     FILE *fp;
201     struct stat st;
202     unsigned char *buf;
203     int len,ret ;
204 
205     fp = fopen(crash_dump_filename, "r");
206     if (!fp)
207     {
208         ALOGE("File open error %s \n", crash_dump_filename);
209         return -1;
210     }
211 
212     fd = fileno(fp);
213     fstat(fd, &st);
214     fil_len = st.st_size;
215     buf = (unsigned char*) malloc(fil_len);
216 
217     if (NULL == buf) {
218         ALOGE("Failed to allocate buffer exiting");
219         ret = -1;
220         goto exit;
221     }
222 
223     len = fread(buf,1,  fil_len, fp);
224     if (len <=0) {
225         ALOGE("file reading error %s\n", crash_dump_filename);
226         ret = -1;
227         goto exit;
228     }
229     ret = split_crash_dump_buffer(buf, len, time_stamp);
230 
231 exit:
232     if (fp)
233         fclose (fp);
234     if (buf)
235         free(buf);
236     return ret;
237 }
238 
dump_crash_log()239 void dump_crash_log() {
240     void *buf = NULL;
241     int inp_fp = -1, out_fp = -1;
242     int bytes_read = 0;
243     int err = 0;
244     time_t t;
245     struct tm *tm;
246     char file_name[MAX_FILENAME_LEN];
247     char curr_time[MAX_TIMESTR_LEN];
248 
249     buf = malloc(BUF_SIZE);
250     if (NULL == buf) {
251         ALOGE("Failed to allocate buffer exiting");
252         err = -1;
253         goto exit;
254     }
255 
256     inp_fp = open(CRASH_LOGGER_DEV, O_RDONLY);
257     if (inp_fp == -1) {
258         ALOGE("Failed to open %s with error %d(%s)",
259                 CRASH_LOGGER_DEV, errno, strerror(errno));
260         goto exit;
261     }
262 
263     strcpy(file_name, CRASH_DUMP_FILE_PREFIX);
264     t = time(NULL);
265     tm = localtime(&t);
266     strftime(curr_time, 64, "%F_%H_%M_%S", tm);
267     strcat(file_name, curr_time);
268     strcat(file_name, BIN_EXTN);
269 
270     out_fp = open(file_name, O_WRONLY | O_CREAT, 0644);
271     if (out_fp == -1) {
272         ALOGE("Failed to open %s for writing", file_name);
273         goto exit;
274     }
275 
276     do {
277         bytes_read = read(inp_fp, buf, BUF_SIZE);
278         if (bytes_read > 0)
279             write(out_fp, buf, bytes_read);
280     } while (bytes_read > 0);
281 
282     ALOGI("Crash logs has been dumped to %s", file_name);
283     close(out_fp);
284     out_fp = -1;
285     close(inp_fp);
286     inp_fp = -1;
287     free(buf);
288     buf = NULL;
289     split_crash_dump_file(file_name, curr_time);
290 
291 exit:
292     if (out_fp != -1) {
293         close(out_fp);
294     }
295 
296     if (inp_fp != -1) {
297         close(inp_fp);
298     }
299 
300     if (buf) {
301         free(buf);
302     }
303 }
304 
dump_reg_access_hist_log()305 void dump_reg_access_hist_log() {
306     void *buf = NULL;
307     int inp_fp = -1, out_fp = -1;
308     int bytes_read = 0;
309     int err = 0;
310     time_t t;
311     struct tm *tm;
312     char file_name[MAX_FILENAME_LEN];
313     char curr_time[MAX_TIMESTR_LEN];
314 
315     buf = malloc(BUF_SIZE);
316     if (!buf) {
317         ALOGE("Failed to allocate buffer exiting");
318         err = -1;
319         goto exit;
320     }
321 
322     inp_fp = open(REGDUMP_LOGGER_DEV, O_RDONLY);
323     if (inp_fp == -1) {
324         ALOGE("Failed to open %s with error %d(%s)",
325                 REGDUMP_LOGGER_DEV, errno, strerror(errno));
326         goto exit;
327     }
328 
329     strcpy(file_name, REG_ACCESS_FILE_PREFIX);
330     t = time(NULL);
331     tm = localtime(&t);
332     strftime(curr_time, 64, "%F_%H_%M_%S", tm);
333     strcat(file_name, curr_time);
334     strcat(file_name, TXT_EXTN);
335 
336     out_fp = open(file_name, O_WRONLY | O_CREAT, 0644);
337     if (out_fp == -1) {
338         ALOGE("Failed to open %s for writing", file_name);
339         goto exit;
340     }
341 
342     do {
343         bytes_read = read(inp_fp, buf, BUF_SIZE);
344         if (bytes_read > 0)
345             write(out_fp, buf, bytes_read);
346     } while (bytes_read > 0);
347 
348     ALOGI("Register access history has been dumped to %s", file_name);
349 
350 exit:
351     if (out_fp != -1) {
352         close(out_fp);
353     }
354 
355     if (inp_fp != -1) {
356         close(inp_fp);
357     }
358 
359     if (buf) {
360         free(buf);
361     }
362 }
363 
364 /* --- functions for SSR detector ---*/
ssr_split_bin(unsigned char * buf,int len,const char * time_stamp)365 int ssr_split_bin(unsigned char *buf, int len, const char* time_stamp) {
366     unsigned int file_index = 0, size = 0, tot_len = 0, flen = 0;
367     unsigned char *ptr = NULL;
368     char file_name[MAX_FILENAME_LEN] = {0};
369     FILE *fp = NULL;
370     int fcount = 0;
371     int number_crashdump_files = sizeof(ssr_crash_dump_split_file_names) /
372             sizeof(ssr_crash_dump_split_file_names[0]);
373 
374     if (buf == NULL || time_stamp == NULL || len <= 0) {
375         ALOGE("%s: Bad parameters", __func__);
376         return -1;
377     }
378 
379     while ((tot_len + STEP_LENGTH - 1 < len) &&
380            (fcount++ < number_crashdump_files)) {
381 
382         file_index = buf[tot_len];
383 
384         size = buf[tot_len + 8] |
385                 buf[tot_len + 9]  << 8  |
386                 buf[tot_len + 10] << 16 |
387                 buf[tot_len + 11] << 24 ;
388 
389         tot_len += STEP_LENGTH;
390 
391         if (file_index >= number_crashdump_files || size > len - tot_len) {
392             continue;
393         }
394 
395         /* Some special handling is needed for crash reason file */
396         if (!strcmp(ssr_crash_dump_split_file_names[file_index],
397                 SSR_CRASH_REASON_PREFIX)) {
398             dump_crash_reason(buf, len, buf + tot_len, size, time_stamp, true);
399             continue;
400         }
401 
402         snprintf(file_name, MAX_FILENAME_LEN, "%s%s%s%s%s",
403                 SSR_DUMP_PATH, SSR_RAMDUMP_PREFIX,
404                 ssr_crash_dump_split_file_names[file_index],
405                 time_stamp, BIN_EXTN);
406 
407         fp = fopen(file_name, "w+");
408 
409         ptr = buf + tot_len;
410         flen = fwrite(ptr, 1, size, fp);
411         tot_len += size;
412         fclose(fp);
413         ALOGI("SSR Crash logs saved to %s", file_name);
414     }
415     return 0;
416 }
417 
ssr_split_crash_dump_file(const char * crash_dump_filename,const char * time_stamp)418 int ssr_split_crash_dump_file(const char* crash_dump_filename,
419                                const char* time_stamp) {
420     int fd = -1, fil_len = 0;
421     FILE *fp = NULL;
422     struct stat st;
423     unsigned char *buf = NULL;
424     int len = 0, ret = 0;
425 
426     fp = fopen(crash_dump_filename, "r");
427     if (!fp) {
428         ALOGE("File open error %s \n", crash_dump_filename);
429         return -1;
430     }
431 
432     fd = fileno(fp);
433     fstat(fd, &st);
434     fil_len = st.st_size;
435     buf = (unsigned char *)malloc(fil_len);
436 
437     if (NULL == buf) {
438         ALOGE("Failed to allocate buffer exiting");
439         ret = -1;
440         goto exit;
441     }
442 
443     len = fread(buf, 1, fil_len, fp);
444     if (len <= 0) {
445         ALOGE("file reading error %s\n", crash_dump_filename);
446         ret = -1;
447         goto exit;
448     }
449     ret = ssr_split_bin(buf, len, time_stamp);
450 
451 exit:
452     if (fp) {
453         fclose(fp);
454     }
455 
456     if (buf) {
457         free(buf);
458     }
459     return ret;
460 }
461 
ssr_copy_log(const char * src_path,const char * dis_path)462 void ssr_copy_log(const char* src_path, const char* dis_path) {
463     int src_fp = -1, dis_fp = -1;
464     int bytes_read = 0;
465     void *temp_buf = NULL;
466 
467     // allocate temp buf
468     temp_buf = malloc(BUF_SIZE);
469     if (!temp_buf) {
470         ALOGE("Failed to allocate buffer exiting");
471         goto exit;
472     }
473 
474     // open src file
475     src_fp = open(src_path, O_RDONLY);
476     if (src_fp == -1) {
477         ALOGE("Failed to open %s with error %d(%s)",
478                 src_path, errno, strerror(errno));
479         goto exit;
480     }
481 
482     // open dis file and append
483     dis_fp = open(dis_path, O_CREAT | O_SYNC | O_WRONLY,
484                   S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
485     if (dis_fp == -1) {
486         ALOGE("Failed to open %s with error %d(%s)",
487                 dis_path, errno, strerror(errno));
488         goto exit;
489     }
490 
491     // copy data
492     do {
493         bytes_read = read(src_fp, temp_buf, BUF_SIZE);
494         if (bytes_read > 0)
495             write(dis_fp, temp_buf, bytes_read);
496     } while (bytes_read > 0);
497 
498     ALOGI("Write data successfully from %s to %s", src_path, dis_path);
499 
500 exit:
501     if (src_fp != -1) {
502         close(src_fp);
503     }
504 
505     if (dis_fp != -1) {
506         close(dis_fp);
507     }
508 
509     if (temp_buf) {
510         free(temp_buf);
511     }
512     return;
513 }
514 
check_crash_reason_file(const char * time_stamp)515 void check_crash_reason_file(const char *time_stamp)
516 {
517     FILE *out_fp = NULL;
518     char file_name[MAX_FILENAME_LEN] = {0};
519     const char *default_crash_reason = "Iaxxx Firmware Crashed";
520 
521     snprintf(file_name, MAX_FILENAME_LEN, "%s%s%s%s%s",
522              SSR_DUMP_PATH, SSR_RAMDUMP_PREFIX,
523              SSR_CRASH_REASON_PREFIX,
524              time_stamp, BIN_EXTN);
525 
526     if (access(file_name, F_OK) == -1) {
527         ALOGE("Write default crash reason into the crash reason file");
528 
529         out_fp = fopen(file_name, "w");
530 
531         if (out_fp == NULL) {
532             ALOGE("%s: Failed to open: %s , errno: %s", __func__,
533                    file_name, strerror(errno));
534             return;
535         }
536 
537         fwrite(default_crash_reason, 1,
538                strlen(default_crash_reason), out_fp);
539 
540         fclose(out_fp);
541     }
542 }
543 
ssr_dump_log()544 void ssr_dump_log() {
545     time_t t;
546     struct tm *tm = NULL;
547     char curr_time_for_property[MAX_TIMESTR_LEN] = {0};
548     char curr_time_for_dump[MAX_TIMESTR_LEN] = {0};
549     char out_crash_file_name[MAX_FILENAME_LEN] = {0};
550     char out_reg_file_name[MAX_FILENAME_LEN] = {0};
551 
552     // get current time
553     t = time(NULL);
554     tm = localtime(&t);
555     snprintf(curr_time_for_property, MAX_TIMESTR_LEN,
556             "%.4d-%.2d-%.2d %.2d-%.2d-%.2d",
557             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
558             tm->tm_hour, tm->tm_min, tm->tm_sec);
559     snprintf(curr_time_for_dump, MAX_TIMESTR_LEN,
560             "%.02d-%.02d-%.02d_%.02d-%.02d-%.02d",
561             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
562             tm->tm_hour, tm->tm_min, tm->tm_sec);
563     // strftime(curr_time_for_dump, MAX_TIMESTR_LEN, "%F_%H_%M_%S", tm);
564 
565     // set property
566     property_set("vendor.debug.ssrdump.subsys", "audio_codec");
567     property_set("vendor.debug.ssrdump.timestamp", curr_time_for_property);
568 
569     // copy crash log only
570     snprintf(out_crash_file_name, MAX_FILENAME_LEN, "%s%s%s%s%s",
571             SSR_DUMP_PATH, SSR_RAMDUMP_PREFIX, SSR_CRASH_FILE_PREFIX,
572             curr_time_for_dump, BIN_EXTN);
573     ssr_copy_log(CRASH_LOGGER_DEV, out_crash_file_name);
574     ssr_split_crash_dump_file(out_crash_file_name, curr_time_for_dump);
575     ALOGI("Crash logs has been dumped to %s", out_crash_file_name);
576 
577     // copy reg
578     snprintf(out_reg_file_name, MAX_FILENAME_LEN, "%s%s%s%s%s",
579             SSR_DUMP_PATH, SSR_RAMDUMP_PREFIX, SSR_REG_FILE_PREFIX,
580             curr_time_for_dump, TXT_EXTN);
581     ssr_copy_log(REGDUMP_LOGGER_DEV, out_reg_file_name);
582     ALOGI("Register access history has been dumped %s", out_reg_file_name);
583 
584     // Check the crash reason file
585     check_crash_reason_file(curr_time_for_dump);
586 }
587 
588 /* --- main function --- */
main(int argc,char ** argv)589 int main(int argc, char** argv) {
590     int err = 0;
591     int timeout = -1; // Wait for event indefinitely
592     struct pollfd fds[2];
593     char msg[UEVENT_MSG_LEN];
594     int i, n;
595     bool ssr_monitor = false;
596 
597     if ((argc == 2) && !strcmp(argv[1], "-m")) {
598         ALOGD("Monitor the crash logs");
599         (void)umask(S_IWGRP | S_IWOTH);
600         ssr_monitor = true;
601     }
602 
603     signal(SIGINT, sigint_handler);
604 
605     if ( (argc == 2) && !strcmp(argv[1], "-f")) {
606         ALOGD("Read to get the crash logs");
607         dump_reg_access_hist_log();
608         dump_crash_log();
609         return 0;
610     }
611 
612     if (socketpair(AF_UNIX, SOCK_STREAM, 0, g_exit_socket) == -1) {
613         ALOGE("%s: Failed to create termination socket", __func__);
614         err = -1;
615         goto exit;
616     }
617 
618 
619     fds[0].events = POLLIN;
620     fds[0].fd = uevent_open_socket(64*1024, true);
621     if (fds[0].fd == -1) {
622         ALOGE("Error opening socket for hotplug uevent errno %d(%s)",
623                 errno, strerror(errno));
624         goto exit;
625     }
626     fds[1].events = POLLIN;
627     fds[1].fd = g_exit_socket[1];
628 
629     while (1) {
630         err = poll (fds, 2, timeout);
631 
632         if (fds[0].revents & POLLIN) {
633             n = uevent_kernel_multicast_recv(fds[0].fd, msg, UEVENT_MSG_LEN);
634             if (n <= 0) {
635                 continue;
636             }
637 
638             for (i = 0; i < n;) {
639                 if (strstr(msg + i, "IAXXX_CRASH_EVENT")) {
640                     ALOGD("IAXXX_CRASH_EVENT received trying to get the crash logs");
641                     if (ssr_monitor) {
642                         ssr_dump_log();
643                     } else {
644                         dump_reg_access_hist_log();
645                         dump_crash_log();
646                     }
647                 }
648 
649                 i += strlen(msg + i) + 1;
650             }
651         } else if (fds[1].revents & POLLIN) {
652             read(fds[1].fd, &n, sizeof(n)); /* clear the socket */
653             ALOGE("Interrupt received, exiting");
654             break;
655         } else {
656             ALOGI("Message ignored");
657         }
658     }
659 
660 exit:
661     if (g_exit_socket[0] >= 0) {
662         close(g_exit_socket[0]);
663     }
664 
665     if (g_exit_socket[1] >= 0) {
666         close(g_exit_socket[1]);
667     }
668 
669     return err;
670 }
671