TODO netlogon_user_flags_ntlmv2_enabled
[wireshark-sm.git] / epan / ftypes / ftype-time.c
blob4a10b6815ca8fb6efc3393b1a1a1466f48d46958
1 /*
2 * Wireshark - Network traffic analyzer
3 * By Gerald Combs <gerald@wireshark.org>
4 * Copyright 2001 Gerald Combs
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
9 #define _GNU_SOURCE
10 #include "config.h"
11 #include "ftypes-int.h"
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <math.h>
17 #include <epan/prefs.h>
18 #include <epan/to_str.h>
19 #include <wsutil/time_util.h>
20 #include <wsutil/ws_strptime.h>
21 #include <wsutil/safe-math.h>
22 #include <wsutil/array.h>
24 static enum ft_result
25 cmp_order(const fvalue_t *a, const fvalue_t *b, int *cmp)
27 *cmp = nstime_cmp(&(a->value.time), &(b->value.time));
28 return FT_OK;
32 * Get a nanoseconds value, starting at "p".
34 * Returns true on success, false on failure.
36 * If successful endptr points to the first invalid character.
38 static bool
39 get_nsecs(const char *startp, int *nsecs, const char **endptr)
41 int ndigits = 0;
42 int scale;
43 const char *p;
44 int val;
45 int digit;
46 int i;
49 * How many digits are in the string?
51 for (p = startp; g_ascii_isdigit(*p); p++)
52 ndigits++;
55 * If there are N characters in the string, the last of the
56 * characters would be the digit corresponding to 10^(9-N)
57 * nanoseconds.
59 scale = 9 - ndigits;
62 * Start at the last character, and work backwards.
64 val = 0;
65 while (p != startp) {
66 p--;
68 if (!g_ascii_isdigit(*p)) {
70 * Not a digit - error.
72 return false;
74 digit = *p - '0';
75 if (digit != 0) {
77 * Non-zero digit corresponding to that number
78 * of (10^scale) units.
80 * If scale is less than zero, this digit corresponds
81 * to a value less than a nanosecond, so this number
82 * isn't valid.
84 if (scale < 0)
85 return false;
86 for (i = 0; i < scale; i++)
87 digit *= 10;
88 val += digit;
90 scale++;
92 *nsecs = val;
93 if (endptr)
94 *endptr = startp + ndigits;
95 return true;
98 static bool
99 val_from_unix_time(fvalue_t *fv, const char *s)
101 const char *curptr;
102 char *endptr;
103 bool negative = false;
105 curptr = s;
107 if (*curptr == '-') {
108 negative = true;
109 curptr++;
113 * If it doesn't begin with ".", it should contain a seconds
114 * value.
116 if (*curptr != '.') {
118 * Get the seconds value.
120 fv->value.time.secs = strtoul(curptr, &endptr, 10);
121 if (endptr == curptr || (*endptr != '\0' && *endptr != '.'))
122 return false;
123 curptr = endptr;
124 if (*curptr == '.')
125 curptr++; /* skip the decimal point */
126 } else {
128 * No seconds value - it's 0.
130 fv->value.time.secs = 0;
131 curptr++; /* skip the decimal point */
135 * If there's more stuff left in the string, it should be the
136 * nanoseconds value.
138 if (*curptr != '\0') {
140 * Get the nanoseconds value.
142 if (!get_nsecs(curptr, &fv->value.time.nsecs, NULL))
143 return false;
144 } else {
146 * No nanoseconds value - it's 0.
148 fv->value.time.nsecs = 0;
151 if (negative) {
152 fv->value.time.secs = -fv->value.time.secs;
153 fv->value.time.nsecs = -fv->value.time.nsecs;
155 return true;
158 static bool
159 relative_val_from_uinteger64(fvalue_t *fv, const char *s _U_, uint64_t value, char **err_msg _U_)
161 fv->value.time.secs = (time_t)value;
162 fv->value.time.nsecs = 0;
163 return true;
166 static bool
167 relative_val_from_sinteger64(fvalue_t *fv, const char *s _U_, int64_t value, char **err_msg _U_)
169 fv->value.time.secs = (time_t)value;
170 fv->value.time.nsecs = 0;
171 return true;
174 static bool
175 relative_val_from_float(fvalue_t *fv, const char *s, double value, char **err_msg _U_)
177 if (val_from_unix_time(fv, s))
178 return true;
180 double whole, fraction;
182 fraction = modf(value, &whole);
183 fv->value.time.secs = (time_t)whole;
184 fv->value.time.nsecs = (int)(fraction * 1000000000);
185 return true;
189 * Parses an absolute time value from a string. The string can have
190 * a UTC time zone suffix. In that case it is interpreted in UTC. Otherwise
191 * it is interpreted in local time.
193 * OS-dependent; e.g., on 32 bit versions of Windows when compiled to use
194 * _mktime32 treats dates before January 1, 1970 as invalid.
195 * (https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/mktime-mktime32-mktime64)
199 * Timezone support:
201 %z an ISO 8601, RFC-2822, or RFC-3339 time zone specification. (A
202 NetBSD extension.) This is one of the following:
203 - The offset from Coordinated Universal Time (`UTC') speci-
204 fied as:
205 · [+-]hhmm
206 · [+-]hh:mm
207 · [+-]hh
208 - `UTC' specified as:
209 · UTC (`Coordinated Universal Time')
210 · GMT (`Greenwich Mean Time')
211 · UT (`Universal Time')
212 · Z (`Zulu Time')
213 - A three character US time zone specified as:
214 · EDT
215 · EST
216 · CDT
217 · CST
218 · MDT
219 · MST
220 · PDT
221 · PST
222 with the first letter standing for `Eastern' (``E''),
223 `Central' (``C''), `Mountain' (``M'') or `Pacific'
224 (``P''), and the second letter standing for `Daylight'
225 (``D'' or summer) time or `Standard' (``S'') time
226 - a single letter military or nautical time zone specified
228 · ``A'' through ``I''
229 · ``K'' through ``Y''
230 · ``J'' (non-nautical local time zone)
232 %Z time zone name or no characters when time zone information is
233 unavailable. (A NetBSD extension.)
237 * POSIX and C11 calendar time APIs are limited, poorly documented and have
238 * loads of bagage and surprising behavior and quirks (most stemming from
239 * the fact that the struct tm argument is sometimes both input and output).
240 * See the following reference for a reliable method of handling arbitrary timezones:
241 * C: Converting struct tm times with timezone to time_t
242 * http://kbyanc.blogspot.com/2007/06/c-converting-struct-tm-times-with.html
243 * Relevant excerpt:
244 * "However, if your libc implements both tm_gmtoff and timegm(3) you are
245 * in luck. You just need to use timegm(3) to get the time_t representing
246 * the time in GMT and then subtract the offset stored in tm_gmtoff.
247 * The tricky part is that calling timegm(3) will modify the struct tm,
248 * clearing the tm_gmtoff field to zero."
251 #define EXAMPLE "Example: \"Nov 12, 1999 08:55:44.123\" or \"2011-07-04 12:34:56\""
253 static bool
254 absolute_val_from_string(fvalue_t *fv, const char *s, size_t len _U_, char **err_msg_ptr)
256 struct tm tm;
257 const char *bufptr, *curptr = NULL;
258 const char *endptr;
259 bool has_seconds = true;
260 bool has_timezone = true;
261 char *err_msg = NULL;
262 struct ws_timezone zoneinfo = { 0, NULL };
264 /* Try Unix time first. */
265 if (val_from_unix_time(fv, s))
266 return true;
268 /* Try ISO 8601 format. */
269 endptr = iso8601_to_nstime(&fv->value.time, s, ISO8601_DATETIME);
270 /* Check whether it parsed all of the string */
271 if (endptr != NULL && *endptr == '\0')
272 return true;
274 /* No - try other legacy formats. */
275 memset(&tm, 0, sizeof(tm));
276 /* Let the computer figure out if it's DST. */
277 tm.tm_isdst = -1;
279 /* Parse the date. ws_strptime() always uses the "C" locale. */
280 bufptr = s;
281 curptr = ws_strptime(bufptr, "%b %d, %Y", &tm, &zoneinfo);
282 if (curptr == NULL)
283 curptr = ws_strptime(bufptr,"%Y-%m-%d", &tm, &zoneinfo);
284 if (curptr == NULL)
285 goto fail;
287 /* Parse the time, it is optional. */
288 bufptr = curptr;
289 curptr = ws_strptime(bufptr, " %H:%M:%S", &tm, &zoneinfo);
290 if (curptr == NULL) {
291 has_seconds = false;
292 /* Seconds can be omitted but minutes (and hours) are required
293 * for a valid time value. */
294 curptr = ws_strptime(bufptr," %H:%M", &tm, &zoneinfo);
296 if (curptr == NULL)
297 curptr = bufptr;
299 if (*curptr == '.') {
300 /* Nanoseconds */
301 if (!has_seconds) {
302 err_msg = ws_strdup("Subsecond precision requires a seconds field.");
303 goto fail; /* Requires seconds */
305 curptr++; /* skip the "." */
306 if (!g_ascii_isdigit((unsigned char)*curptr)) {
307 /* not a digit, so not valid */
308 err_msg = ws_strdup("Subseconds value is not a number.");
309 goto fail;
311 if (!get_nsecs(curptr, &fv->value.time.nsecs, &endptr)) {
312 err_msg = ws_strdup("Subseconds value is invalid.");
313 goto fail;
315 curptr = endptr;
317 else {
319 * No nanoseconds value - it's 0.
321 fv->value.time.nsecs = 0;
324 /* Timezone */
325 bufptr = curptr;
326 curptr = ws_strptime(bufptr, "%n%z", &tm, &zoneinfo);
327 if (curptr == NULL) {
328 /* No timezone, assume localtime. */
329 has_timezone = false;
330 curptr = bufptr;
333 /* Skip whitespace */
334 while (g_ascii_isspace(*curptr)) {
335 curptr++;
338 if (*curptr != '\0') {
339 err_msg = ws_strdup("Unexpected data after time value.");
340 goto fail;
343 if (has_timezone) {
344 /* Convert our calendar time (presumed in UTC, possibly with
345 * an extra timezone offset correction datum) to epoch time. */
346 fv->value.time.secs = mktime_utc(&tm);
348 else {
349 /* Convert our calendar time (in the local timezone) to epoch time. */
350 fv->value.time.secs = mktime(&tm);
352 if (fv->value.time.secs == (time_t)-1) {
354 * XXX - should we supply an error message that mentions
355 * that the time specified might be syntactically valid
356 * but might not actually have occurred, e.g. a time in
357 * the non-existent time range after the clocks are
358 * set forward during daylight savings time (or possibly
359 * that it's in the time range after the clocks are set
360 * backward, so that there are two different times that
361 * it could be)?
363 err_msg = ws_strdup_printf("\"%s\" cannot be converted to a valid calendar time.", s);
364 goto fail;
367 if (has_timezone) {
368 /* Normalize to UTC with the offset we have saved. */
369 fv->value.time.secs -= zoneinfo.tm_gmtoff;
372 return true;
374 fail:
375 if (err_msg_ptr != NULL) {
376 if (err_msg == NULL) {
377 *err_msg_ptr = ws_strdup_printf("\"%s\" is not a valid absolute time. " EXAMPLE, s);
379 else {
380 *err_msg_ptr = err_msg;
383 else {
384 g_free(err_msg);
387 return false;
390 static bool
391 absolute_val_from_literal(fvalue_t *fv, const char *s, bool allow_partial_value _U_, char **err_msg)
393 return absolute_val_from_string(fv, s, 0, err_msg);
396 static bool
397 absolute_val_from_uinteger64(fvalue_t *fv, const char *s, uint64_t value _U_, char **err_msg)
399 return absolute_val_from_literal(fv, s, FALSE, err_msg);
402 static bool
403 absolute_val_from_sinteger64(fvalue_t *fv, const char *s, int64_t value _U_, char **err_msg)
405 return absolute_val_from_literal(fv, s, FALSE, err_msg);
408 static bool
409 absolute_val_from_float(fvalue_t *fv, const char *s, double value _U_, char **err_msg)
411 return absolute_val_from_literal(fv, s, FALSE, err_msg);
414 static void
415 time_fvalue_new(fvalue_t *fv)
417 fv->value.time.secs = 0;
418 fv->value.time.nsecs = 0;
421 static void
422 time_fvalue_copy(fvalue_t *dst, const fvalue_t *src)
424 nstime_copy(&dst->value.time, &src->value.time);
427 static void
428 time_fvalue_set(fvalue_t *fv, const nstime_t *value)
430 fv->value.time = *value;
433 static const nstime_t *
434 value_get(fvalue_t *fv)
436 return &(fv->value.time);
439 static char *
440 absolute_val_to_repr(wmem_allocator_t *scope, const fvalue_t *fv, ftrepr_t rtype, int field_display)
442 int flags = ABS_TIME_TO_STR_SHOW_ZONE;
444 * There could be a preference not to show the time zone for local
445 * time. (I.e., to use ABS_TIME_TO_STR_SHOW_UTC_ONLY instead.)
449 * Backwards compatibility preference. Note below we'll always use
450 * ISO 8601 when generating a filter, although filters do support
451 * the older format.
453 if (prefs.display_abs_time_ascii != ABS_TIME_ASCII_ALWAYS) {
454 flags |= ABS_TIME_TO_STR_ISO8601;
457 if (field_display == BASE_NONE)
458 field_display = ABSOLUTE_TIME_LOCAL;
460 switch (rtype) {
461 case FTREPR_DISPLAY:
462 case FTREPR_RAW:
463 break;
465 case FTREPR_DFILTER:
467 * Display filters don't handle DOY or the special NULL
468 * NTP time representation. Normalize.
470 switch (field_display) {
471 case BASE_NONE:
472 case ABSOLUTE_TIME_LOCAL:
473 case ABSOLUTE_TIME_UNIX:
474 case ABSOLUTE_TIME_UTC:
475 break;
476 default:
477 field_display = ABSOLUTE_TIME_UTC;
479 flags |= ABS_TIME_TO_STR_ADD_DQUOTES;
480 flags |= ABS_TIME_TO_STR_ISO8601;
481 break;
483 case FTREPR_JSON:
485 * JSON is a data serialization format used primarily
486 * for machine input (despite the human-readable text)
487 * and so we use a standard representation regardless
488 * of human display.
490 field_display = ABSOLUTE_TIME_UTC;
491 /* TODO - add quotes here so that write_json_proto_node_value
492 * in print.c doesn't always add quotes itself, and can write
493 * booleans and numbers as JSON types.
495 flags |= ABS_TIME_TO_STR_ISO8601;
496 break;
498 default:
499 ws_assert_not_reached();
500 break;
503 return abs_time_to_str_ex(scope, &fv->value.time, field_display, flags);
506 static char *
507 relative_val_to_repr(wmem_allocator_t *scope, const fvalue_t *fv, ftrepr_t rtype _U_, int field_display _U_)
509 return rel_time_to_secs_str(scope, &fv->value.time);
512 static enum ft_result
513 time_val_to_double(const fvalue_t *fv, double *repr)
515 if (nstime_is_unset(&fv->value.time)) {
516 return FT_ERROR;
518 *repr = nstime_to_sec(&fv->value.time);
519 return FT_OK;
522 static unsigned
523 time_hash(const fvalue_t *fv)
525 return nstime_hash(&fv->value.time);
528 static bool
529 time_is_zero(const fvalue_t *fv)
531 return nstime_is_zero(&fv->value.time);
534 static bool
535 time_is_negative(const fvalue_t *fv)
537 return fv->value.time.secs < 0;
540 static enum ft_result
541 time_unary_minus(fvalue_t * dst, const fvalue_t *src, char **err_ptr _U_)
543 dst->value.time.secs = -src->value.time.secs;
544 dst->value.time.nsecs = -src->value.time.nsecs;
545 return FT_OK;
548 #define NS_PER_S 1000000000
550 static void
551 check_ns_wraparound(nstime_t *ns, jmp_buf env)
553 while(ns->nsecs >= NS_PER_S || (ns->nsecs > 0 && ns->secs < 0)) {
554 ws_safe_sub_jmp(&ns->nsecs, ns->nsecs, NS_PER_S, env);
555 ws_safe_add_jmp(&ns->secs, ns->secs, 1, env);
557 while (ns->nsecs <= -NS_PER_S || (ns->nsecs < 0 && ns->secs > 0)) {
558 ws_safe_add_jmp(&ns->nsecs, ns->nsecs, NS_PER_S, env);
559 ws_safe_sub_jmp(&ns->secs, ns->secs, 1, env);
563 static void
564 _nstime_add(nstime_t *res, nstime_t a, const nstime_t b, jmp_buf env)
566 ws_safe_add_jmp(&res->secs, a.secs, b.secs, env);
567 ws_safe_add_jmp(&res->nsecs, a.nsecs, b.nsecs, env);
568 check_ns_wraparound(res, env);
571 static enum ft_result
572 time_add(fvalue_t *dst, const fvalue_t *a, const fvalue_t *b, char **err_ptr)
574 jmp_buf env;
575 if (setjmp(env) != 0) {
576 *err_ptr = ws_strdup_printf("time_add: overflow");
577 return FT_ERROR;
579 _nstime_add(&dst->value.time, a->value.time, b->value.time, env);
580 return FT_OK;
583 static void
584 _nstime_sub(nstime_t *res, nstime_t a, const nstime_t b, jmp_buf env)
586 ws_safe_sub_jmp(&res->secs, a.secs, b.secs, env);
587 ws_safe_sub_jmp(&res->nsecs, a.nsecs, b.nsecs, env);
588 check_ns_wraparound(res, env);
591 static enum ft_result
592 time_subtract(fvalue_t *dst, const fvalue_t *a, const fvalue_t *b, char **err_ptr)
594 jmp_buf env;
595 if (setjmp(env) != 0) {
596 *err_ptr = ws_strdup_printf("time_subtract: overflow");
597 return FT_ERROR;
599 _nstime_sub(&dst->value.time, a->value.time, b->value.time, env);
600 return FT_OK;
603 static void
604 _nstime_mul_int(nstime_t *res, nstime_t a, int64_t val, jmp_buf env)
606 // XXX - To handle large (64-bit) val, use 128-bit integers
607 // to hold intermediate results
608 int64_t tmp_nsecs;
609 ws_safe_mul_jmp(&tmp_nsecs, a.nsecs, val, env);
610 res->nsecs = (int)(tmp_nsecs % NS_PER_S);
611 ws_safe_mul_jmp(&res->secs, a.secs, (time_t)val, env);
612 res->secs += (time_t)(tmp_nsecs / NS_PER_S);
613 check_ns_wraparound(res, env);
616 static void
617 _nstime_mul_float(nstime_t *res, nstime_t a, double val, jmp_buf env)
619 double tmp_secs, tmp_nsecs;
620 double tmp_time = nstime_to_sec(&a) * val;
621 tmp_nsecs = modf(tmp_time, &tmp_secs);
623 res->secs = (time_t)(tmp_secs);
624 res->nsecs = (int)(round(tmp_nsecs * NS_PER_S));
625 check_ns_wraparound(res, env);
628 static enum ft_result
629 time_multiply(fvalue_t *dst, const fvalue_t *a, const fvalue_t *b, char **err_ptr)
631 jmp_buf env;
632 if (setjmp(env) != 0) {
633 *err_ptr = ws_strdup_printf("time_multiply: overflow");
634 return FT_ERROR;
637 ftenum_t ft_b = fvalue_type_ftenum(b);
638 if (ft_b == FT_INT64) {
639 int64_t val = fvalue_get_sinteger64((fvalue_t *)b);
640 _nstime_mul_int(&dst->value.time, a->value.time, val, env);
642 else if (ft_b == FT_DOUBLE) {
643 double val = fvalue_get_floating((fvalue_t *)b);
644 _nstime_mul_float(&dst->value.time, a->value.time, val, env);
646 else {
647 ws_critical("Invalid RHS ftype: %s", ftype_pretty_name(ft_b));
648 return FT_BADARG;
650 return FT_OK;
653 static enum ft_result
654 time_divide(fvalue_t *dst, const fvalue_t *a, const fvalue_t *b, char **err_ptr)
656 jmp_buf env;
657 if (setjmp(env) != 0) {
658 *err_ptr = ws_strdup_printf("time_divide: overflow");
659 return FT_ERROR;
662 ftenum_t ft_b = fvalue_type_ftenum(b);
663 if (ft_b == FT_INT64) {
664 int64_t val = fvalue_get_sinteger64((fvalue_t *)b);
665 if (val == 0) {
666 *err_ptr = ws_strdup_printf("time_divide: division by zero");
667 return FT_ERROR;
669 // Integer division is annoying, this is acceptable
670 _nstime_mul_float(&dst->value.time, a->value.time, 1 / (double)val, env);
672 else if (ft_b == FT_DOUBLE) {
673 double val = fvalue_get_floating((fvalue_t *)b);
674 if (val == 0) {
675 *err_ptr = ws_strdup_printf("time_divide: division by zero");
676 return FT_ERROR;
678 _nstime_mul_float(&dst->value.time, a->value.time, 1 / val, env);
680 else {
681 ws_critical("Invalid RHS ftype: %s", ftype_pretty_name(ft_b));
682 return FT_BADARG;
684 return FT_OK;
687 void
688 ftype_register_time(void)
691 static const ftype_t abstime_type = {
692 FT_ABSOLUTE_TIME, /* ftype */
693 0, /* wire_size */
694 time_fvalue_new, /* new_value */
695 time_fvalue_copy, /* copy_value */
696 NULL, /* free_value */
697 absolute_val_from_literal, /* val_from_literal */
698 absolute_val_from_string, /* val_from_string */
699 NULL, /* val_from_charconst */
700 absolute_val_from_uinteger64, /* val_from_uinteger64 */
701 absolute_val_from_sinteger64, /* val_from_sinteger64 */
702 absolute_val_from_float, /* val_from_double */
703 absolute_val_to_repr, /* val_to_string_repr */
705 NULL, /* val_to_uinteger64 */
706 NULL, /* val_to_sinteger64 */
707 time_val_to_double, /* val_to_double */
709 { .set_value_time = time_fvalue_set }, /* union set_value */
710 { .get_value_time = value_get }, /* union get_value */
712 cmp_order,
713 NULL, /* cmp_contains */
714 NULL, /* cmp_matches */
716 time_hash, /* hash */
717 time_is_zero, /* is_zero */
718 time_is_negative, /* is_negative */
719 NULL,
720 NULL,
721 NULL, /* bitwise_and */
722 time_unary_minus, /* unary_minus */
723 time_add, /* add */
724 time_subtract, /* subtract */
725 time_multiply, /* multiply */
726 time_divide, /* divide */
727 NULL, /* modulo */
729 static const ftype_t reltime_type = {
730 FT_RELATIVE_TIME, /* ftype */
731 0, /* wire_size */
732 time_fvalue_new, /* new_value */
733 time_fvalue_copy, /* copy_value */
734 NULL, /* free_value */
735 NULL, /* val_from_literal */
736 NULL, /* val_from_string */
737 NULL, /* val_from_charconst */
738 relative_val_from_uinteger64, /* val_from_uinteger64 */
739 relative_val_from_sinteger64, /* val_from_sinteger64 */
740 relative_val_from_float, /* val_from_double */
741 relative_val_to_repr, /* val_to_string_repr */
743 NULL, /* val_to_uinteger64 */
744 NULL, /* val_to_sinteger64 */
745 time_val_to_double, /* val_to_double */
747 { .set_value_time = time_fvalue_set }, /* union set_value */
748 { .get_value_time = value_get }, /* union get_value */
750 cmp_order,
751 NULL, /* cmp_contains */
752 NULL, /* cmp_matches */
754 time_hash, /* hash */
755 time_is_zero, /* is_zero */
756 time_is_negative, /* is_negative */
757 NULL,
758 NULL,
759 NULL, /* bitwise_and */
760 time_unary_minus, /* unary_minus */
761 time_add, /* add */
762 time_subtract, /* subtract */
763 time_multiply, /* multiply */
764 time_divide, /* divide */
765 NULL, /* modulo */
768 ftype_register(FT_ABSOLUTE_TIME, &abstime_type);
769 ftype_register(FT_RELATIVE_TIME, &reltime_type);
772 void
773 ftype_register_pseudofields_time(int proto)
775 static int hf_ft_rel_time;
776 static int hf_ft_abs_time;
778 static hf_register_info hf_ftypes[] = {
779 { &hf_ft_abs_time,
780 { "FT_ABSOLUTE_TIME", "_ws.ftypes.abs_time",
781 FT_ABSOLUTE_TIME, ABSOLUTE_TIME_UTC, NULL, 0x00,
782 NULL, HFILL }
784 { &hf_ft_rel_time,
785 { "FT_RELATIVE_TIME", "_ws.ftypes.rel_time",
786 FT_RELATIVE_TIME, BASE_NONE, NULL, 0x00,
787 NULL, HFILL }
791 proto_register_field_array(proto, hf_ftypes, array_length(hf_ftypes));
795 * Editor modelines - https://www.wireshark.org/tools/modelines.html
797 * Local variables:
798 * c-basic-offset: 8
799 * tab-width: 8
800 * indent-tabs-mode: t
801 * End:
803 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
804 * :indentSize=8:tabSize=8:noTabs=false: