You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

552 lines
21 KiB

1 year ago
/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
#include "zbxcommon.h"
#include "zbxmockdata.h"
/* output formats */
#define ZBX_MOCK_FORMAT_DATE "%04d-%02d-%02d"
#define ZBX_MOCK_FORMAT_TIME "%02d:%02d:%02d"
#define ZBX_MOCK_FORMAT_DATETIME ZBX_MOCK_FORMAT_DATE " " ZBX_MOCK_FORMAT_TIME
#define ZBX_MOCK_FORMAT_NS ".%09d"
#define ZBX_MOCK_FORMAT_TZ "%c%02d:%02d"
#define ZBX_MOCK_TZ_MAX 7
#define ZBX_MOCK_TIME_DATE 0x0001
#define ZBX_MOCK_TIME_TIME 0x0002
#define ZBX_MOCK_TIME_NS 0x0004
#define ZBX_MOCK_TIME_TZ 0x0008
/******************************************************************************
* *
* Purpose: finds the next character after numeric time component *
* *
* Parameters: text - [IN] the text *
* *
* Return value: text after the time component *
* *
* Comments: If the first character is not a digit the source text is *
* returned. *
* *
******************************************************************************/
static const char *ts_get_component_end(const char *text)
{
while (0 != isdigit(*text))
text++;
return text;
}
/******************************************************************************
* *
* Purpose: parses year, month and day from date component having *
* YYYY-MM-DD format *
* *
* Parameters: text - [IN] the text *
* year - [OUT] the year *
* month - [OUT] the month *
* day - [OUT] the day *
* pnext - [OUT] text after date component *
* *
* Return value: ZBX_MOCK_SUCCESS - the date was parsed successfully *
* ZBX_MOCK_NOT_A_TIMESTAMP - invalid date format *
* *
* Comments: The year, month, day limits are not validated. *
* *
******************************************************************************/
static zbx_mock_error_t ts_get_date(const char *text, int *year, int *month, int *day, const char **pnext)
{
const char *year_end, *month_end, *day_end;
int value_year, value_month, value_day;
year_end = ts_get_component_end(text);
if (year_end - text != 4 || '-' != *year_end)
return ZBX_MOCK_NOT_A_TIMESTAMP;
month_end = ts_get_component_end(year_end + 1);
if (2 > month_end - year_end || 3 < month_end - year_end || '-' != *month_end)
return ZBX_MOCK_NOT_A_TIMESTAMP;
day_end = ts_get_component_end(month_end + 1);
if (2 > day_end - month_end || 3 < day_end - month_end)
return ZBX_MOCK_NOT_A_TIMESTAMP;
value_year = atoi(text);
if (1970 > value_year || 2038 < value_year)
return ZBX_MOCK_NOT_A_TIMESTAMP;
value_month = atoi(year_end + 1);
if (12 < value_month)
return ZBX_MOCK_NOT_A_TIMESTAMP;
value_day = atoi(month_end + 1);
if (value_day > zbx_day_in_month(value_year, value_month))
return ZBX_MOCK_NOT_A_TIMESTAMP;
*pnext = day_end;
*year = value_year;
*month = value_month;
*day = value_day;
return ZBX_MOCK_SUCCESS;
}
/******************************************************************************
* *
* Purpose: parses hours, minutes and seconds from time component having *
* HH:MM:SS format *
* *
* Parameters: text - [IN] the text *
* hours - [OUT] the hours *
* minutes - [OUT] the minutes *
* seconds - [OUT] the seconds *
* pnext - [OUT] text after time component *
* *
* Return value: ZBX_MOCK_SUCCESS - the time was parsed successfully *
* ZBX_MOCK_NOT_A_TIMESTAMP - invalid time format *
* *
* Comments: The hours, minutes and seconds limits are not validated. *
* *
******************************************************************************/
static zbx_mock_error_t ts_get_time(const char *text, int *hours, int *minutes, int *seconds, const char **pnext)
{
const char *hours_end, *minutes_end, *seconds_end;
int value_hours, value_minutes, value_seconds;
hours_end = ts_get_component_end(text);
if (hours_end == text || ':' != *hours_end)
return ZBX_MOCK_NOT_A_TIMESTAMP;
minutes_end = ts_get_component_end(hours_end + 1);
if (minutes_end - hours_end != 3 || ':' != *minutes_end)
return ZBX_MOCK_NOT_A_TIMESTAMP;
seconds_end = ts_get_component_end(minutes_end + 1);
if (seconds_end - minutes_end != 3)
return ZBX_MOCK_NOT_A_TIMESTAMP;
value_hours = atoi(text);
if (24 <= value_hours)
return ZBX_MOCK_NOT_A_TIMESTAMP;
value_minutes = atoi(hours_end + 1);
if (60 <= value_minutes)
return ZBX_MOCK_NOT_A_TIMESTAMP;
value_seconds = atoi(minutes_end + 1);
if (60 <= value_seconds)
return ZBX_MOCK_NOT_A_TIMESTAMP;
*pnext = seconds_end;
*hours = value_hours;
*minutes = value_minutes;
*seconds = value_seconds;
return ZBX_MOCK_SUCCESS;
}
/******************************************************************************
* *
* Purpose: parses nanoseconds from time component having .NNNNNNNNN format *
* *
* Parameters: text - [IN] the text *
* ns - [OUT] nanoseconds *
* pnext - [OUT] text after time component *
* *
* Return value: ZBX_MOCK_SUCCESS - the nanoseconds was parsed successfully *
* ZBX_MOCK_NOT_A_TIMESTAMP - invalid time format *
* *
* Comments: The nanoseconds limits are not validated. *
* *
******************************************************************************/
static zbx_mock_error_t ts_get_ns(const char *text, int *ns, const char **pnext)
{
const char *ns_end;
int pad;
ns_end = ts_get_component_end(text + 1);
if (ns_end == text + 1 || 10 < ns_end - text)
return ZBX_MOCK_NOT_A_TIMESTAMP;
*pnext = ns_end;
*ns = atoi(text + 1);
pad = 9 - (ns_end - text);
while (0 <= pad--)
*ns *= 10;
return ZBX_MOCK_SUCCESS;
}
/******************************************************************************
* *
* Purpose: parses timezone offset seconds from timezone component having *
* (+|-)HH[:MM] format *
* *
* Parameters: text - [IN] the text *
* sec - [OUT] timezone offset seconds *
* pnext - [OUT] text after timezone component *
* *
* Return value: ZBX_MOCK_SUCCESS - the timezone was parsed successfully *
* ZBX_MOCK_NOT_A_TIMESTAMP - invalid time format *
* *
* Comments: The timezone offset limits are not validated. *
* *
******************************************************************************/
static zbx_mock_error_t ts_get_tz(const char *text, int *sec, const char **pnext)
{
const char *tz_end;
int hours = 0, minutes = 0;
tz_end = ts_get_component_end(text + 1);
if (tz_end == text + 1)
return ZBX_MOCK_NOT_A_TIMESTAMP;
hours = atoi(text + 1);
if (':' == *tz_end)
{
minutes = atoi(tz_end + 1);
tz_end = ts_get_component_end(tz_end + 1);
if (tz_end == text + 1)
return ZBX_MOCK_NOT_A_TIMESTAMP;
}
*pnext = tz_end;
*sec = hours * SEC_PER_HOUR + minutes * SEC_PER_MIN;
if ('-' == *text)
*sec = -*sec;
return ZBX_MOCK_SUCCESS;
}
/******************************************************************************
* *
* Purpose: converts timestamp to a broken-down time representation and *
* timezone offset (in seconds). *
* *
* Parameters: timestamp - [IN] the number of seconds since Epoch *
* local - [OUT] broken-down time representation *
* tz_offset - [OUT] timezone offset in seconds *
* *
* Return value: ZBX_MOCK_SUCCESS - the time was converted successfully *
* ZBX_MOCK_INTERNAL_ERROR - invalid timestamp was specified *
* *
******************************************************************************/
static zbx_mock_error_t zbx_time_to_localtime(time_t timestamp, struct tm *local, int *tz_offset)
{
struct tm tm_utc, tm_local;
if (NULL == gmtime_r(&timestamp, &tm_utc))
return ZBX_MOCK_INTERNAL_ERROR;
if (NULL == localtime_r(&timestamp, &tm_local))
return ZBX_MOCK_INTERNAL_ERROR;
*tz_offset = (tm_local.tm_yday - tm_utc.tm_yday) * SEC_PER_DAY +
(tm_local.tm_hour - tm_utc.tm_hour) * SEC_PER_HOUR +
(tm_local.tm_min - tm_utc.tm_min) * SEC_PER_MIN;
while (tm_local.tm_year > tm_utc.tm_year)
*tz_offset += (SUCCEED == zbx_is_leap_year(tm_utc.tm_year++) ? SEC_PER_YEAR + SEC_PER_DAY : SEC_PER_YEAR);
while (tm_local.tm_year < tm_utc.tm_year)
*tz_offset -= (SUCCEED == zbx_is_leap_year(--tm_utc.tm_year) ? SEC_PER_YEAR + SEC_PER_DAY : SEC_PER_YEAR);
*local = tm_local;
return ZBX_MOCK_SUCCESS;
}
/******************************************************************************
* *
* Purpose: formats timezone to +hh:mm format *
* *
* Parameters: buffer - [OUT] the output buffer *
* size - [IN] the output buffer size *
* tz_sec - [IN] the timezone offset in seconds *
* *
******************************************************************************/
static void zbx_tz_format(char *buffer, size_t size, int tz_sec)
{
int tz_hour, tz_min;
char tz_sign;
if (0 > tz_sec)
{
tz_sec = -tz_sec;
tz_sign = '-';
}
else
tz_sign = '+';
tz_hour = tz_sec / 60;
tz_min = tz_hour % 60;
tz_hour /= 60;
zbx_snprintf(buffer, size, ZBX_MOCK_FORMAT_TZ, tz_sign, tz_hour, tz_min);
}
typedef enum
{
ZBX_TOKEN_START,
ZBX_TOKEN_DELIM,
ZBX_TOKEN_COMPONENT
}
zbx_mock_time_parser_state_t;
/******************************************************************************
* *
* Purpose: converts YAML space separated timestamp having *
* YYYY-MM-DD hh:mm:ss.nnnnnnnnn TZ format to zabbix timespec *
* *
* Parameters: strtime - [IN] the YAML space separated timestamp *
* ts - [OUT] zabbix timespec *
* *
* Return value: ZBX_MOCK_SUCCESS - the timestamp was converted successfully *
* ZBX_MOCK_NOT_A_TIMESTAMP - invalid timestamp format *
* *
* Comments: Timestamp consists of 4 components - date, time, nanoseconds and *
* timezone. Any of those components can be omitted except timezone *
* component requires date component to be present. Absent *
* components are treated as zero values. So for example 10:00:00 *
* is equal to 1970-01-01 10:00:00.000000000 +00:00 and parsing it *
* would result in timespec with 36000 seconds and 0 nanoseconds. *
* *
******************************************************************************/
zbx_mock_error_t zbx_strtime_to_timespec(const char *strtime, zbx_timespec_t *ts)
{
int sec, ns, tz, components = 0;
const char *ptr, *pnext;
struct tm tm;
zbx_mock_error_t err;
zbx_mock_time_parser_state_t state = ZBX_TOKEN_START;
for (ptr = strtime; '\0' != *ptr;)
{
if ('.' == *ptr)
{
if (ZBX_TOKEN_DELIM == state)
return ZBX_MOCK_NOT_A_TIMESTAMP;
if (ZBX_MOCK_TIME_NS < components)
return ZBX_MOCK_NOT_A_TIMESTAMP;
if (ZBX_MOCK_SUCCESS != (err = ts_get_ns(ptr, &ns, &ptr)))
return err;
components |= ZBX_MOCK_TIME_NS;
state = ZBX_TOKEN_COMPONENT;
continue;
}
if ('Z' == *ptr)
{
if (ZBX_TOKEN_DELIM == state)
return ZBX_MOCK_NOT_A_TIMESTAMP;
if (0 != (components & ZBX_MOCK_TIME_TZ) || 0 == (components & ZBX_MOCK_TIME_DATE))
return ZBX_MOCK_NOT_A_TIMESTAMP;
tz = 0;
ptr++;
components |= ZBX_MOCK_TIME_TZ;
state = ZBX_TOKEN_COMPONENT;
continue;
}
if (' ' == *ptr || '\t' == *ptr)
{
state = ZBX_TOKEN_DELIM;
ptr++;
continue;
}
if (ZBX_TOKEN_COMPONENT == state)
return ZBX_MOCK_NOT_A_TIMESTAMP;
state = ZBX_TOKEN_COMPONENT;
if ('-' == *ptr || '+' == *ptr)
{
if (0 != (components & ZBX_MOCK_TIME_TZ) || 0 == (components & ZBX_MOCK_TIME_DATE))
return ZBX_MOCK_NOT_A_TIMESTAMP;
if (ZBX_MOCK_SUCCESS != (err = ts_get_tz(ptr, &tz, &ptr)))
return err;
components |= ZBX_MOCK_TIME_TZ;
continue;
}
pnext = ts_get_component_end(ptr);
if (ptr == pnext)
return ZBX_MOCK_NOT_A_TIMESTAMP;
if ('-' == *pnext)
{
if (ZBX_MOCK_TIME_DATE < components)
return ZBX_MOCK_NOT_A_TIMESTAMP;
if (ZBX_MOCK_SUCCESS != (err = ts_get_date(ptr, &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &ptr)))
return err;
components |= ZBX_MOCK_TIME_DATE;
tm.tm_year -= 1900;
tm.tm_mon--;
continue;
}
if (':' == *pnext)
{
if (ZBX_MOCK_TIME_TIME < components)
return ZBX_MOCK_NOT_A_TIMESTAMP;
if (ZBX_MOCK_SUCCESS != (err = ts_get_time(ptr, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &ptr)))
return err;
components |= ZBX_MOCK_TIME_TIME;
continue;
}
return ZBX_MOCK_NOT_A_TIMESTAMP;
}
if (0 != (components & (ZBX_MOCK_TIME_DATE | ZBX_MOCK_TIME_TIME)))
{
if (0 == (components & ZBX_MOCK_TIME_DATE))
{
tm.tm_year = 70;
tm.tm_mon = 0;
tm.tm_mday = 1;
}
if (0 == (components & ZBX_MOCK_TIME_TIME))
{
tm.tm_hour = 0;
tm.tm_min = 0;
tm.tm_sec = 0;
}
if (0 > (sec = timegm(&tm)))
return ZBX_MOCK_NOT_A_TIMESTAMP;
if (0 != (components & ZBX_MOCK_TIME_TZ))
sec -= tz;
}
else
sec = 0;
if (0 == (components & ZBX_MOCK_TIME_NS))
ns = 0;
ts->sec = sec;
ts->ns = ns;
return ZBX_MOCK_SUCCESS;
}
/******************************************************************************
* *
* Purpose: converts time to YAML space separated timestamp in *
* YYYY-MM-DD hh:mm:ss TZ format *
* *
* Parameters: timestamp - [IN] the time (seconds since Epoch) *
* buffer - [OUT] the output buffer *
* size - [OUT] the size of output buffer *
* *
* Return value: ZBX_MOCK_SUCCESS - the time was converted successfully *
* ZBX_MOCK_NOT_ENOUGH_MEMORY - the output buffer size is too *
* small *
* ZBX_MOCK_INTERNAL_ERROR - invalid timestamp specified *
* *
* Comments: The time is converted by using current timezone settings. *
* *
******************************************************************************/
zbx_mock_error_t zbx_time_to_strtime(time_t timestamp, char *buffer, size_t size)
{
struct tm tm;
int tz_sec;
char tz_buf[ZBX_MOCK_TZ_MAX];
zbx_mock_error_t err;
/* max timestamp length minus nanosecond component */
if (size < ZBX_MOCK_TIMESTAMP_MAX_LEN - 10)
return ZBX_MOCK_NOT_ENOUGH_MEMORY;
if (ZBX_MOCK_SUCCESS != (err = zbx_time_to_localtime(timestamp, &tm, &tz_sec)))
return err;
zbx_tz_format(tz_buf, sizeof(tz_buf), tz_sec);
zbx_snprintf(buffer, size, ZBX_MOCK_FORMAT_DATETIME " %s",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, tz_buf);
return ZBX_MOCK_SUCCESS;
}
/******************************************************************************
* *
* Purpose: converts timespec (seconds + nanoseconds) to YAML space separated *
* timestamp in YYYY-MM-DD hh:mm:ss.nnnnnnnnn TZ format *
* *
* Parameters: ts - [IN] the zabbix timespec (seconds + nanoseconds) *
* buffer - [OUT] the output buffer *
* size - [OUT] the size of output buffer *
* *
* Return value: ZBX_MOCK_SUCCESS - the time was converted successfully *
* ZBX_MOCK_NOT_ENOUGH_MEMORY - the output buffer size is too *
* small *
* ZBX_MOCK_INTERNAL_ERROR - invalid timestamp specified *
* *
* Comments: The time is converted by using current timezone settings. *
* *
******************************************************************************/
zbx_mock_error_t zbx_timespec_to_strtime(const zbx_timespec_t *ts, char *buffer, size_t size)
{
struct tm tm;
int tz_sec;
char tz_buf[ZBX_MOCK_TZ_MAX + 1];
zbx_mock_error_t err;
if (size < ZBX_MOCK_TIMESTAMP_MAX_LEN)
return ZBX_MOCK_NOT_ENOUGH_MEMORY;
if (ZBX_MOCK_SUCCESS != (err = zbx_time_to_localtime(ts->sec, &tm, &tz_sec)))
return err;
zbx_tz_format(tz_buf, sizeof(tz_buf), tz_sec);
zbx_snprintf(buffer, size, ZBX_MOCK_FORMAT_DATETIME ZBX_MOCK_FORMAT_NS " %s",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, ts->ns, tz_buf);
return ZBX_MOCK_SUCCESS;
}