refactor(core): Reuse the generic DateTime decoding for XML

This commit is contained in:
Julius Pfrommer 2024-09-14 21:07:06 +02:00 committed by Julius Pfrommer
parent 3ef14ba7f7
commit 28306413e0

View File

@ -1101,132 +1101,8 @@ DECODE_XML(String) {
DECODE_XML(DateTime) {
CHECK_DATA_BOUNDS;
GET_DATA_VALUE;
/* The last character has to be 'Z'. We can omit some length checks later on
* because we know the atoi functions stop before the 'Z'. */
if(length == 0 || data[length - 1] != 'Z')
return UA_STATUSCODE_BADDECODINGERROR;
struct mytm dts;
memset(&dts, 0, sizeof(dts));
size_t pos = 0;
size_t len;
/* Parse the year. The ISO standard asks for four digits. But we accept up
* to five with an optional plus or minus in front due to the range of the
* DateTime 64bit integer. But in that case we require the year and the
* month to be separated by a '-'. Otherwise we cannot know where the month
* starts. */
if(data[0] == '-' || data[0] == '+')
pos++;
UA_Int64 year = 0;
len = parseInt64(&data[pos], 5, &year);
pos += len;
if(len != 4 && data[pos] != '-')
return UA_STATUSCODE_BADDECODINGERROR;
if(data[0] == '-')
year = -year;
dts.tm_year = (UA_Int16)year - 1900;
if(data[pos] == '-')
pos++;
/* Parse the month */
UA_UInt64 month = 0;
len = parseUInt64(&data[pos], 2, &month);
pos += len;
UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR);
dts.tm_mon = (UA_UInt16)month - 1;
if(data[pos] == '-')
pos++;
/* Parse the day and check the T between date and time */
UA_UInt64 day = 0;
len = parseUInt64(&data[pos], 2, &day);
pos += len;
UA_CHECK(len == 2 || data[pos] != 'T',
return UA_STATUSCODE_BADDECODINGERROR);
dts.tm_mday = (UA_UInt16)day;
pos++;
/* Parse the hour */
UA_UInt64 hour = 0;
len = parseUInt64(&data[pos], 2, &hour);
pos += len;
UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR);
dts.tm_hour = (UA_UInt16)hour;
if(data[pos] == ':')
pos++;
/* Parse the minute */
UA_UInt64 min = 0;
len = parseUInt64(&data[pos], 2, &min);
pos += len;
UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR);
dts.tm_min = (UA_UInt16)min;
if(data[pos] == ':')
pos++;
/* Parse the second */
UA_UInt64 sec = 0;
len = parseUInt64(&data[pos], 2, &sec);
pos += len;
UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR);
dts.tm_sec = (UA_UInt16)sec;
/* Compute the seconds since the Unix epoch */
long long sinceunix = __tm_to_secs(&dts);
/* Are we within the range that can be represented? */
long long sinceunix_min =
(long long)(UA_INT64_MIN / UA_DATETIME_SEC) -
(long long)(UA_DATETIME_UNIX_EPOCH / UA_DATETIME_SEC) -
(long long)1; /* manual correction due to rounding */
long long sinceunix_max = (long long)
((UA_INT64_MAX - UA_DATETIME_UNIX_EPOCH) / UA_DATETIME_SEC);
if(sinceunix < sinceunix_min || sinceunix > sinceunix_max)
return UA_STATUSCODE_BADDECODINGERROR;
/* Convert to DateTime. Add or subtract one extra second here to prevent
* underflow/overflow. This is reverted once the fractional part has been
* added. */
sinceunix -= (sinceunix > 0) ? 1 : -1;
UA_DateTime dt = (UA_DateTime)
(sinceunix + (UA_DATETIME_UNIX_EPOCH / UA_DATETIME_SEC)) * UA_DATETIME_SEC;
/* Parse the fraction of the second if defined */
if(data[pos] == ',' || data[pos] == '.') {
pos++;
double frac = 0.0;
double denom = 0.1;
while(pos < length && data[pos] >= '0' && data[pos] <= '9') {
frac += denom * (data[pos] - '0');
denom *= 0.1;
pos++;
}
frac += 0.00000005; /* Correct rounding when converting to integer */
dt += (UA_DateTime)(frac * UA_DATETIME_SEC);
}
/* Remove the underflow/overflow protection (see above) */
if(sinceunix > 0) {
if(dt > UA_INT64_MAX - UA_DATETIME_SEC)
return UA_STATUSCODE_BADDECODINGERROR;
dt += UA_DATETIME_SEC;
} else {
if(dt < UA_INT64_MIN + UA_DATETIME_SEC)
return UA_STATUSCODE_BADDECODINGERROR;
dt -= UA_DATETIME_SEC;
}
/* We must be at the end of the string (ending with 'Z' as checked above) */
if(pos != length - 1)
return UA_STATUSCODE_BADDECODINGERROR;
*dst = dt;
ctx->index++;
return UA_STATUSCODE_GOOD;
UA_String str = {length, (UA_Byte*)(uintptr_t)data};
return decodeDateTime(str, dst);
}
static status