#include <open62541/types.h>
#include "ua_server_internal.h"
#include "ua_subscription.h"
#ifdef UA_ENABLE_SUBSCRIPTIONS_EVENTS
static UA_StatusCode
readSAOfromEventInstance(UA_FilterEvalContext *ctx,
const UA_SimpleAttributeOperand *sao,
UA_Variant *value) {
const UA_NodeId *ei = ctx->ed.eventInstance;
if(!ei)
return UA_STATUSCODE_BADINVALIDARGUMENT;
UA_ReadValueId rvi;
UA_ReadValueId_init(&rvi);
rvi.indexRange = sao->indexRange;
rvi.attributeId = sao->attributeId;
UA_DataValue v;
if(sao->browsePathSize == 0) {
rvi.nodeId = *ei;
UA_NodeId conditionTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE);
if(UA_NodeId_equal(&sao->typeDefinitionId, &conditionTypeId)) {
#ifdef UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS
UA_StatusCode res = UA_getConditionId(ctx->server, ctx->ed.eventInstance, &rvi.nodeId);
UA_CHECK_STATUS(res, return res);
#else
return UA_STATUSCODE_BADNOTSUPPORTED;
#endif
}
v = readWithSession(ctx->server, ctx->session, &rvi, UA_TIMESTAMPSTORETURN_NEITHER);
} else {
UA_BrowsePathResult bpr =
browseSimplifiedBrowsePath(ctx->server, *ei, sao->browsePathSize, sao->browsePath);
if(bpr.targetsSize == 0 && bpr.statusCode == UA_STATUSCODE_GOOD)
bpr.statusCode = UA_STATUSCODE_BADNOTFOUND;
if(bpr.statusCode != UA_STATUSCODE_GOOD) {
UA_StatusCode res = bpr.statusCode;
UA_BrowsePathResult_clear(&bpr);
return res;
}
rvi.nodeId = bpr.targets[0].targetId.nodeId;
v = readWithSession(ctx->server, ctx->session, &rvi, UA_TIMESTAMPSTORETURN_NEITHER);
UA_BrowsePathResult_clear(&bpr);
}
if(v.status != UA_STATUSCODE_GOOD) {
UA_Variant_clear(&v.value);
return v.status;
}
if(!v.hasValue)
return UA_STATUSCODE_BADNODATAAVAILABLE;
*value = v.value;
return UA_STATUSCODE_GOOD;
}
#define MANDATORY_EVENT_PROPERTIES_COUNT 8
static UA_String mandatoryEventProperties[MANDATORY_EVENT_PROPERTIES_COUNT] = {
UA_STRING_STATIC("/EventId"),
UA_STRING_STATIC("/EventType"),
UA_STRING_STATIC("/SourceNode"),
UA_STRING_STATIC("/SourceName"),
UA_STRING_STATIC("/Time"),
UA_STRING_STATIC("/ReceiveTime"),
UA_STRING_STATIC("/Message"),
UA_STRING_STATIC("/Severity")
};
void
UA_FilterEvalContext_init(UA_FilterEvalContext *ctx) {
memset(ctx, 0, sizeof(UA_FilterEvalContext));
}
void
UA_FilterEvalContext_reset(UA_FilterEvalContext *ctx) {
UA_KeyValueMap_clear(&ctx->fieldCache);
if(ctx->eventId.data != ctx->eventIdBuf)
UA_ByteString_init(&ctx->eventId);
}
UA_StatusCode
cacheEventId(UA_FilterEvalContext *ctx) {
if(ctx->eventId.length > 0)
return UA_STATUSCODE_GOOD;
const UA_EventDescription *ed = &ctx->ed;
if(ed && ed->eventFields) {
const UA_Variant *found =
UA_KeyValueMap_get(ed->eventFields, UA_QUALIFIEDNAME(0, "/EventId"));
if(found) {
if(!UA_Variant_hasScalarType(found, &UA_TYPES[UA_TYPES_BYTESTRING]))
return UA_STATUSCODE_BADTYPEMISMATCH;
ctx->eventId = *(UA_ByteString*)found->data;
return UA_STATUSCODE_GOOD;
}
}
static UA_QualifiedName eventIdName = {0, UA_STRING_STATIC("EventId")};
UA_SimpleAttributeOperand sao;
UA_SimpleAttributeOperand_init(&sao);
sao.attributeId = UA_ATTRIBUTEID_VALUE;
sao.browsePath = &eventIdName;
sao.browsePathSize = 1;
UA_Variant val;
UA_Variant_init(&val);
UA_StatusCode res = readSAOfromEventInstance(ctx, &sao, &val);
if(res == UA_STATUSCODE_GOOD) {
UA_Boolean isIdString = UA_Variant_hasScalarType(&val, &UA_TYPES[UA_TYPES_BYTESTRING]);
UA_assert(!isIdString || val.data != NULL);
if(isIdString && ((UA_ByteString*)val.data)->length > 0) {
res = UA_KeyValueMap_set(&ctx->fieldCache,
UA_QUALIFIEDNAME(0, "/EventId"), &val);
UA_Variant_clear(&val);
if(res != UA_STATUSCODE_GOOD)
return res;
const UA_Variant *cached =
UA_KeyValueMap_get(&ctx->fieldCache,
UA_QUALIFIEDNAME(0, "/EventId"));
ctx->eventId = *(UA_ByteString*)cached->data;
return UA_STATUSCODE_GOOD;
}
UA_Variant_clear(&val);
}
UA_UInt32 *ids = (UA_UInt32*)ctx->eventIdBuf;
ids[0] = UA_UInt32_random();
ids[1] = UA_UInt32_random();
ids[2] = UA_UInt32_random();
ids[3] = UA_UInt32_random();
ctx->eventId = (UA_ByteString){16, ctx->eventIdBuf};
return UA_STATUSCODE_GOOD;
}
static UA_Variant *
getEventFieldNonNormalForm(UA_Server *server, const UA_KeyValueMap *eventFields,
const UA_SimpleAttributeOperand *sao) {
if(!eventFields)
return NULL;
UA_SimpleAttributeOperand keySao;
for(size_t i = 0; i < eventFields->mapSize; i++) {
UA_KeyValuePair *kvp = &eventFields->map[i];
UA_StatusCode res =
sao_parseWithDefaultNsIdx(&keySao, kvp->key.name,
kvp->key.namespaceIndex);
if(res != UA_STATUSCODE_GOOD)
continue;
UA_Boolean found = UA_SimpleAttributeOperand_equal(sao, &keySao);
UA_SimpleAttributeOperand_clear(&keySao);
if(found)
return &kvp->value;
}
return NULL;
}
UA_StatusCode
resolveSAO(UA_FilterEvalContext *ctx, const UA_SimpleAttributeOperand *sao,
UA_Variant *out) {
UA_StatusCode res = UA_STATUSCODE_GOOD;
const UA_EventDescription *ed = &ctx->ed;
const UA_Variant *found;
UA_Byte pathBuf[512];
UA_QualifiedName pathString = {0, {512, pathBuf}};
static UA_NodeId baseEventTypeId = {0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_BASEEVENTTYPE}};
UA_SimpleAttributeOperand tmp_sao = *sao;
tmp_sao.indexRange = UA_STRING_NULL;
if(UA_NodeId_isNull(&tmp_sao.typeDefinitionId))
tmp_sao.typeDefinitionId = baseEventTypeId;
search_again:
res = UA_SimpleAttributeOperand_print(&tmp_sao, &pathString.name);
if(res != UA_STATUSCODE_GOOD)
return res;
if(UA_String_equal(&pathString.name, &mandatoryEventProperties[0])) {
res = cacheEventId(ctx);
if(res != UA_STATUSCODE_GOOD)
return res;
UA_Variant_setScalar(out, &ctx->eventId, &UA_TYPES[UA_TYPES_BYTESTRING]);
out->storageType = UA_VARIANT_DATA_NODELETE;
return UA_STATUSCODE_GOOD;
}
found = UA_KeyValueMap_get(ed->eventFields, pathString);
if(!found)
found = UA_KeyValueMap_get(&ctx->fieldCache, pathString);
if(!found)
found = getEventFieldNonNormalForm(ctx->server, ed->eventFields, &tmp_sao);
if(found) {
if(sao->indexRange.length == 0) {
*out = *found;
out->storageType = UA_VARIANT_DATA_NODELETE;
} else {
UA_NumericRange range;
res = UA_NumericRange_parse(&range, sao->indexRange);
if(res != UA_STATUSCODE_GOOD)
return res;
res = UA_Variant_copyRange(found, out, range);
UA_free(range.dimensions);
}
return res;
}
if(!UA_NodeId_equal(&tmp_sao.typeDefinitionId, &baseEventTypeId)) {
tmp_sao.typeDefinitionId = baseEventTypeId;
goto search_again;
}
res = readSAOfromEventInstance(ctx, sao, out);
if(res == UA_STATUSCODE_GOOD)
return UA_STATUSCODE_GOOD;
UA_String_init(&tmp_sao.indexRange);
if(UA_String_equal(&pathString.name, &mandatoryEventProperties[1])) {
return UA_Variant_setScalarCopy(out, &ed->eventType, &UA_TYPES[UA_TYPES_NODEID]);
} else if(UA_String_equal(&pathString.name, &mandatoryEventProperties[2])) {
return UA_Variant_setScalarCopy(out, &ed->sourceNode, &UA_TYPES[UA_TYPES_NODEID]);
} else if(UA_String_equal(&pathString.name, &mandatoryEventProperties[3])) {
UA_ReadValueId rvi;
UA_ReadValueId_init(&rvi);
rvi.nodeId = ed->sourceNode;
rvi.attributeId = UA_ATTRIBUTEID_DISPLAYNAME;
UA_DataValue dv = readWithSession(ctx->server, ctx->session, &rvi,
UA_TIMESTAMPSTORETURN_NEITHER);
if(dv.status != UA_STATUSCODE_GOOD) {
res = dv.status;
UA_DataValue_clear(&dv);
return res;
}
if(dv.value.type != &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]) {
UA_DataValue_clear(&dv);
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_LocalizedText *displayName = (UA_LocalizedText*)dv.value.data;
res = UA_Variant_setScalarCopy(out, &displayName->text, &UA_TYPES[UA_TYPES_STRING]);
UA_DataValue_clear(&dv);
return res;
} else if(UA_String_equal(&pathString.name, &mandatoryEventProperties[4]) ||
UA_String_equal(&pathString.name, &mandatoryEventProperties[5])) {
UA_EventLoop *el = ctx->server->config.eventLoop;
UA_DateTime rcvTime = el->dateTime_now(el);
return UA_Variant_setScalarCopy(out, &rcvTime, &UA_TYPES[UA_TYPES_DATETIME]);
} else if(UA_String_equal(&pathString.name, &mandatoryEventProperties[6])) {
return UA_Variant_setScalarCopy(out, &ed->message, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
} else if(UA_String_equal(&pathString.name, &mandatoryEventProperties[7])) {
return UA_Variant_setScalarCopy(out, &ed->severity, &UA_TYPES[UA_TYPES_UINT16]);
}
return UA_STATUSCODE_GOOD;
}
typedef enum {
UA_TERNARY_FALSE = -1,
UA_TERNARY_NULL = 0,
UA_TERNARY_TRUE = 1
} UA_Ternary;
static UA_Ternary
UA_Ternary_and(UA_Ternary first, UA_Ternary second) {
if(first == UA_TERNARY_FALSE || second == UA_TERNARY_FALSE)
return UA_TERNARY_FALSE;
if(first == UA_TERNARY_NULL || second == UA_TERNARY_NULL)
return UA_TERNARY_NULL;
return UA_TERNARY_TRUE;
}
static UA_Ternary
UA_Ternary_or(UA_Ternary first, UA_Ternary second) {
if(first == UA_TERNARY_TRUE || second == UA_TERNARY_TRUE)
return UA_TERNARY_TRUE;
if(first == UA_TERNARY_NULL || second == UA_TERNARY_NULL)
return UA_TERNARY_NULL;
return UA_TERNARY_FALSE;
}
static UA_Ternary
UA_Ternary_not(UA_Ternary v) {
return (UA_Ternary)((int)v * -1);
}
static UA_Ternary v2t(const UA_Variant *v) {
if(UA_Variant_isEmpty(v) || !UA_Variant_hasScalarType(v, &UA_TYPES[UA_TYPES_BOOLEAN]))
return UA_TERNARY_NULL;
UA_Boolean b = *(UA_Boolean*)v->data;
return (b) ? UA_TERNARY_TRUE : UA_TERNARY_FALSE;
}
static const UA_Boolean bFalse = false;
static const UA_Boolean bTrue = true;
static UA_Variant t2v(UA_Ternary t) {
UA_Variant v;
UA_Variant_init(&v);
switch(t) {
case UA_TERNARY_FALSE:
UA_Variant_setScalar(&v, (void*)(uintptr_t)&bFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
break;
case UA_TERNARY_TRUE:
UA_Variant_setScalar(&v, (void*)(uintptr_t)&bTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
break;
default:
return v;
}
v.storageType = UA_VARIANT_DATA_NODELETE;
return v;
}
static UA_Byte typePrecedence[UA_DATATYPEKINDS] = {
12, 10, 11, 8, 9, 5, 6, 3, 4, 2, 1, 14, 255, 13, 255, 255, 16,
15, 7, 18, 17, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
};
static UA_SByte convertLookup[21][21] = {
{ 0, 1,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 1,-1, 2,-1,-1, 1, 1, 1,-1},
{ 2, 0,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 1,-1, 2,-1,-1, 1, 1, 1,-1},
{-1,-1, 0,-1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1},
{-1,-1,-1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1},
{ 2, 2,-1,-1, 0,-1, 2,-1, 2, 2, 2,-1, 2,-1, 2,-1,-1, 2, 2, 2,-1},
{-1,-1,-1,-1,-1, 0,-1,-1,-1,-1,-1, 2,-1,-1, 1,-1,-1,-1,-1,-1,-1},
{ 2, 2,-1,-1, 1,-1, 0,-1, 2, 2, 2,-1, 2,-1, 2,-1,-1, 2, 2, 2,-1},
{-1,-1, 2,-1,-1,-1,-1, 0,-1,-1,-1,-1,-1,-1, 2,-1,-1,-1,-1,-1,-1},
{ 2, 2,-1,-1, 1,-1, 1,-1, 0, 1, 1,-1, 2,-1, 2,-1,-1, 2, 1, 1,-1},
{ 2, 2,-1,-1, 1,-1, 1,-1, 2, 0, 1,-1, 2, 2, 2,-1,-1, 2, 2, 1,-1},
{ 2, 2,-1,-1, 1,-1, 1,-1, 2, 2, 0,-1, 2, 2, 2,-1,-1, 2, 2, 2,-1},
{-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1, 0,-1,-1, 1,-1,-1,-1,-1,-1,-1},
{ 2, 2,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 0,-1, 2,-1,-1, 1, 1, 1,-1},
{-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1,-1,-1, 0,-1,-1,-1, 2, 1, 1,-1},
{ 1, 1,-1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1,-1, 0, 2, 2, 1, 1, 1,-1},
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 0,-1,-1,-1,-1,-1},
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 0,-1,-1,-1,-1},
{ 2, 2,-1,-1, 1,-1, 1,-1, 1, 1, 1,-1, 2, 1, 2,-1,-1, 0, 1, 1,-1},
{ 2, 2,-1,-1, 1,-1, 1,-1, 2, 1, 1,-1, 2, 2, 2,-1,-1, 2, 0, 1,-1},
{ 2, 2,-1,-1, 1,-1, 1,-1, 2, 2, 1,-1, 2, 2, 2,-1,-1, 2, 2, 0,-1},
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0}
};
static UA_Byte convertLookupIndex[UA_DATATYPEKINDS] = {
0, 12, 1, 8, 17, 9, 18, 10, 19, 6, 4, 14, 3, 7, 2,
20, 11, 5, 13, 16, 15,255,255,255,255,255,255,255,255,255,255
};
static const UA_DataType *
implicitCastTargetType(const UA_DataType *t1, const UA_DataType *t2) {
if(!t1 || t1 == t2)
return t1;
UA_Byte p1 = typePrecedence[t1->typeKind];
UA_Byte p2 = typePrecedence[t2->typeKind];
if(p1 == UA_BYTE_MAX || p2 == UA_BYTE_MAX)
return NULL;
const UA_DataType *targetType = (p1 < p2) ? t1 : t2;
const UA_DataType *sourceType = (p1 < p2) ? t2 : t1;
UA_Byte sourceIndex = convertLookupIndex[sourceType->typeKind];
UA_Byte targetIndex = convertLookupIndex[targetType->typeKind];
if(sourceIndex == UA_BYTE_MAX || targetIndex == UA_BYTE_MAX)
return NULL;
UA_SByte castingRule = convertLookup[sourceIndex][targetIndex];
if(castingRule != 0 && castingRule != 1)
return NULL;
return targetType;
}
#define UA_CAST_SIGNED(t, T) \
if(i < (UA_Int64)T##_MIN || i > (UA_Int64)T##_MAX) { \
UA_free(data); \
return; \
} \
*(t*)data = (t)i; \
do { } while(0)
#define UA_CAST_UNSIGNED(t, T) \
if(u > T##_MAX) { \
UA_free(data); \
return; \
} \
*(t*)data = (t)u; \
do { } while(0)
#define UA_CAST_FLOAT(t, T) \
if(f + 0.5 < (UA_Double)T##_MIN || f + 0.5 > (UA_Double)T##_MAX) { \
UA_free(data); \
return; \
} \
*(t*)data = (t)(f + 0.5); \
do { } while(0)
static void
castNumerical(const UA_Variant *in, const UA_DataType *type, UA_Variant *out) {
UA_assert(UA_Variant_isScalar(in));
UA_Variant_init(out);
UA_Int64 i = 0;
UA_UInt64 u = 0;
UA_Double f = 0.0;
const UA_DataTypeKind ink = (UA_DataTypeKind)in->type->typeKind;
switch(ink) {
case UA_DATATYPEKIND_SBYTE: i = *(UA_SByte*)in->data; break;
case UA_DATATYPEKIND_INT16: i = *(UA_Int16*)in->data; break;
case UA_DATATYPEKIND_INT32: i = *(UA_Int32*)in->data; break;
case UA_DATATYPEKIND_INT64: i = *(UA_Int64*)in->data; break;
case UA_DATATYPEKIND_BYTE:
case UA_DATATYPEKIND_BOOLEAN: u = *(UA_Byte*)in->data; break;
case UA_DATATYPEKIND_UINT16: u = *(UA_UInt16*)in->data; break;
case UA_DATATYPEKIND_UINT32:
case UA_DATATYPEKIND_STATUSCODE: u = *(UA_UInt32*)in->data; break;
case UA_DATATYPEKIND_UINT64: u = *(UA_UInt64*)in->data; break;
case UA_DATATYPEKIND_FLOAT: f = *(UA_Float*)in->data; break;
case UA_DATATYPEKIND_DOUBLE: f = *(UA_Double*)in->data; break;
default: return;
}
void *data = UA_new(type);
if(!data)
return;
if(ink == UA_DATATYPEKIND_SBYTE || ink == UA_DATATYPEKIND_INT16 ||
ink == UA_DATATYPEKIND_INT32 || ink == UA_DATATYPEKIND_INT64) {
switch(type->typeKind) {
case UA_DATATYPEKIND_SBYTE: UA_CAST_SIGNED(UA_SByte, UA_SBYTE); break;
case UA_DATATYPEKIND_INT16: UA_CAST_SIGNED(UA_Int16, UA_INT16); break;
case UA_DATATYPEKIND_INT32: UA_CAST_SIGNED(UA_Int32, UA_INT32); break;
case UA_DATATYPEKIND_INT64: *(UA_Int64*)data = i; break;
case UA_DATATYPEKIND_BYTE: UA_CAST_SIGNED(UA_Byte, UA_BYTE); break;
case UA_DATATYPEKIND_UINT16: UA_CAST_SIGNED(UA_UInt16, UA_UINT16); break;
case UA_DATATYPEKIND_UINT32: UA_CAST_SIGNED(UA_UInt32, UA_UINT32); break;
case UA_DATATYPEKIND_UINT64: UA_CAST_SIGNED(UA_UInt64, UA_UINT64); break;
case UA_DATATYPEKIND_FLOAT: *(UA_Float*)data = (UA_Float)i; break;
case UA_DATATYPEKIND_DOUBLE: *(UA_Double*)data = (UA_Double)i; break;
default:
UA_free(data);
return;
}
} else if(ink == UA_DATATYPEKIND_BYTE || ink == UA_DATATYPEKIND_UINT16 ||
ink == UA_DATATYPEKIND_UINT32 || ink == UA_DATATYPEKIND_UINT64) {
switch(type->typeKind) {
case UA_DATATYPEKIND_SBYTE: UA_CAST_UNSIGNED(UA_SByte, UA_SBYTE); break;
case UA_DATATYPEKIND_INT16: UA_CAST_UNSIGNED(UA_Int16, UA_INT16); break;
case UA_DATATYPEKIND_INT32: UA_CAST_UNSIGNED(UA_Int32, UA_INT32); break;
case UA_DATATYPEKIND_INT64: UA_CAST_UNSIGNED(UA_Int64, UA_INT64); break;
case UA_DATATYPEKIND_BYTE: UA_CAST_UNSIGNED(UA_Byte, UA_BYTE); break;
case UA_DATATYPEKIND_UINT16: UA_CAST_UNSIGNED(UA_UInt16, UA_UINT16); break;
case UA_DATATYPEKIND_UINT32: UA_CAST_UNSIGNED(UA_UInt32, UA_UINT32); break;
case UA_DATATYPEKIND_UINT64: *(UA_UInt64*)data = u; break;
case UA_DATATYPEKIND_FLOAT: *(UA_Float*)data = (UA_Float)u; break;
case UA_DATATYPEKIND_DOUBLE: *(UA_Double*)data = (UA_Double)u; break;
default:
UA_free(data);
return;
}
} else {
if(f != f) {
UA_free(data);
return;
}
switch(type->typeKind) {
case UA_DATATYPEKIND_SBYTE: UA_CAST_FLOAT(UA_SByte, UA_SBYTE); break;
case UA_DATATYPEKIND_INT16: UA_CAST_FLOAT(UA_Int16, UA_INT16); break;
case UA_DATATYPEKIND_INT32: UA_CAST_FLOAT(UA_Int32, UA_INT32); break;
case UA_DATATYPEKIND_INT64: UA_CAST_FLOAT(UA_Int64, UA_INT64); break;
case UA_DATATYPEKIND_BYTE: UA_CAST_FLOAT(UA_Byte, UA_BYTE); break;
case UA_DATATYPEKIND_UINT16: UA_CAST_FLOAT(UA_UInt16, UA_UINT16); break;
case UA_DATATYPEKIND_UINT32: UA_CAST_FLOAT(UA_UInt32, UA_UINT32); break;
case UA_DATATYPEKIND_UINT64: UA_CAST_FLOAT(UA_UInt64, UA_UINT64); break;
case UA_DATATYPEKIND_FLOAT: *(UA_Float*)data = (UA_Float)f; break;
case UA_DATATYPEKIND_DOUBLE: *(UA_Double*)data = (UA_Double)f; break;
default:
UA_free(data);
return;
}
}
UA_Variant_setScalar(out, data, type);
}
static UA_INLINE UA_Byte uppercase(UA_Byte in) { return in | 32; }
static UA_StatusCode
castImplicitFromString(const UA_Variant *in, const UA_DataType *outType, UA_Variant *out) {
UA_StatusCode res = UA_STATUSCODE_GOOD;
if(outType == &UA_TYPES[UA_TYPES_BOOLEAN]) {
UA_Boolean b;
const UA_String *inStr = (const UA_String*)in->data;
if(inStr->length == 1 && inStr->data[0] == '0') {
b = false;
} else if(inStr->length == 1 && inStr->data[0] == '1') {
b = true;
} else if(inStr->length == 4 &&
uppercase(inStr->data[0])== 'T' && uppercase(inStr->data[1])== 'R' &&
uppercase(inStr->data[2])== 'U' && uppercase(inStr->data[3])== 'E') {
b = true;
} else if(inStr->length == 5 && uppercase(inStr->data[0])== 'F' &&
uppercase(inStr->data[1])== 'A' && uppercase(inStr->data[2])== 'L' &&
uppercase(inStr->data[3])== 'S' && uppercase(inStr->data[4])== 'E') {
b = false;
} else {
return UA_STATUSCODE_BADTYPEMISMATCH;
}
return UA_Variant_setScalarCopy(out, &b, outType);
} else if(outType == &UA_TYPES[UA_TYPES_GUID]) {
UA_Guid guid;
res = UA_Guid_parse(&guid, *(UA_String*)in->data);
if(res != UA_STATUSCODE_GOOD)
return res;
return UA_Variant_setScalarCopy(out, &guid, outType);
}
#ifdef UA_ENABLE_JSON_ENCODING
else if(UA_DataType_isNumeric(outType)) {
void *outData = UA_new(outType);
if(!outData)
return UA_STATUSCODE_BADOUTOFMEMORY;
res = UA_decodeJson((const UA_ByteString*)in->data, outData, outType, NULL);
if(res != UA_STATUSCODE_GOOD) {
UA_free(outData);
return res;
}
UA_Variant_setScalar(out, outData, outType);
return UA_STATUSCODE_GOOD;
}
#endif
return UA_STATUSCODE_BADTYPEMISMATCH;
}
static UA_StatusCode
castImplicit(const UA_Variant *in, const UA_DataType *outType, UA_Variant *out) {
if(UA_Variant_isEmpty(in)) {
UA_Variant_init(out);
return UA_STATUSCODE_GOOD;
}
if(!UA_Variant_isScalar(in))
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
if(in->type == outType) {
*out = *in;
out->storageType = UA_VARIANT_DATA_NODELETE;
return UA_STATUSCODE_GOOD;
}
UA_StatusCode res = UA_STATUSCODE_GOOD;
switch(in->type->typeKind) {
case UA_DATATYPEKIND_EXPANDEDNODEID: {
if(outType != &UA_TYPES[UA_TYPES_STRING])
break;
UA_String *outStr = UA_String_new();
if(!outStr)
return UA_STATUSCODE_BADOUTOFMEMORY;
res = UA_ExpandedNodeId_print((const UA_ExpandedNodeId*)in->data, outStr);
if(res != UA_STATUSCODE_GOOD) {
UA_free(outStr);
break;
}
UA_Variant_setScalar(out, outStr, outType);
break;
}
case UA_DATATYPEKIND_NODEID: {
if(outType == &UA_TYPES[UA_TYPES_STRING]) {
UA_String *outStr = UA_String_new();
if(!outStr)
return UA_STATUSCODE_BADOUTOFMEMORY;
res = UA_NodeId_print((const UA_NodeId*)in->data, outStr);
if(res != UA_STATUSCODE_GOOD) {
UA_free(outStr);
break;
}
UA_Variant_setScalar(out, outStr, outType);
} else if(outType == &UA_TYPES[UA_TYPES_EXPANDEDNODEID]) {
UA_ExpandedNodeId *eid = UA_ExpandedNodeId_new();
if(!eid)
return UA_STATUSCODE_BADOUTOFMEMORY;
res = UA_NodeId_copy((const UA_NodeId*)in->data, &eid->nodeId);
if(res != UA_STATUSCODE_GOOD) {
UA_free(eid);
break;
}
UA_Variant_setScalar(out, eid, outType);
}
break;
}
case UA_DATATYPEKIND_STRING:
res = castImplicitFromString(in, outType, out);
break;
case UA_DATATYPEKIND_LOCALIZEDTEXT: {
if(outType != &UA_TYPES[UA_TYPES_STRING])
break;
UA_LocalizedText *inLT = (UA_LocalizedText*)in->data;
res = UA_Variant_setScalarCopy(out, &inLT->text, outType);
break;
}
case UA_DATATYPEKIND_QUALIFIEDNAME: {
UA_QualifiedName *inQN = (UA_QualifiedName*)in->data;
if(outType == &UA_TYPES[UA_TYPES_STRING]) {
res = UA_Variant_setScalarCopy(out, &inQN->name, outType);
} else if(outType == &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]) {
UA_LocalizedText lt;
lt.text = inQN->name;
lt.locale = UA_STRING_NULL;
res = UA_Variant_setScalarCopy(out, <, outType);
}
break;
}
default:
castNumerical(in, outType, out);
}
return res;
}
static UA_StatusCode
resolveOperand(UA_FilterEvalContext *ctx, UA_ExtensionObject *op, UA_Variant *out) {
if(op->encoding != UA_EXTENSIONOBJECT_DECODED &&
op->encoding != UA_EXTENSIONOBJECT_DECODED_NODELETE)
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
if(op->content.decoded.type == &UA_TYPES[UA_TYPES_ELEMENTOPERAND]) {
UA_ElementOperand *eo = (UA_ElementOperand*)op->content.decoded.data;
*out = ctx->operatorResults[eo->index];
out->storageType = UA_VARIANT_DATA_NODELETE;
return UA_STATUSCODE_GOOD;
}
if(op->content.decoded.type == &UA_TYPES[UA_TYPES_LITERALOPERAND]) {
UA_LiteralOperand *lo = (UA_LiteralOperand*)op->content.decoded.data;
*out = lo->value;
out->storageType = UA_VARIANT_DATA_NODELETE;
return UA_STATUSCODE_GOOD;
}
if(op->content.decoded.type == &UA_TYPES[UA_TYPES_SIMPLEATTRIBUTEOPERAND]) {
UA_SimpleAttributeOperand *sao =
(UA_SimpleAttributeOperand*)op->content.decoded.data;
return resolveSAO(ctx, sao, out);
}
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
}
static UA_StatusCode
ofTypeOperator(UA_FilterEvalContext *ctx, size_t index) {
const UA_ContentFilterElement *elm = &ctx->filter.whereClause.elements[index];
UA_assert(elm->filterOperandsSize == 1);
UA_Variant *op0 = &ctx->operandStack[ctx->top++];
UA_StatusCode res = resolveOperand(ctx, &elm->filterOperands[0], op0);
if(res != UA_STATUSCODE_GOOD || !UA_Variant_hasScalarType(op0, &UA_TYPES[UA_TYPES_NODEID]))
return UA_STATUSCODE_BADFILTEROPERANDINVALID;
const UA_NodeId *operandTypeId = (const UA_NodeId *)op0->data;
static UA_QualifiedName eventTypeName = {0, UA_STRING_STATIC("EventType")};
UA_SimpleAttributeOperand sao;
UA_SimpleAttributeOperand_init(&sao);
sao.attributeId = UA_ATTRIBUTEID_VALUE;
sao.browsePath = &eventTypeName;
sao.browsePathSize = 1;
UA_Variant eventType;
UA_Variant_init(&eventType);
res = resolveSAO(ctx, &sao, &eventType);
if(res != UA_STATUSCODE_GOOD)
return res;
if(!UA_Variant_hasScalarType(&eventType, &UA_TYPES[UA_TYPES_NODEID])) {
UA_Variant_clear(&eventType);
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_Boolean ofType =
isNodeInTree_singleRef(ctx->server, (UA_NodeId*)eventType.data, operandTypeId,
UA_REFERENCETYPEINDEX_HASSUBTYPE);
ctx->operatorResults[index] = t2v(ofType ? UA_TERNARY_TRUE : UA_TERNARY_FALSE);
UA_Variant_clear(&eventType);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
andOperator(UA_FilterEvalContext *ctx, size_t index) {
const UA_ContentFilterElement *elm = &ctx->filter.whereClause.elements[index];
UA_assert(elm->filterOperandsSize == 2);
UA_Variant *op0 = &ctx->operandStack[ctx->top++];
UA_StatusCode res = resolveOperand(ctx, &elm->filterOperands[0], op0);
UA_CHECK_STATUS(res, return res);
UA_Variant *op1 = &ctx->operandStack[ctx->top++];
res = resolveOperand(ctx, &elm->filterOperands[1], op1);
UA_CHECK_STATUS(res, return res);
ctx->operatorResults[index] = t2v(UA_Ternary_and(v2t(op0), v2t(op1)));
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
orOperator(UA_FilterEvalContext *ctx, size_t index) {
const UA_ContentFilterElement *elm = &ctx->filter.whereClause.elements[index];
UA_assert(elm->filterOperandsSize == 2);
UA_Variant *op0 = &ctx->operandStack[ctx->top++];
UA_StatusCode res = resolveOperand(ctx, &elm->filterOperands[0], op0);
UA_CHECK_STATUS(res, return res);
UA_Variant *op1 = &ctx->operandStack[ctx->top++];
res = resolveOperand(ctx, &elm->filterOperands[1], op1);
UA_CHECK_STATUS(res, return res);
ctx->operatorResults[index] = t2v(UA_Ternary_or(v2t(op0), v2t(op1)));
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
notOperator(UA_FilterEvalContext *ctx, size_t index) {
const UA_ContentFilterElement *elm = &ctx->filter.whereClause.elements[index];
UA_assert(elm->filterOperandsSize == 1);
UA_Variant *op0 = &ctx->operandStack[ctx->top++];
UA_StatusCode res = resolveOperand(ctx, &elm->filterOperands[0], op0);
UA_CHECK_STATUS(res, return res);
ctx->operatorResults[index] = t2v(UA_Ternary_not(v2t(op0)));
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
castResolveOperands(UA_FilterEvalContext *ctx, size_t index, UA_Boolean setError) {
const UA_ContentFilterElement *elm = &ctx->filter.whereClause.elements[index];
if(ctx->top + elm->filterOperandsSize > UA_EVENTFILTER_MAXOPERANDS)
return UA_STATUSCODE_BADOUTOFMEMORY;
UA_assert(ctx->top == 0);
UA_StatusCode res = UA_STATUSCODE_GOOD;
for(size_t i = 0; i < elm->filterOperandsSize; i++) {
res = resolveOperand(ctx, &elm->filterOperands[i], &ctx->operandStack[ctx->top++]);
UA_CHECK_STATUS(res, return res);
}
UA_assert(ctx->top > 0);
const UA_DataType *targetType = ctx->operandStack[0].type;
for(size_t pos = 1; pos < ctx->top; pos++) {
if(targetType)
targetType = implicitCastTargetType(targetType, ctx->operandStack[pos].type);
if(!targetType)
return UA_STATUSCODE_BADFILTEROPERANDINVALID;
}
for(size_t pos = 0; pos < ctx->top; pos++) {
UA_Variant orig = ctx->operandStack[pos];
res = castImplicit(&orig, targetType, &ctx->operandStack[pos]);
if(res != UA_STATUSCODE_GOOD)
return res;
if(ctx->operandStack[pos].data == orig.data) {
ctx->operandStack[pos].storageType = orig.storageType;
} else {
UA_Variant_clear(&orig);
}
}
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
compareOperator(UA_FilterEvalContext *ctx, size_t index, UA_FilterOperator op) {
UA_assert(ctx->filter.whereClause.elements[index].filterOperandsSize == 2);
UA_assert(ctx->top == 0);
UA_StatusCode res = castResolveOperands(ctx, index, false);
if(res != UA_STATUSCODE_GOOD || !ctx->operandStack[0].type ||
ctx->operandStack[0].type != ctx->operandStack[1].type) {
ctx->operatorResults[index] = t2v(UA_TERNARY_FALSE);
return UA_STATUSCODE_GOOD;
}
UA_assert(ctx->top == 2);
const UA_DataType *type = ctx->operandStack[0].type;
if(op != UA_FILTEROPERATOR_EQUALS && !UA_DataType_isNumeric(type) &&
type->typeKind != UA_DATATYPEKIND_BOOLEAN &&
type->typeKind != UA_DATATYPEKIND_STATUSCODE &&
type->typeKind != UA_DATATYPEKIND_DATETIME)
return UA_STATUSCODE_BADFILTEROPERANDINVALID;
UA_Order eq = UA_order(ctx->operandStack[0].data, ctx->operandStack[1].data, type);
UA_Ternary operatorResult = UA_TERNARY_FALSE;
switch(op) {
case UA_FILTEROPERATOR_EQUALS:
default:
if(eq == UA_ORDER_EQ)
operatorResult = UA_TERNARY_TRUE;
break;
case UA_FILTEROPERATOR_GREATERTHAN:
if(eq == UA_ORDER_MORE)
operatorResult = UA_TERNARY_TRUE;
break;
case UA_FILTEROPERATOR_LESSTHAN:
if(eq == UA_ORDER_LESS)
operatorResult = UA_TERNARY_TRUE;
break;
case UA_FILTEROPERATOR_GREATERTHANOREQUAL:
if(eq == UA_ORDER_MORE || eq == UA_ORDER_EQ)
operatorResult = UA_TERNARY_TRUE;
break;
case UA_FILTEROPERATOR_LESSTHANOREQUAL:
if(eq == UA_ORDER_LESS || eq == UA_ORDER_EQ)
operatorResult = UA_TERNARY_TRUE;
break;
}
ctx->operatorResults[index] = t2v(operatorResult);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
equalsOperator(UA_FilterEvalContext *ctx, size_t index) {
return compareOperator(ctx, index, UA_FILTEROPERATOR_EQUALS);
}
static UA_StatusCode
gtOperator(UA_FilterEvalContext *ctx, size_t index) {
return compareOperator(ctx, index, UA_FILTEROPERATOR_GREATERTHAN);
}
static UA_StatusCode
ltOperator(UA_FilterEvalContext *ctx, size_t index) {
return compareOperator(ctx, index, UA_FILTEROPERATOR_LESSTHAN);
}
static UA_StatusCode
gteOperator(UA_FilterEvalContext *ctx, size_t index) {
return compareOperator(ctx, index, UA_FILTEROPERATOR_GREATERTHANOREQUAL);
}
static UA_StatusCode
lteOperator(UA_FilterEvalContext *ctx, size_t index) {
return compareOperator(ctx, index, UA_FILTEROPERATOR_LESSTHANOREQUAL);
}
static UA_StatusCode
bitwiseOperator(UA_FilterEvalContext *ctx, size_t index, UA_FilterOperator op) {
UA_assert(ctx->filter.whereClause.elements[index].filterOperandsSize == 2);
UA_assert(ctx->top == 0);
UA_StatusCode res = castResolveOperands(ctx, index, true);
UA_CHECK_STATUS(res, return res);
UA_assert(ctx->top == 2);
const UA_DataType *type = ctx->operandStack[0].type;
if(!type || !UA_DataType_isNumeric(type) ||
ctx->operandStack[0].type != ctx->operandStack[1].type)
return UA_STATUSCODE_BADTYPEMISMATCH;
res = UA_Variant_copy(&ctx->operandStack[0], &ctx->operatorResults[index]);
UA_CHECK_STATUS(res, return res);
UA_Byte *bytesOut = (UA_Byte*)ctx->operatorResults[index].data;
const UA_Byte *bytes2 = (const UA_Byte*)ctx->operandStack[1].data;
for(size_t i = 0; i < type->memSize; i++) {
if(op == UA_FILTEROPERATOR_BITWISEAND)
bytesOut[i] = bytesOut[i] & bytes2[i];
else
bytesOut[i] = bytesOut[i] | bytes2[i];
}
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
bitwiseAndOperator(UA_FilterEvalContext *ctx, size_t index) {
return bitwiseOperator(ctx, index, UA_FILTEROPERATOR_BITWISEAND);
}
static UA_StatusCode
bitwiseOrOperator(UA_FilterEvalContext *ctx, size_t index) {
return bitwiseOperator(ctx, index, UA_FILTEROPERATOR_BITWISEOR);
}
static UA_StatusCode
betweenOperator(UA_FilterEvalContext *ctx, size_t index) {
UA_assert(ctx->filter.whereClause.elements[index].filterOperandsSize == 3);
UA_assert(ctx->top == 0);
UA_StatusCode res = castResolveOperands(ctx, index, false);
if(res != UA_STATUSCODE_GOOD) {
ctx->operatorResults[index] = t2v(UA_TERNARY_FALSE);
return UA_STATUSCODE_GOOD;
}
UA_assert(ctx->top == 3);
const UA_DataType *type = ctx->operandStack[0].type;
if(!type || !UA_DataType_isNumeric(type) ||
type != ctx->operandStack[1].type || type != ctx->operandStack[2].type)
return UA_STATUSCODE_BADTYPEMISMATCH;
UA_Order o1 = UA_order(ctx->operandStack[0].data, ctx->operandStack[1].data, type);
UA_Order o2 = UA_order(ctx->operandStack[0].data, ctx->operandStack[2].data, type);
UA_Ternary comp = ((o1 == UA_ORDER_MORE || o1 == UA_ORDER_EQ) &&
(o2 == UA_ORDER_LESS || o2 == UA_ORDER_EQ)) ?
UA_TERNARY_TRUE : UA_TERNARY_FALSE;
ctx->operatorResults[index] = t2v(comp);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
inListOperator(UA_FilterEvalContext *ctx, size_t index) {
const UA_ContentFilterElement *elm = &ctx->filter.whereClause.elements[index];
UA_assert(elm->filterOperandsSize >= 2);
UA_Boolean found = false;
UA_Variant *op0 = &ctx->operandStack[ctx->top++];
UA_Variant *op1 = &ctx->operandStack[ctx->top++];
UA_StatusCode res = resolveOperand(ctx, &elm->filterOperands[0], op0);
UA_CHECK_STATUS(res, return res);
for(size_t i = 1; i < elm->filterOperandsSize && !found; i++) {
res = resolveOperand(ctx, &elm->filterOperands[i], op1);
if(res != UA_STATUSCODE_GOOD)
continue;
if(op0->type == op1->type && UA_equal(op0->data, op1->data, op0->type))
found = true;
UA_Variant_clear(op1);
}
ctx->operatorResults[index] = t2v((found) ? UA_TERNARY_TRUE: UA_TERNARY_FALSE);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
isNullOperator(UA_FilterEvalContext *ctx, size_t index) {
const UA_ContentFilterElement *elm = &ctx->filter.whereClause.elements[index];
UA_assert(elm->filterOperandsSize == 1);
UA_Variant *op0 = &ctx->operandStack[ctx->top++];
UA_StatusCode res = resolveOperand(ctx, &elm->filterOperands[0], op0);
UA_CHECK_STATUS(res, return res);
ctx->operatorResults[index] =
t2v(UA_Variant_isEmpty(op0) ? UA_TERNARY_TRUE : UA_TERNARY_FALSE);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
notImplementedOperator(UA_FilterEvalContext *ctx, size_t index) {
return UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
}
typedef struct {
UA_StatusCode (*operatorMethod)(UA_FilterEvalContext *ctx, size_t index);
UA_Byte minOperatorCount;
UA_Byte maxOperatorCount;
} UA_FilterOperatorJumptableElement;
static const UA_FilterOperatorJumptableElement operatorJumptable[18] = {
{equalsOperator, 2, 2},
{isNullOperator, 1, 1},
{gtOperator, 2, 2},
{ltOperator, 2, 2},
{gteOperator, 2, 2},
{lteOperator, 2, 2},
{notImplementedOperator, 0, UA_EVENTFILTER_MAXOPERANDS},
{notOperator, 1, 1},
{betweenOperator, 3, 3},
{inListOperator, 2, UA_EVENTFILTER_MAXOPERANDS},
{andOperator, 2, 2},
{orOperator, 2, 2},
{notImplementedOperator, 0, UA_EVENTFILTER_MAXOPERANDS},
{notImplementedOperator, 0, UA_EVENTFILTER_MAXOPERANDS},
{ofTypeOperator, 1, 1},
{notImplementedOperator, 0, UA_EVENTFILTER_MAXOPERANDS},
{bitwiseAndOperator, 2, 2},
{bitwiseOrOperator, 2, 2}
};
UA_StatusCode
evaluateWhereClause(UA_FilterEvalContext *ctx) {
UA_LOCK_ASSERT(&ctx->server->serviceMutex);
if(ctx->filter.whereClause.elementsSize == 0)
return UA_STATUSCODE_GOOD;
UA_assert(ctx->top == 0);
UA_Variant_init(&ctx->operatorResults[0]);
UA_StatusCode res = UA_STATUSCODE_GOOD;
const UA_ContentFilter *cf = &ctx->filter.whereClause;
for(size_t i = cf->elementsSize - 1; i < cf->elementsSize; i--) {
UA_ContentFilterElement *cfe = &cf->elements[i];
res = operatorJumptable[cfe->filterOperator].operatorMethod(ctx, i);
for(size_t j = 0; j < ctx->top; j++)
UA_Variant_clear(&ctx->operandStack[j]);
ctx->top = 0;
if(res != UA_STATUSCODE_GOOD)
break;
}
if(res == UA_STATUSCODE_GOOD && v2t(&ctx->operatorResults[0]) != UA_TERNARY_TRUE)
res = UA_STATUSCODE_BADNOMATCH;
for(size_t i = 0; i < cf->elementsSize; i++)
UA_Variant_clear(&ctx->operatorResults[i]);
return res;
}
UA_StatusCode
UA_SimpleAttributeOperandValidation(UA_Server *server,
const UA_SimpleAttributeOperand *sao) {
if(UA_NodeId_isNull(&sao->typeDefinitionId))
return UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
UA_NodeId baseEventTypeId = UA_NS0ID(BASEEVENTTYPE);
if(!isNodeInTree_singleRef(server, &sao->typeDefinitionId, &baseEventTypeId,
UA_REFERENCETYPEINDEX_HASSUBTYPE))
return UA_STATUSCODE_BADTYPEDEFINITIONINVALID;
if(sao->attributeId == 0 || sao->attributeId >= 28)
return UA_STATUSCODE_BADATTRIBUTEIDINVALID;
if(sao->browsePathSize > 0) {
for(size_t j = 0; j < sao->browsePathSize; ++j) {
if(UA_QualifiedName_isNull(&sao->browsePath[j]))
return UA_STATUSCODE_BADBROWSENAMEINVALID;
}
if(!UA_NodeId_equal(&baseEventTypeId, &sao->typeDefinitionId)) {
UA_ReferenceTypeSet reftypes_interface =
UA_REFTYPESET(UA_REFERENCETYPEINDEX_HASSUBTYPE);
UA_ExpandedNodeId *childTypeNodes = NULL;
size_t childTypeNodesSize = 0;
UA_StatusCode res =
browseRecursive(server, 1, &sao->typeDefinitionId, UA_BROWSEDIRECTION_FORWARD,
&reftypes_interface, UA_NODECLASS_OBJECTTYPE, true,
&childTypeNodesSize, &childTypeNodes);
if(res != UA_STATUSCODE_GOOD)
return UA_STATUSCODE_BADATTRIBUTEIDINVALID;
UA_Boolean subTypeContainField = false;
for(size_t j = 0; j < childTypeNodesSize && !subTypeContainField; j++) {
UA_BrowsePathResult bpr =
browseSimplifiedBrowsePath(server, childTypeNodes[j].nodeId,
sao->browsePathSize, sao->browsePath);
if(bpr.statusCode == UA_STATUSCODE_GOOD && bpr.targetsSize > 0)
subTypeContainField = true;
UA_BrowsePathResult_clear(&bpr);
}
UA_Array_delete(childTypeNodes, childTypeNodesSize, &UA_TYPES[UA_TYPES_EXPANDEDNODEID]);
if(!subTypeContainField)
return UA_STATUSCODE_BADNODEIDUNKNOWN;
}
}
if(!UA_String_isEmpty(&sao->indexRange)) {
UA_NumericRange numericRange = UA_NUMERICRANGE("");
if(UA_NumericRange_parse(&numericRange, sao->indexRange) != UA_STATUSCODE_GOOD)
return UA_STATUSCODE_BADINDEXRANGEINVALID;
UA_free(numericRange.dimensions);
if(sao->attributeId != UA_ATTRIBUTEID_VALUE)
return UA_STATUSCODE_BADTYPEMISMATCH;
}
return UA_STATUSCODE_GOOD;
}
UA_ContentFilterElementResult
UA_ContentFilterElementValidation(UA_Server *server, size_t operatorIndex,
size_t operatorsCount,
const UA_ContentFilterElement *ef) {
UA_ContentFilterElementResult er;
UA_ContentFilterElementResult_init(&er);
er.operandStatusCodes = (UA_StatusCode *)
UA_Array_new(ef->filterOperandsSize, &UA_TYPES[UA_TYPES_STATUSCODE]);
if(!er.operandStatusCodes) {
er.statusCode = UA_STATUSCODE_BADOUTOFMEMORY;
return er;
}
er.operandStatusCodesSize = ef->filterOperandsSize;
if(ef->filterOperator < 0 || ef->filterOperator > UA_FILTEROPERATOR_BITWISEOR) {
er.statusCode = UA_STATUSCODE_BADEVENTFILTERINVALID;
return er;
}
if(ef->filterOperandsSize < operatorJumptable[ef->filterOperator].minOperatorCount ||
ef->filterOperandsSize > operatorJumptable[ef->filterOperator].maxOperatorCount) {
er.statusCode = UA_STATUSCODE_BADFILTEROPERANDCOUNTMISMATCH;
return er;
}
for(size_t i = 0; i < ef->filterOperandsSize; i++) {
UA_ExtensionObject *op = &ef->filterOperands[i];
if(op->encoding != UA_EXTENSIONOBJECT_DECODED &&
op->encoding != UA_EXTENSIONOBJECT_DECODED_NODELETE) {
er.operandStatusCodes[i] = UA_STATUSCODE_BADFILTEROPERANDINVALID;
er.statusCode = UA_STATUSCODE_BADFILTEROPERANDINVALID;
return er;
}
if(op->content.decoded.type == &UA_TYPES[UA_TYPES_ELEMENTOPERAND]) {
UA_ElementOperand *eo = (UA_ElementOperand *)op->content.decoded.data;
if(eo->index <= operatorIndex || eo->index >= operatorsCount) {
er.operandStatusCodes[i] = UA_STATUSCODE_BADINDEXRANGEINVALID;
er.statusCode = UA_STATUSCODE_BADINDEXRANGEINVALID;
return er;
}
} else if(op->content.decoded.type == &UA_TYPES[UA_TYPES_ATTRIBUTEOPERAND]) {
er.operandStatusCodes[i] = UA_STATUSCODE_BADNOTSUPPORTED;
er.statusCode = UA_STATUSCODE_BADFILTERNOTALLOWED;
return er;
} else if(op->content.decoded.type != &UA_TYPES[UA_TYPES_SIMPLEATTRIBUTEOPERAND] &&
op->content.decoded.type != &UA_TYPES[UA_TYPES_LITERALOPERAND]) {
er.operandStatusCodes[i] = UA_STATUSCODE_BADFILTEROPERANDINVALID;
er.statusCode = UA_STATUSCODE_BADFILTEROPERANDINVALID;
return er;
}
}
switch(ef->filterOperator) {
case UA_FILTEROPERATOR_INVIEW:
case UA_FILTEROPERATOR_RELATEDTO:
er.statusCode = UA_STATUSCODE_BADEVENTFILTERINVALID;
break;
case UA_FILTEROPERATOR_EQUALS:
case UA_FILTEROPERATOR_GREATERTHAN:
case UA_FILTEROPERATOR_LESSTHAN:
case UA_FILTEROPERATOR_GREATERTHANOREQUAL:
case UA_FILTEROPERATOR_LESSTHANOREQUAL:
case UA_FILTEROPERATOR_BITWISEAND:
case UA_FILTEROPERATOR_BITWISEOR:
case UA_FILTEROPERATOR_BETWEEN:
case UA_FILTEROPERATOR_INLIST:
case UA_FILTEROPERATOR_AND:
case UA_FILTEROPERATOR_OR:
case UA_FILTEROPERATOR_ISNULL:
case UA_FILTEROPERATOR_NOT:
break;
case UA_FILTEROPERATOR_OFTYPE: {
UA_ExtensionObject *o = &ef->filterOperands[0];
UA_LiteralOperand *lo = (UA_LiteralOperand *)o->content.decoded.data;
if(o->content.decoded.type != &UA_TYPES[UA_TYPES_LITERALOPERAND] ||
!UA_Variant_hasScalarType(&lo->value, &UA_TYPES[UA_TYPES_NODEID])) {
er.operandStatusCodes[0] = UA_STATUSCODE_BADFILTEROPERANDINVALID;
er.statusCode = UA_STATUSCODE_BADFILTEROPERANDINVALID;
break;
}
UA_NodeId baseEventTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE);
if(!isNodeInTree_singleRef(server, (UA_NodeId *)lo->value.data, &baseEventTypeId,
UA_REFERENCETYPEINDEX_HASSUBTYPE)) {
er.operandStatusCodes[0] = UA_STATUSCODE_BADFILTEROPERANDINVALID;
er.statusCode = UA_STATUSCODE_BADFILTEROPERANDINVALID;
break;
}
break;
}
case UA_FILTEROPERATOR_LIKE:
case UA_FILTEROPERATOR_CAST:
er.statusCode = UA_STATUSCODE_BADFILTEROPERATORUNSUPPORTED;
break;
default:
er.statusCode = UA_STATUSCODE_BADFILTEROPERATORINVALID;
break;
}
return er;
}
UA_StatusCode
evaluateSelectClause(UA_FilterEvalContext *ctx, UA_EventFieldList *efl) {
if(ctx->filter.selectClausesSize == 0)
return UA_STATUSCODE_GOOD;
efl->eventFields = (UA_Variant*)
UA_calloc(ctx->filter.selectClausesSize, sizeof(UA_Variant));
if(!efl->eventFields)
return UA_STATUSCODE_BADOUTOFMEMORY;
efl->eventFieldsSize = ctx->filter.selectClausesSize;
UA_StatusCode res = UA_STATUSCODE_GOOD;
for(size_t i = 0; i < ctx->filter.selectClausesSize; i++) {
const UA_SimpleAttributeOperand *sao = &ctx->filter.selectClauses[i];
UA_Variant *field = &efl->eventFields[i];
res |= resolveSAO(ctx, sao, field);
if(field->storageType == UA_VARIANT_DATA_NODELETE) {
UA_Variant tmp_val;
UA_StatusCode res_ign = UA_Variant_copy(field, &tmp_val);
(void)res_ign;
*field = tmp_val;
}
}
return res;
}
static UA_StatusCode
UA_MonitoredItem_addEvent(UA_MonitoredItem *mon, UA_FilterEvalContext *ctx) {
UA_StatusCode res = evaluateWhereClause(ctx);
if(res != UA_STATUSCODE_GOOD) {
if(res == UA_STATUSCODE_BADNOMATCH)
res = UA_STATUSCODE_GOOD;
return res;
}
UA_Notification *n = UA_Notification_new();
if(!n)
return UA_STATUSCODE_BADOUTOFMEMORY;
res = evaluateSelectClause(ctx, &n->data.event);
if(res != UA_STATUSCODE_GOOD) {
UA_EventFieldList_clear(&n->data.event);
UA_free(n);
return res;
}
n->data.event.clientHandle = mon->parameters.clientHandle;
n->mon = mon;
UA_Notification_enqueueAndTrigger(ctx->server, n);
return UA_STATUSCODE_GOOD;
}
#ifdef UA_ENABLE_HISTORIZING
static void
setHistoricalEvent(UA_Server *server, const UA_NodeId *emitNode,
const UA_EventDescription *ed) {
UA_Variant historicalEventFilterValue;
UA_Variant_init(&historicalEventFilterValue);
UA_StatusCode retval =
readObjectProperty(server, *emitNode,
UA_QUALIFIEDNAME(0, "HistoricalEventFilter"),
&historicalEventFilterValue);
if(retval != UA_STATUSCODE_GOOD) {
if(retval != UA_STATUSCODE_BADNOMATCH)
UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER,
"Cannot read the HistoricalEventFilter property of a "
"listening node. StatusCode %s",
UA_StatusCode_name(retval));
return;
}
if(!UA_Variant_hasScalarType(&historicalEventFilterValue,
&UA_TYPES[UA_TYPES_EVENTFILTER])) {
UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER,
"HistoricalEventFilter property of a listening node "
"does not have a valid value");
UA_Variant_clear(&historicalEventFilterValue);
return;
}
UA_EventFilter *ef = (UA_EventFilter*)historicalEventFilterValue.data;
UA_FilterEvalContext ctx;
UA_FilterEvalContext_init(&ctx);
ctx.server = server;
ctx.session = &server->adminSession;
ctx.filter = *ef;
ctx.ed = *ed;
UA_StatusCode res = evaluateWhereClause(&ctx);
if(res != UA_STATUSCODE_GOOD) {
UA_FilterEvalContext_reset(&ctx);
UA_Variant_clear(&historicalEventFilterValue);
return;
}
UA_EventFieldList efl;
UA_EventFieldList_init(&efl);
res = evaluateSelectClause(&ctx, &efl);
if(UA_LIKELY(res == UA_STATUSCODE_GOOD)) {
server->config.historyDatabase.
setEvent(server, server->config.historyDatabase.context,
&ed->sourceNode, emitNode, ef, &efl);
}
UA_FilterEvalContext_reset(&ctx);
UA_Variant_clear(&historicalEventFilterValue);
UA_EventFieldList_clear(&efl);
}
#endif
#define EMIT_REFS_ROOT_COUNT 4
static const UA_NodeId emitReferencesRoots[EMIT_REFS_ROOT_COUNT] =
{{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_ORGANIZES}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASCOMPONENT}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASEVENTSOURCE}},
{0, UA_NODEIDTYPE_NUMERIC, {UA_NS0ID_HASNOTIFIER}}};
UA_StatusCode
createEvent(UA_Server *server, const UA_EventDescription *ed,
UA_ByteString *outEventId) {
UA_StatusCode res = UA_STATUSCODE_GOOD;
UA_LOCK_ASSERT(&server->serviceMutex);
if((ed->subscriptionId && !ed->sessionId) ||
(ed->monitoredItemId && !ed->subscriptionId)) {
UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER,
"Event shall be created with a filter on Subscription (or MonitoredItem), "
"but no Session (or Subscription) is defined");
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_LOG_DEBUG(server->config.logging, UA_LOGCATEGORY_SERVER,
"Events: An event of severity %u is emitted by node %N",
ed->severity, ed->sourceNode);
#ifdef UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS
UA_Boolean isCallerAC = false;
if(isConditionOrBranch(server, &ed->eventType, &ed->sourceNode, &isCallerAC) &&
!isCallerAC) {
UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER,
"Condition Events: Please use A&C API to trigger Condition Events 0x%08X",
UA_STATUSCODE_BADINVALIDARGUMENT);
return UA_STATUSCODE_BADINVALIDARGUMENT;
}
#endif
UA_ReferenceTypeSet emitRefTypes;
UA_ReferenceTypeSet_init(&emitRefTypes);
for(size_t i = 0; i < EMIT_REFS_ROOT_COUNT; i++) {
UA_ReferenceTypeSet tmpRefTypes;
res = referenceTypeIndices(server, &emitReferencesRoots[i], &tmpRefTypes, true);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER,
"Events: Could not create the list of references for event "
"propagation with StatusCode %s", UA_StatusCode_name(res));
return res;
}
emitRefTypes = UA_ReferenceTypeSet_union(emitRefTypes, tmpRefTypes);
}
UA_NodeId emitStartNodes[2];
emitStartNodes[0] = ed->sourceNode;
emitStartNodes[1] = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER);
UA_ExpandedNodeId *emitNodes = NULL;
size_t emitNodesSize = 0;
res = browseRecursive(server, 2, emitStartNodes, UA_BROWSEDIRECTION_INVERSE,
&emitRefTypes, UA_NODECLASS_UNSPECIFIED, true,
&emitNodesSize, &emitNodes);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER,
"Events: Could not create the list of nodes listening on the "
"event with StatusCode %s", UA_StatusCode_name(res));
return res;
}
UA_FilterEvalContext ctx;
UA_FilterEvalContext_init(&ctx);
ctx.server = server;
ctx.ed = *ed;
if(outEventId) {
ctx.session = &server->adminSession;
res = cacheEventId(&ctx);
if(UA_LIKELY(res == UA_STATUSCODE_GOOD))
res = UA_ByteString_copy(&ctx.eventId, outEventId);
UA_FilterEvalContext_reset(&ctx);
if(res != UA_STATUSCODE_GOOD)
return res;
}
for(size_t i = 0; i < emitNodesSize; i++) {
if(!UA_ExpandedNodeId_isLocal(&emitNodes[i]))
continue;
const UA_Node *node = UA_NODESTORE_GET(server, &emitNodes[i].nodeId);
if(!node)
continue;
if(node->head.nodeClass != UA_NODECLASS_OBJECT) {
UA_NODESTORE_RELEASE(server, node);
continue;
}
for(UA_MonitoredItem *mon = node->head.monitoredItems;
mon != NULL; mon = mon->sampling.nodeListNext) {
if(mon->itemToMonitor.attributeId != UA_ATTRIBUTEID_EVENTNOTIFIER)
continue;
UA_Subscription *sub = mon->subscription;
if(ed->sessionId && (!sub->session || !UA_NodeId_equal(ed->sessionId, &sub->session->sessionId)))
continue;
if(ed->subscriptionId && *ed->subscriptionId != sub->subscriptionId)
continue;
if(ed->monitoredItemId && *ed->monitoredItemId != mon->monitoredItemId)
continue;
if(!UA_ExtensionObject_hasDecodedType(&mon->parameters.filter,
&UA_TYPES[UA_TYPES_EVENTFILTER])) {
UA_LOG_ERROR_SUBSCRIPTION(server->config.logging, mon->subscription,
"MonitoredItem %" PRIi32 " | "
"The filter must be an EventFilter", mon->monitoredItemId);
continue;
}
ctx.filter = *(UA_EventFilter*)mon->parameters.filter.content.decoded.data;
if(!sub->session)
continue;
ctx.session = sub->session;
res = UA_MonitoredItem_addEvent(mon, &ctx);
UA_FilterEvalContext_reset(&ctx);
if(res != UA_STATUSCODE_GOOD) {
UA_LOG_WARNING(server->config.logging, UA_LOGCATEGORY_SERVER,
"Events: Could not add the event to a listening "
"node with StatusCode %s", UA_StatusCode_name(res));
res = UA_STATUSCODE_GOOD;
}
}
UA_NODESTORE_RELEASE(server, node);
#ifdef UA_ENABLE_HISTORIZING
if(server->config.historyDatabase.setEvent)
setHistoricalEvent(server, &emitNodes[i].nodeId, ed);
#endif
}
if(outEventId && res != UA_STATUSCODE_GOOD)
UA_ByteString_clear(outEventId);
UA_Array_delete(emitNodes, emitNodesSize, &UA_TYPES[UA_TYPES_EXPANDEDNODEID]);
return res;
}
UA_StatusCode
UA_Server_createEvent(UA_Server *server, const UA_NodeId sourceNode,
const UA_NodeId eventType, UA_UInt16 severity,
const UA_LocalizedText message,
const UA_KeyValueMap *eventFields,
const UA_NodeId *eventInstance,
UA_ByteString *outEventId) {
UA_EventDescription ed = {0};
ed.sourceNode = sourceNode;
ed.eventType = eventType;
ed.severity = severity;
ed.message = message;
ed.eventFields = eventFields;
ed.eventInstance = eventInstance;
lockServer(server);
UA_StatusCode res = createEvent(server, &ed, outEventId);
unlockServer(server);
return res;
}
UA_StatusCode UA_EXPORT UA_THREADSAFE
UA_Server_createEventEx(UA_Server *server,
const UA_EventDescription *ed,
UA_ByteString *outEventId) {
lockServer(server);
UA_StatusCode res = createEvent(server, ed, outEventId);
unlockServer(server);
return res;
}
#endif