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
11 #include "ftypes-int.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>
25 cmp_order(const fvalue_t
*a
, const fvalue_t
*b
, int *cmp
)
27 *cmp
= nstime_cmp(&(a
->value
.time
), &(b
->value
.time
));
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.
39 get_nsecs(const char *startp
, int *nsecs
, const char **endptr
)
49 * How many digits are in the string?
51 for (p
= startp
; g_ascii_isdigit(*p
); p
++)
55 * If there are N characters in the string, the last of the
56 * characters would be the digit corresponding to 10^(9-N)
62 * Start at the last character, and work backwards.
68 if (!g_ascii_isdigit(*p
)) {
70 * Not a digit - error.
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
86 for (i
= 0; i
< scale
; i
++)
94 *endptr
= startp
+ ndigits
;
99 val_from_unix_time(fvalue_t
*fv
, const char *s
)
103 bool negative
= false;
107 if (*curptr
== '-') {
113 * If it doesn't begin with ".", it should contain a seconds
116 if (*curptr
!= '.') {
118 * Get the seconds value.
120 fv
->value
.time
.secs
= strtoul(curptr
, &endptr
, 10);
121 if (endptr
== curptr
|| (*endptr
!= '\0' && *endptr
!= '.'))
125 curptr
++; /* skip the decimal point */
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
138 if (*curptr
!= '\0') {
140 * Get the nanoseconds value.
142 if (!get_nsecs(curptr
, &fv
->value
.time
.nsecs
, NULL
))
146 * No nanoseconds value - it's 0.
148 fv
->value
.time
.nsecs
= 0;
152 fv
->value
.time
.secs
= -fv
->value
.time
.secs
;
153 fv
->value
.time
.nsecs
= -fv
->value
.time
.nsecs
;
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;
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;
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
))
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);
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)
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-
208 - `UTC' specified as:
209 · UTC (`Coordinated Universal Time')
210 · GMT (`Greenwich Mean Time')
211 · UT (`Universal Time')
213 - A three character US time zone specified as:
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
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\""
254 absolute_val_from_string(fvalue_t
*fv
, const char *s
, size_t len _U_
, char **err_msg_ptr
)
257 const char *bufptr
, *curptr
= NULL
;
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
))
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')
274 /* No - try other legacy formats. */
275 memset(&tm
, 0, sizeof(tm
));
276 /* Let the computer figure out if it's DST. */
279 /* Parse the date. ws_strptime() always uses the "C" locale. */
281 curptr
= ws_strptime(bufptr
, "%b %d, %Y", &tm
, &zoneinfo
);
283 curptr
= ws_strptime(bufptr
,"%Y-%m-%d", &tm
, &zoneinfo
);
287 /* Parse the time, it is optional. */
289 curptr
= ws_strptime(bufptr
, " %H:%M:%S", &tm
, &zoneinfo
);
290 if (curptr
== NULL
) {
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
);
299 if (*curptr
== '.') {
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.");
311 if (!get_nsecs(curptr
, &fv
->value
.time
.nsecs
, &endptr
)) {
312 err_msg
= ws_strdup("Subseconds value is invalid.");
319 * No nanoseconds value - it's 0.
321 fv
->value
.time
.nsecs
= 0;
326 curptr
= ws_strptime(bufptr
, "%n%z", &tm
, &zoneinfo
);
327 if (curptr
== NULL
) {
328 /* No timezone, assume localtime. */
329 has_timezone
= false;
333 /* Skip whitespace */
334 while (g_ascii_isspace(*curptr
)) {
338 if (*curptr
!= '\0') {
339 err_msg
= ws_strdup("Unexpected data after time value.");
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
);
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
363 err_msg
= ws_strdup_printf("\"%s\" cannot be converted to a valid calendar time.", s
);
368 /* Normalize to UTC with the offset we have saved. */
369 fv
->value
.time
.secs
-= zoneinfo
.tm_gmtoff
;
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
);
380 *err_msg_ptr
= err_msg
;
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
);
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
);
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
);
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
);
415 time_fvalue_new(fvalue_t
*fv
)
417 fv
->value
.time
.secs
= 0;
418 fv
->value
.time
.nsecs
= 0;
422 time_fvalue_copy(fvalue_t
*dst
, const fvalue_t
*src
)
424 nstime_copy(&dst
->value
.time
, &src
->value
.time
);
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
);
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
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
;
467 * Display filters don't handle DOY or the special NULL
468 * NTP time representation. Normalize.
470 switch (field_display
) {
472 case ABSOLUTE_TIME_LOCAL
:
473 case ABSOLUTE_TIME_UNIX
:
474 case ABSOLUTE_TIME_UTC
:
477 field_display
= ABSOLUTE_TIME_UTC
;
479 flags
|= ABS_TIME_TO_STR_ADD_DQUOTES
;
480 flags
|= ABS_TIME_TO_STR_ISO8601
;
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
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
;
499 ws_assert_not_reached();
503 return abs_time_to_str_ex(scope
, &fv
->value
.time
, field_display
, flags
);
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
)) {
518 *repr
= nstime_to_sec(&fv
->value
.time
);
523 time_hash(const fvalue_t
*fv
)
525 return nstime_hash(&fv
->value
.time
);
529 time_is_zero(const fvalue_t
*fv
)
531 return nstime_is_zero(&fv
->value
.time
);
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
;
548 #define NS_PER_S 1000000000
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
);
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
)
575 if (setjmp(env
) != 0) {
576 *err_ptr
= ws_strdup_printf("time_add: overflow");
579 _nstime_add(&dst
->value
.time
, a
->value
.time
, b
->value
.time
, env
);
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
)
595 if (setjmp(env
) != 0) {
596 *err_ptr
= ws_strdup_printf("time_subtract: overflow");
599 _nstime_sub(&dst
->value
.time
, a
->value
.time
, b
->value
.time
, env
);
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
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
);
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
)
632 if (setjmp(env
) != 0) {
633 *err_ptr
= ws_strdup_printf("time_multiply: overflow");
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
);
647 ws_critical("Invalid RHS ftype: %s", ftype_pretty_name(ft_b
));
653 static enum ft_result
654 time_divide(fvalue_t
*dst
, const fvalue_t
*a
, const fvalue_t
*b
, char **err_ptr
)
657 if (setjmp(env
) != 0) {
658 *err_ptr
= ws_strdup_printf("time_divide: overflow");
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
);
666 *err_ptr
= ws_strdup_printf("time_divide: division by zero");
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
);
675 *err_ptr
= ws_strdup_printf("time_divide: division by zero");
678 _nstime_mul_float(&dst
->value
.time
, a
->value
.time
, 1 / val
, env
);
681 ws_critical("Invalid RHS ftype: %s", ftype_pretty_name(ft_b
));
688 ftype_register_time(void)
691 static const ftype_t abstime_type
= {
692 FT_ABSOLUTE_TIME
, /* ftype */
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 */
713 NULL
, /* cmp_contains */
714 NULL
, /* cmp_matches */
716 time_hash
, /* hash */
717 time_is_zero
, /* is_zero */
718 time_is_negative
, /* is_negative */
721 NULL
, /* bitwise_and */
722 time_unary_minus
, /* unary_minus */
724 time_subtract
, /* subtract */
725 time_multiply
, /* multiply */
726 time_divide
, /* divide */
729 static const ftype_t reltime_type
= {
730 FT_RELATIVE_TIME
, /* ftype */
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 */
751 NULL
, /* cmp_contains */
752 NULL
, /* cmp_matches */
754 time_hash
, /* hash */
755 time_is_zero
, /* is_zero */
756 time_is_negative
, /* is_negative */
759 NULL
, /* bitwise_and */
760 time_unary_minus
, /* unary_minus */
762 time_subtract
, /* subtract */
763 time_multiply
, /* multiply */
764 time_divide
, /* divide */
768 ftype_register(FT_ABSOLUTE_TIME
, &abstime_type
);
769 ftype_register(FT_RELATIVE_TIME
, &reltime_type
);
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
[] = {
780 { "FT_ABSOLUTE_TIME", "_ws.ftypes.abs_time",
781 FT_ABSOLUTE_TIME
, ABSOLUTE_TIME_UTC
, NULL
, 0x00,
785 { "FT_RELATIVE_TIME", "_ws.ftypes.rel_time",
786 FT_RELATIVE_TIME
, BASE_NONE
, NULL
, 0x00,
791 proto_register_field_array(proto
, hf_ftypes
, array_length(hf_ftypes
));
795 * Editor modelines - https://www.wireshark.org/tools/modelines.html
800 * indent-tabs-mode: t
803 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
804 * :indentSize=8:tabSize=8:noTabs=false: