mirror of
https://github.com/open62541/open62541.git
synced 2025-06-03 04:00:21 +00:00
refactor(core): Make the datetime decoding shareable between JSON and XML decoding
This commit is contained in:
parent
dbdac8d2af
commit
3ef14ba7f7
@ -28,7 +28,6 @@
|
||||
#include "../deps/dtoa.h"
|
||||
#include "../deps/parse_num.h"
|
||||
#include "../deps/base64.h"
|
||||
#include "../deps/libc_time.h"
|
||||
|
||||
#ifndef UA_ENABLE_PARSING
|
||||
#error UA_ENABLE_PARSING required for JSON encoding
|
||||
@ -1938,132 +1937,11 @@ DECODE_JSON(DateTime) {
|
||||
CHECK_STRING;
|
||||
GET_TOKEN;
|
||||
|
||||
/* 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(tokenSize == 0 || tokenData[tokenSize-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(tokenData[0] == '-' || tokenData[0] == '+')
|
||||
pos++;
|
||||
UA_Int64 year = 0;
|
||||
len = parseInt64(&tokenData[pos], 5, &year);
|
||||
pos += len;
|
||||
if(len != 4 && tokenData[pos] != '-')
|
||||
return UA_STATUSCODE_BADDECODINGERROR;
|
||||
if(tokenData[0] == '-')
|
||||
year = -year;
|
||||
dts.tm_year = (UA_Int16)year - 1900;
|
||||
if(tokenData[pos] == '-')
|
||||
pos++;
|
||||
|
||||
/* Parse the month */
|
||||
UA_UInt64 month = 0;
|
||||
len = parseUInt64(&tokenData[pos], 2, &month);
|
||||
pos += len;
|
||||
UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR);
|
||||
dts.tm_mon = (UA_UInt16)month - 1;
|
||||
if(tokenData[pos] == '-')
|
||||
pos++;
|
||||
|
||||
/* Parse the day and check the T between date and time */
|
||||
UA_UInt64 day = 0;
|
||||
len = parseUInt64(&tokenData[pos], 2, &day);
|
||||
pos += len;
|
||||
UA_CHECK(len == 2 || tokenData[pos] != 'T',
|
||||
return UA_STATUSCODE_BADDECODINGERROR);
|
||||
dts.tm_mday = (UA_UInt16)day;
|
||||
pos++;
|
||||
|
||||
/* Parse the hour */
|
||||
UA_UInt64 hour = 0;
|
||||
len = parseUInt64(&tokenData[pos], 2, &hour);
|
||||
pos += len;
|
||||
UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR);
|
||||
dts.tm_hour = (UA_UInt16)hour;
|
||||
if(tokenData[pos] == ':')
|
||||
pos++;
|
||||
|
||||
/* Parse the minute */
|
||||
UA_UInt64 min = 0;
|
||||
len = parseUInt64(&tokenData[pos], 2, &min);
|
||||
pos += len;
|
||||
UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR);
|
||||
dts.tm_min = (UA_UInt16)min;
|
||||
if(tokenData[pos] == ':')
|
||||
pos++;
|
||||
|
||||
/* Parse the second */
|
||||
UA_UInt64 sec = 0;
|
||||
len = parseUInt64(&tokenData[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(tokenData[pos] == ',' || tokenData[pos] == '.') {
|
||||
pos++;
|
||||
double frac = 0.0;
|
||||
double denom = 0.1;
|
||||
while(pos < tokenSize &&
|
||||
tokenData[pos] >= '0' && tokenData[pos] <= '9') {
|
||||
frac += denom * (tokenData[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 != tokenSize - 1)
|
||||
return UA_STATUSCODE_BADDECODINGERROR;
|
||||
|
||||
*dst = dt;
|
||||
|
||||
ctx->index++;
|
||||
return UA_STATUSCODE_GOOD;
|
||||
UA_ByteString input = {tokenSize, (UA_Byte*)(uintptr_t)tokenData};
|
||||
UA_StatusCode res = decodeDateTime(input, dst);
|
||||
if(UA_LIKELY(res == UA_STATUSCODE_GOOD))
|
||||
ctx->index++;
|
||||
return res;
|
||||
}
|
||||
|
||||
DECODE_JSON(StatusCode) {
|
||||
|
@ -20,6 +20,8 @@
|
||||
#include "pcg_basic.h"
|
||||
#include "base64.h"
|
||||
#include "itoa.h"
|
||||
#include "../../deps/parse_num.h"
|
||||
#include "../../deps/libc_time.h"
|
||||
|
||||
const char * attributeIdNames[28] = {
|
||||
"Invalid", "NodeId", "NodeClass", "BrowseName", "DisplayName", "Description",
|
||||
@ -327,6 +329,135 @@ UA_ByteString_fromBase64(UA_ByteString *bs,
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
/* DateTime parsing for both JSON and XML */
|
||||
UA_StatusCode
|
||||
decodeDateTime(const UA_ByteString s, UA_DateTime *dst) {
|
||||
/* 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(s.length == 0 || s.data[s.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(s.data[0] == '-' || s.data[0] == '+')
|
||||
pos++;
|
||||
UA_Int64 year = 0;
|
||||
len = parseInt64((char*)&s.data[pos], 5, &year);
|
||||
pos += len;
|
||||
if(len != 4 && s.data[pos] != '-')
|
||||
return UA_STATUSCODE_BADDECODINGERROR;
|
||||
if(s.data[0] == '-')
|
||||
year = -year;
|
||||
dts.tm_year = (UA_Int16)year - 1900;
|
||||
if(s.data[pos] == '-')
|
||||
pos++;
|
||||
|
||||
/* Parse the month */
|
||||
UA_UInt64 month = 0;
|
||||
len = parseUInt64((char*)&s.data[pos], 2, &month);
|
||||
pos += len;
|
||||
UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR);
|
||||
dts.tm_mon = (UA_UInt16)month - 1;
|
||||
if(s.data[pos] == '-')
|
||||
pos++;
|
||||
|
||||
/* Parse the day and check the T between date and time */
|
||||
UA_UInt64 day = 0;
|
||||
len = parseUInt64((char*)&s.data[pos], 2, &day);
|
||||
pos += len;
|
||||
UA_CHECK(len == 2 || s.data[pos] != 'T',
|
||||
return UA_STATUSCODE_BADDECODINGERROR);
|
||||
dts.tm_mday = (UA_UInt16)day;
|
||||
pos++;
|
||||
|
||||
/* Parse the hour */
|
||||
UA_UInt64 hour = 0;
|
||||
len = parseUInt64((char*)&s.data[pos], 2, &hour);
|
||||
pos += len;
|
||||
UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR);
|
||||
dts.tm_hour = (UA_UInt16)hour;
|
||||
if(s.data[pos] == ':')
|
||||
pos++;
|
||||
|
||||
/* Parse the minute */
|
||||
UA_UInt64 min = 0;
|
||||
len = parseUInt64((char*)&s.data[pos], 2, &min);
|
||||
pos += len;
|
||||
UA_CHECK(len == 2, return UA_STATUSCODE_BADDECODINGERROR);
|
||||
dts.tm_min = (UA_UInt16)min;
|
||||
if(s.data[pos] == ':')
|
||||
pos++;
|
||||
|
||||
/* Parse the second */
|
||||
UA_UInt64 sec = 0;
|
||||
len = parseUInt64((char*)&s.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(s.data[pos] == ',' || s.data[pos] == '.') {
|
||||
pos++;
|
||||
double frac = 0.0;
|
||||
double denom = 0.1;
|
||||
while(pos < s.length &&
|
||||
s.data[pos] >= '0' && s.data[pos] <= '9') {
|
||||
frac += denom * (s.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 != s.length - 1)
|
||||
return UA_STATUSCODE_BADDECODINGERROR;
|
||||
|
||||
*dst = dt;
|
||||
return UA_STATUSCODE_GOOD;
|
||||
}
|
||||
|
||||
/* Key Value Map */
|
||||
|
||||
const UA_KeyValueMap UA_KEYVALUEMAP_NULL = {0, NULL};
|
||||
|
@ -123,6 +123,9 @@ UA_StatusCode
|
||||
nodeId_printEscape(const UA_NodeId *id, UA_String *output,
|
||||
const UA_NamespaceMapping *nsMapping, UA_Escaping idEsc);
|
||||
|
||||
UA_StatusCode
|
||||
decodeDateTime(const UA_ByteString s, UA_DateTime *dst);
|
||||
|
||||
/**
|
||||
* Error checking macros
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user