Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / ui / tap-rtp-common.c
blobe7abc7c0b52b29978ba06479ae01b80fed1d9625
1 /* tap-rtp-common.c
2 * RTP stream handler functions used by tshark and wireshark
4 * Copyright 2008, Ericsson AB
5 * By Balint Reczey <balint.reczey@ericsson.com>
7 * most functions are copied from ui/gtk/rtp_stream.c and ui/gtk/rtp_analysis.c
8 * Copyright 2003, Alcatel Business Systems
9 * By Lars Ruoff <lars.ruoff@gmx.net>
11 * Wireshark - Network traffic analyzer
12 * By Gerald Combs <gerald@wireshark.org>
13 * Copyright 1998 Gerald Combs
15 * SPDX-License-Identifier: GPL-2.0-or-later
18 #include "config.h"
20 #include <stdlib.h>
21 #include <string.h>
22 #include <math.h>
24 #include <glib.h>
26 #include <epan/rtp_pt.h>
27 #include <epan/addr_resolv.h>
28 #include <epan/proto_data.h>
29 #include <epan/dissectors/packet-rtp.h>
30 #include <wsutil/pint.h>
31 #include "rtp_stream.h"
32 #include "tap-rtp-common.h"
34 /* XXX: are changes needed to properly handle situations where
35 info_all_data_present == false ?
36 E.G., when captured frames are truncated.
39 /****************************************************************************/
40 /* Type for storing and writing rtpdump information */
41 typedef struct st_rtpdump_info {
42 double rec_time; /**< milliseconds since start of recording */
43 uint16_t num_samples; /**< number of bytes in *frame */
44 const uint8_t *samples; /**< data bytes */
45 } rtpdump_info_t;
47 /****************************************************************************/
48 /* init rtpstream_info_t structure */
49 void rtpstream_info_init(rtpstream_info_t *info)
51 memset(info, 0, sizeof(rtpstream_info_t));
54 /****************************************************************************/
55 /* malloc and init rtpstream_info_t structure */
56 rtpstream_info_t *rtpstream_info_malloc_and_init(void)
58 rtpstream_info_t *dest;
60 dest = g_new(rtpstream_info_t, 1);
61 rtpstream_info_init(dest);
63 return dest;
66 /****************************************************************************/
67 /* deep copy of rtpstream_info_t */
68 void rtpstream_info_copy_deep(rtpstream_info_t *dest, const rtpstream_info_t *src)
70 /* Deep clone of contents */
71 *dest = *src; /* memberwise copy of struct */
72 copy_address(&(dest->id.src_addr), &(src->id.src_addr));
73 copy_address(&(dest->id.dst_addr), &(src->id.dst_addr));
74 dest->all_payload_type_names = g_strdup(src->all_payload_type_names);
77 /****************************************************************************/
78 /* malloc and deep copy rtpstream_info_t structure */
79 rtpstream_info_t *rtpstream_info_malloc_and_copy_deep(const rtpstream_info_t *src)
81 rtpstream_info_t *dest;
83 dest = g_new(rtpstream_info_t, 1);
84 rtpstream_info_copy_deep(dest, src);
86 return dest;
89 /****************************************************************************/
90 /* free rtpstream_info_t referenced values */
91 void rtpstream_info_free_data(rtpstream_info_t *info)
93 if (info->all_payload_type_names != NULL) {
94 g_free(info->all_payload_type_names);
97 rtpstream_id_free(&info->id);
100 /****************************************************************************/
101 /* free rtpstream_info_t referenced values and whole structure */
102 void rtpstream_info_free_all(rtpstream_info_t *info)
104 rtpstream_info_free_data(info);
105 g_free(info);
108 /****************************************************************************/
109 /* GCompareFunc style comparison function for rtpstream_info_t */
110 int rtpstream_info_cmp(const void *aa, const void *bb)
112 const rtpstream_info_t *a = (const rtpstream_info_t *)aa;
113 const rtpstream_info_t *b = (const rtpstream_info_t *)bb;
115 if (a==b)
116 return 0;
117 if (a==NULL || b==NULL)
118 return 1;
119 if (rtpstream_id_equal(&(a->id),&(b->id),RTPSTREAM_ID_EQUAL_SSRC))
120 return 0;
121 else
122 return 1;
125 /****************************************************************************/
126 /* compare the endpoints of two RTP streams */
127 bool rtpstream_info_is_reverse(const rtpstream_info_t *stream_a, rtpstream_info_t *stream_b)
129 if (stream_a == NULL || stream_b == NULL)
130 return false;
132 if ((addresses_equal(&(stream_a->id.src_addr), &(stream_b->id.dst_addr)))
133 && (stream_a->id.src_port == stream_b->id.dst_port)
134 && (addresses_equal(&(stream_a->id.dst_addr), &(stream_b->id.src_addr)))
135 && (stream_a->id.dst_port == stream_b->id.src_port))
136 return true;
137 else
138 return false;
141 /****************************************************************************/
142 /* when there is a [re]reading of packet's */
143 void rtpstream_reset(rtpstream_tapinfo_t *tapinfo)
145 GList* list;
146 rtpstream_info_t *stream_info;
148 if (tapinfo->mode == TAP_ANALYSE) {
149 /* free the data items first */
150 if (tapinfo->strinfo_hash) {
151 g_hash_table_foreach(tapinfo->strinfo_hash, rtpstream_info_multihash_destroy_value, NULL);
152 g_hash_table_destroy(tapinfo->strinfo_hash);
154 list = g_list_first(tapinfo->strinfo_list);
155 while (list)
157 stream_info = (rtpstream_info_t *)(list->data);
158 rtpstream_info_free_data(stream_info);
159 g_free(list->data);
160 list = g_list_next(list);
162 g_list_free(tapinfo->strinfo_list);
163 tapinfo->strinfo_list = NULL;
164 tapinfo->strinfo_hash = NULL;
165 tapinfo->nstreams = 0;
166 tapinfo->npackets = 0;
169 return;
172 void rtpstream_reset_cb(void *arg)
174 rtpstream_tapinfo_t *ti =(rtpstream_tapinfo_t *)arg;
175 if (ti->tap_reset) {
176 /* Give listeners a chance to cleanup references. */
177 ti->tap_reset(ti);
179 rtpstream_reset(ti);
182 /****************************************************************************/
183 /* TAP INTERFACE */
184 /****************************************************************************/
186 /****************************************************************************/
187 /* redraw the output */
188 static void rtpstream_draw_cb(void *ti_ptr)
190 rtpstream_tapinfo_t *tapinfo = (rtpstream_tapinfo_t *)ti_ptr;
191 /* XXX: see rtpstream_on_update in rtp_streams_dlg.c for comments
192 g_signal_emit_by_name(top_level, "signal_rtpstream_update");
194 if (tapinfo && tapinfo->tap_draw) {
195 /* RTP_STREAM_DEBUG("streams: %d packets: %d", tapinfo->nstreams, tapinfo->npackets); */
196 tapinfo->tap_draw(tapinfo);
198 return;
203 /****************************************************************************/
204 void
205 remove_tap_listener_rtpstream(rtpstream_tapinfo_t *tapinfo)
207 if (tapinfo && tapinfo->is_registered) {
208 remove_tap_listener(tapinfo);
209 tapinfo->is_registered = false;
213 /****************************************************************************/
214 void
215 register_tap_listener_rtpstream(rtpstream_tapinfo_t *tapinfo, const char *fstring, rtpstream_tap_error_cb tap_error)
217 GString *error_string;
219 if (!tapinfo) {
220 return;
223 if (!tapinfo->is_registered) {
224 error_string = register_tap_listener("rtp", tapinfo,
225 fstring, 0, rtpstream_reset_cb, rtpstream_packet_cb,
226 rtpstream_draw_cb, NULL);
228 if (error_string != NULL) {
229 if (tap_error) {
230 tap_error(error_string);
232 g_string_free(error_string, TRUE);
233 exit(1);
236 tapinfo->is_registered = true;
241 * rtpdump file format
243 * The file starts with the tool to be used for playing this file,
244 * the multicast/unicast receive address and the port.
246 * #!rtpplay1.0 224.2.0.1/3456\n
248 * This is followed by one binary header (RD_hdr_t) and one RD_packet_t
249 * structure for each received packet. All fields are in network byte
250 * order. We don't need the source IP address since we can do mapping
251 * based on SSRC. This saves (a little) space, avoids non-IPv4
252 * problems and privacy/security concerns. The header is followed by
253 * the RTP/RTCP header and (optionally) the actual payload.
256 static const char *PAYLOAD_UNKNOWN_STR = "Unknown";
258 static void update_payload_names(rtpstream_info_t *stream_info, const struct _rtp_info *rtpinfo)
260 GString *payload_type_names;
261 const char *new_payload_type_str;
263 /* Ensure that we have non empty payload_type_str */
264 if (rtpinfo->info_payload_type_str != NULL) {
265 new_payload_type_str = rtpinfo->info_payload_type_str;
267 else {
268 /* String is created from const strings only */
269 new_payload_type_str = val_to_str_ext_const(rtpinfo->info_payload_type,
270 &rtp_payload_type_short_vals_ext,
271 PAYLOAD_UNKNOWN_STR
274 stream_info->payload_type_names[rtpinfo->info_payload_type] = new_payload_type_str;
276 /* Join all existing payload names to one string */
277 payload_type_names = g_string_sized_new(40); /* Preallocate memory */
278 for(int i=0; i<256; i++) {
279 if (stream_info->payload_type_names[i] != NULL) {
280 if (payload_type_names->len > 0) {
281 g_string_append(payload_type_names, ", ");
283 g_string_append(payload_type_names, stream_info->payload_type_names[i]);
286 if (stream_info->all_payload_type_names != NULL) {
287 g_free(stream_info->all_payload_type_names);
289 stream_info->all_payload_type_names = g_string_free(payload_type_names, FALSE);
292 bool rtpstream_is_payload_used(const rtpstream_info_t *stream_info, const uint8_t payload_type)
294 return stream_info->payload_type_names[payload_type] != NULL;
297 #define RTPFILE_VERSION "1.0"
300 * Write a header to the current output file.
301 * The header consists of an identifying string, followed
302 * by a binary structure.
304 void rtp_write_header(rtpstream_info_t *strinfo, FILE *file)
306 uint32_t start_sec; /* start of recording (GMT) (seconds) */
307 uint32_t start_usec; /* start of recording (GMT) (microseconds)*/
308 uint32_t source; /* network source (multicast address) */
309 size_t sourcelen;
310 uint16_t port; /* UDP port */
311 uint16_t padding; /* 2 padding bytes */
312 char* addr_str = address_to_display(NULL, &(strinfo->id.dst_addr));
314 fprintf(file, "#!rtpplay%s %s/%u\n", RTPFILE_VERSION,
315 addr_str,
316 strinfo->id.dst_port);
317 wmem_free(NULL, addr_str);
319 start_sec = g_htonl(strinfo->start_fd->abs_ts.secs);
320 start_usec = g_htonl(strinfo->start_fd->abs_ts.nsecs / 1000);
321 /* rtpdump only accepts uint32_t as source, will be fake for IPv6 */
322 memset(&source, 0, sizeof source);
323 sourcelen = strinfo->id.src_addr.len;
324 if (sourcelen > sizeof source)
325 sourcelen = sizeof source;
326 memcpy(&source, strinfo->id.src_addr.data, sourcelen);
327 port = g_htons(strinfo->id.src_port);
328 padding = 0;
330 if (fwrite(&start_sec, 4, 1, file) == 0)
331 return;
332 if (fwrite(&start_usec, 4, 1, file) == 0)
333 return;
334 if (fwrite(&source, 4, 1, file) == 0)
335 return;
336 if (fwrite(&port, 2, 1, file) == 0)
337 return;
338 if (fwrite(&padding, 2, 1, file) == 0)
339 return;
342 /* utility function for writing a sample to file in rtpdump -F dump format (.rtp)*/
343 static void rtp_write_sample(rtpdump_info_t* rtpdump_info, FILE* file)
345 uint16_t length; /* length of packet, including this header (may
346 be smaller than plen if not whole packet recorded) */
347 uint16_t plen; /* actual header+payload length for RTP, 0 for RTCP */
348 uint32_t offset; /* milliseconds since the start of recording */
350 length = g_htons(rtpdump_info->num_samples + 8);
351 plen = g_htons(rtpdump_info->num_samples);
352 offset = g_htonl(rtpdump_info->rec_time);
354 if (fwrite(&length, 2, 1, file) == 0)
355 return;
356 if (fwrite(&plen, 2, 1, file) == 0)
357 return;
358 if (fwrite(&offset, 4, 1, file) == 0)
359 return;
360 if (fwrite(rtpdump_info->samples, rtpdump_info->num_samples, 1, file) == 0)
361 return;
365 /****************************************************************************/
366 /* whenever a RTP packet is seen by the tap listener */
367 tap_packet_status rtpstream_packet_cb(void *arg, packet_info *pinfo, epan_dissect_t *edt _U_, const void *arg2, tap_flags_t flags _U_)
369 rtpstream_tapinfo_t *tapinfo = (rtpstream_tapinfo_t *)arg;
370 const struct _rtp_info *rtpinfo = (const struct _rtp_info *)arg2;
371 rtpstream_id_t new_stream_id;
372 rtpstream_info_t *stream_info = NULL;
373 rtpdump_info_t rtpdump_info;
375 /* gather infos on the stream this packet is part of.
376 * Shallow copy addresses as this is just for examination. */
377 rtpstream_id_copy_pinfo_shallow(pinfo,&new_stream_id,false);
378 new_stream_id.ssrc = rtpinfo->info_sync_src;
380 if (tapinfo->mode == TAP_ANALYSE) {
381 /* if display filtering activated and packet do not match, ignore it */
382 if (tapinfo->apply_display_filter && (pinfo->fd->passed_dfilter == 0)) {
383 return TAP_PACKET_DONT_REDRAW;
386 /* check whether we already have a stream with these parameters in the list */
387 if (tapinfo->strinfo_hash) {
388 stream_info = rtpstream_info_multihash_lookup(tapinfo->strinfo_hash, &new_stream_id);
391 /* not in the list? then create a new entry */
392 if (!stream_info) {
393 /* init info and collect id */
394 stream_info = rtpstream_info_malloc_and_init();
395 /* Deep copy addresses for the new entry. */
396 rtpstream_id_copy_pinfo(pinfo,&(stream_info->id),false);
397 stream_info->id.ssrc = rtpinfo->info_sync_src;
399 /* init counters for first packet */
400 rtpstream_info_analyse_init(stream_info, pinfo, rtpinfo);
402 /* add it to hash */
403 tapinfo->strinfo_list = g_list_prepend(tapinfo->strinfo_list, stream_info);
404 if (!tapinfo->strinfo_hash) {
405 tapinfo->strinfo_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
407 rtpstream_info_multihash_insert(tapinfo->strinfo_hash, stream_info);
410 /* update analysis counters */
411 rtpstream_info_analyse_process(stream_info, pinfo, rtpinfo);
413 /* increment the packets counter of all streams */
414 ++(tapinfo->npackets);
416 return TAP_PACKET_REDRAW; /* refresh output */
418 else if (tapinfo->mode == TAP_SAVE) {
419 if (rtpstream_id_equal(&new_stream_id, &(tapinfo->filter_stream_fwd->id), RTPSTREAM_ID_EQUAL_SSRC)) {
420 /* XXX - what if rtpinfo->info_all_data_present is
421 false, so that we don't *have* all the data? */
422 rtpdump_info.rec_time = nstime_to_msec(&pinfo->abs_ts) -
423 nstime_to_msec(&tapinfo->filter_stream_fwd->start_fd->abs_ts);
424 rtpdump_info.num_samples = rtpinfo->info_data_len;
425 rtpdump_info.samples = rtpinfo->info_data;
426 rtp_write_sample(&rtpdump_info, tapinfo->save_file);
429 else if (tapinfo->mode == TAP_MARK && tapinfo->tap_mark_packet) {
430 if (rtpstream_id_equal(&new_stream_id, &(tapinfo->filter_stream_fwd->id), RTPSTREAM_ID_EQUAL_SSRC)
431 || rtpstream_id_equal(&new_stream_id, &(tapinfo->filter_stream_rev->id), RTPSTREAM_ID_EQUAL_SSRC))
433 tapinfo->tap_mark_packet(tapinfo, pinfo->fd);
436 return TAP_PACKET_DONT_REDRAW;
439 /****************************************************************************/
440 /* evaluate rtpstream_info_t calculations */
441 /* - code is gathered from existing GTK/Qt/tui sources related to RTP statistics calculation
442 * - one place for calculations ensures that all wireshark tools shows same output for same input and avoids code duplication
444 void rtpstream_info_calculate(const rtpstream_info_t *strinfo, rtpstream_info_calc_t *calc)
446 double sumt;
447 double sumTS;
448 double sumt2;
449 double sumtTS;
450 double clock_drift_x;
451 uint32_t clock_rate_x;
452 double duration_x;
454 calc->src_addr_str = address_to_display(NULL, &(strinfo->id.src_addr));
455 calc->src_port = strinfo->id.src_port;
456 calc->dst_addr_str = address_to_display(NULL, &(strinfo->id.dst_addr));
457 calc->dst_port = strinfo->id.dst_port;
458 calc->ssrc = strinfo->id.ssrc;
460 calc->all_payload_type_names = wmem_strdup(NULL, strinfo->all_payload_type_names);
462 calc->packet_count = strinfo->packet_count;
463 /* packet count, lost packets */
464 calc->packet_expected = strinfo->rtp_stats.stop_seq_nr - strinfo->rtp_stats.start_seq_nr + 1;
465 calc->total_nr = strinfo->rtp_stats.total_nr;
466 calc->lost_num = calc->packet_expected - strinfo->rtp_stats.total_nr;
467 if (calc->packet_expected) {
468 calc->lost_perc = (double)(calc->lost_num*100)/(double)calc->packet_expected;
469 } else {
470 calc->lost_perc = 0;
473 calc->max_delta = strinfo->rtp_stats.max_delta;
474 calc->min_delta = strinfo->rtp_stats.min_delta;
475 calc->mean_delta = strinfo->rtp_stats.mean_delta;
476 calc->min_jitter = strinfo->rtp_stats.min_jitter;
477 calc->max_jitter = strinfo->rtp_stats.max_jitter;
478 calc->mean_jitter = strinfo->rtp_stats.mean_jitter;
479 calc->max_skew = strinfo->rtp_stats.max_skew;
480 calc->problem = strinfo->problem;
481 sumt = strinfo->rtp_stats.sumt;
482 sumTS = strinfo->rtp_stats.sumTS;
483 sumt2 = strinfo->rtp_stats.sumt2;
484 sumtTS = strinfo->rtp_stats.sumtTS;
485 duration_x = strinfo->rtp_stats.time - strinfo->rtp_stats.start_time;
487 if ((calc->packet_count >0) && (sumt2 > 0)) {
488 clock_drift_x = (calc->packet_count * sumtTS - sumt * sumTS) / (calc->packet_count * sumt2 - sumt * sumt);
489 calc->clock_drift_ms = duration_x * (clock_drift_x - 1.0);
490 clock_rate_x = (uint32_t)(strinfo->rtp_stats.clock_rate * clock_drift_x);
491 calc->freq_drift_hz = clock_drift_x * clock_rate_x;
492 calc->freq_drift_perc = 100.0 * (clock_drift_x - 1.0);
493 } else {
494 calc->clock_drift_ms = 0.0;
495 calc->freq_drift_hz = 0.0;
496 calc->freq_drift_perc = 0.0;
498 calc->duration_ms = duration_x / 1000.0;
499 calc->sequence_err = strinfo->rtp_stats.sequence;
500 calc->start_time_ms = strinfo->rtp_stats.start_time / 1000.0;
501 calc->first_packet_num = strinfo->rtp_stats.first_packet_num;
502 calc->last_packet_num = strinfo->rtp_stats.max_nr;
505 /****************************************************************************/
506 /* free rtpstream_info_calc_t structure (internal items) */
507 void rtpstream_info_calc_free(rtpstream_info_calc_t *calc)
509 wmem_free(NULL, calc->src_addr_str);
510 wmem_free(NULL, calc->dst_addr_str);
511 wmem_free(NULL, calc->all_payload_type_names);
514 /****************************************************************************/
515 /* Init analyse counters in rtpstream_info_t from pinfo */
516 void rtpstream_info_analyse_init(rtpstream_info_t *stream_info, const packet_info *pinfo, const struct _rtp_info *rtpinfo)
518 struct _rtp_packet_info *p_packet_data = NULL;
520 /* reset stream stats */
521 stream_info->first_payload_type = rtpinfo->info_payload_type;
522 stream_info->first_payload_type_name = rtpinfo->info_payload_type_str;
523 stream_info->start_fd = pinfo->fd;
524 stream_info->start_rel_time = pinfo->rel_ts;
525 stream_info->start_abs_time = pinfo->abs_ts;
527 /* reset RTP stats */
528 stream_info->rtp_stats.first_packet = true;
529 stream_info->rtp_stats.reg_pt = PT_UNDEFINED;
531 /* Get the Setup frame number who set this RTP stream */
532 p_packet_data = (struct _rtp_packet_info *)p_get_proto_data(wmem_file_scope(), (packet_info *)pinfo, proto_get_id_by_filter_name("rtp"), RTP_CONVERSATION_PROTO_DATA);
533 if (p_packet_data)
534 stream_info->setup_frame_number = p_packet_data->frame_number;
535 else
536 stream_info->setup_frame_number = 0xFFFFFFFF;
539 /****************************************************************************/
540 /* Update analyse counters in rtpstream_info_t from pinfo */
541 void rtpstream_info_analyse_process(rtpstream_info_t *stream_info, const packet_info *pinfo, const struct _rtp_info *rtpinfo)
543 /* get RTP stats for the packet */
544 rtppacket_analyse(&(stream_info->rtp_stats), pinfo, rtpinfo);
545 if (stream_info->payload_type_names[rtpinfo->info_payload_type] == NULL ) {
546 update_payload_names(stream_info, rtpinfo);
549 if (stream_info->rtp_stats.flags & STAT_FLAG_WRONG_TIMESTAMP
550 || stream_info->rtp_stats.flags & STAT_FLAG_WRONG_SEQ)
551 stream_info->problem = true;
553 /* increment the packets counter for this stream */
554 ++(stream_info->packet_count);
555 stream_info->stop_rel_time = pinfo->rel_ts;
558 /****************************************************************************/
559 /* Get hash for rtpstream_info_t */
560 unsigned rtpstream_to_hash(const void *key)
562 if (key) {
563 return rtpstream_id_to_hash(&((rtpstream_info_t *)key)->id);
564 } else {
565 return 0;
569 /****************************************************************************/
570 /* Inserts new_stream_info to multihash if its not there */
572 void rtpstream_info_multihash_insert(GHashTable *multihash, rtpstream_info_t *new_stream_info)
574 GList *hlist = (GList *)g_hash_table_lookup(multihash, GINT_TO_POINTER(rtpstream_to_hash(new_stream_info)));
575 bool found = false;
576 if (hlist) {
577 // Key exists in hash
578 GList *list = g_list_first(hlist);
579 while (list)
581 if (rtpstream_id_equal(&(new_stream_info->id), &((rtpstream_info_t *)(list->data))->id, RTPSTREAM_ID_EQUAL_SSRC)) {
582 found = true;
583 break;
585 list = g_list_next(list);
587 if (!found) {
588 // stream_info is not in list yet, add it
589 hlist = g_list_prepend(hlist, new_stream_info);
591 } else {
592 // No key in hash, init new list
593 hlist = g_list_prepend(hlist, new_stream_info);
595 g_hash_table_insert(multihash, GINT_TO_POINTER(rtpstream_to_hash(new_stream_info)), hlist);
598 /****************************************************************************/
599 /* Lookup stream_info in multihash */
601 rtpstream_info_t *rtpstream_info_multihash_lookup(GHashTable *multihash, rtpstream_id_t *stream_id)
603 GList *hlist = (GList *)g_hash_table_lookup(multihash, GINT_TO_POINTER(rtpstream_to_hash(stream_id)));
604 if (hlist) {
605 // Key exists in hash
606 GList *list = g_list_first(hlist);
607 while (list)
609 if (rtpstream_id_equal(stream_id, &((rtpstream_info_t *)(list->data))->id, RTPSTREAM_ID_EQUAL_SSRC)) {
610 return (rtpstream_info_t *)(list->data);
612 list = g_list_next(list);
616 // No stream_info in hash or was not found in existing list
617 return NULL;
620 /****************************************************************************/
621 /* Destroys GList used in multihash */
623 void rtpstream_info_multihash_destroy_value(void *key _U_, void *value, void *user_data _U_)
625 g_list_free((GList *)value);