blob: 090a72bb1780748da838dd2fbb4c1c2689a470af [file] [log] [blame]
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2018 (c) basysKom GmbH <opensource@basyskom.com> (Author: Peter Rustler)
*/
#include <open62541/plugin/historydata/history_data_gathering_default.h>
#include <open62541/plugin/historydata/history_database_default.h>
#include <limits.h>
typedef struct {
UA_HistoryDataGathering gathering;
} UA_HistoryDatabaseContext_default;
static size_t
getResultSize_service_default(const UA_HistoryDataBackend* backend,
UA_Server *server,
const UA_NodeId *sessionId,
void* sessionContext,
const UA_NodeId *nodeId,
UA_DateTime start,
UA_DateTime end,
UA_UInt32 numValuesPerNode,
UA_Boolean returnBounds,
size_t *startIndex,
size_t *endIndex,
UA_Boolean *addFirst,
UA_Boolean *addLast,
UA_Boolean *reverse)
{
size_t storeEnd = backend->getEnd(server, backend->context, sessionId, sessionContext, nodeId);
size_t firstIndex = backend->firstIndex(server, backend->context, sessionId, sessionContext, nodeId);
size_t lastIndex = backend->lastIndex(server, backend->context, sessionId, sessionContext, nodeId);
*startIndex = storeEnd;
*endIndex = storeEnd;
*addFirst = false;
*addLast = false;
if (end == LLONG_MIN) {
*reverse = false;
} else if (start == LLONG_MIN) {
*reverse = true;
} else {
*reverse = end < start;
}
UA_Boolean equal = start == end;
size_t size = 0;
if (lastIndex != storeEnd) {
if (equal) {
if (returnBounds) {
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_BEFORE);
if (*startIndex == storeEnd) {
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_AFTER);
*addFirst = true;
}
*endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_AFTER);
size = backend->resultSize(server, backend->context, sessionId, sessionContext, nodeId, *startIndex, *endIndex);
} else {
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL);
*endIndex = *startIndex;
if (*startIndex == storeEnd)
size = 0;
else
size = 1;
}
} else if (start == LLONG_MIN) {
*endIndex = firstIndex;
if (returnBounds) {
*addLast = true;
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_EQUAL_OR_AFTER);
if (*startIndex == storeEnd) {
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_EQUAL_OR_BEFORE);
*addFirst = true;
}
} else {
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_EQUAL_OR_BEFORE);
}
size = backend->resultSize(server, backend->context, sessionId, sessionContext, nodeId, *endIndex, *startIndex);
} else if (end == LLONG_MIN) {
*endIndex = lastIndex;
if (returnBounds) {
*addLast = true;
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_BEFORE);
if (*startIndex == storeEnd) {
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_AFTER);
*addFirst = true;
}
} else {
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_AFTER);
}
size = backend->resultSize(server, backend->context, sessionId, sessionContext, nodeId, *startIndex, *endIndex);
} else if (*reverse) {
if (returnBounds) {
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_AFTER);
if (*startIndex == storeEnd) {
*addFirst = true;
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_BEFORE);
}
*endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_EQUAL_OR_BEFORE);
if (*endIndex == storeEnd) {
*addLast = true;
*endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_AFTER);
}
} else {
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_BEFORE);
*endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_AFTER);
}
size = backend->resultSize(server, backend->context, sessionId, sessionContext, nodeId, *endIndex, *startIndex);
} else {
if (returnBounds) {
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_BEFORE);
if (*startIndex == storeEnd) {
*addFirst = true;
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_AFTER);
}
*endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_EQUAL_OR_AFTER);
if (*endIndex == storeEnd) {
*addLast = true;
*endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_BEFORE);
}
} else {
*startIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, start, MATCH_EQUAL_OR_AFTER);
*endIndex = backend->getDateTimeMatch(server, backend->context, sessionId, sessionContext, nodeId, end, MATCH_BEFORE);
}
size = backend->resultSize(server, backend->context, sessionId, sessionContext, nodeId, *startIndex, *endIndex);
}
} else if (returnBounds) {
*addLast = true;
*addFirst = true;
}
if (*addLast)
++size;
if (*addFirst)
++size;
if (numValuesPerNode > 0 && size > numValuesPerNode) {
size = numValuesPerNode;
*addLast = false;
}
return size;
}
static UA_StatusCode
getHistoryData_service_default(const UA_HistoryDataBackend* backend,
const UA_DateTime start,
const UA_DateTime end,
UA_Server *server,
const UA_NodeId *sessionId,
void *sessionContext,
const UA_NodeId* nodeId,
size_t maxSize,
UA_UInt32 numValuesPerNode,
UA_Boolean returnBounds,
UA_TimestampsToReturn timestampsToReturn,
UA_NumericRange range,
UA_Boolean releaseContinuationPoints,
const UA_ByteString *continuationPoint,
UA_ByteString *outContinuationPoint,
size_t *resultSize,
UA_DataValue ** result)
{
size_t skip = 0;
UA_ByteString backendContinuationPoint;
UA_ByteString_init(&backendContinuationPoint);
if (continuationPoint->length > 0) {
if (continuationPoint->length >= sizeof(size_t)) {
skip = *((size_t*)(continuationPoint->data));
if (continuationPoint->length > 0) {
backendContinuationPoint.length = continuationPoint->length - sizeof(size_t);
backendContinuationPoint.data = continuationPoint->data + sizeof(size_t);
}
} else {
return UA_STATUSCODE_BADCONTINUATIONPOINTINVALID;
}
}
size_t storeEnd = backend->getEnd(server, backend->context, sessionId, sessionContext, nodeId);
size_t startIndex;
size_t endIndex;
UA_Boolean addFirst;
UA_Boolean addLast;
UA_Boolean reverse;
size_t _resultSize = getResultSize_service_default(backend,
server,
sessionId,
sessionContext,
nodeId,
start,
end,
numValuesPerNode == 0 ? 0 : numValuesPerNode + (UA_UInt32)skip,
returnBounds,
&startIndex,
&endIndex,
&addFirst,
&addLast,
&reverse);
*resultSize = _resultSize - skip;
if (*resultSize > maxSize) {
*resultSize = maxSize;
}
UA_DataValue *outResult= (UA_DataValue*)UA_Array_new(*resultSize, &UA_TYPES[UA_TYPES_DATAVALUE]);
if (!outResult) {
*resultSize = 0;
return UA_STATUSCODE_BADOUTOFMEMORY;
}
*result = outResult;
size_t counter = 0;
if (addFirst) {
if (skip == 0) {
outResult[counter].hasStatus = true;
outResult[counter].status = UA_STATUSCODE_BADBOUNDNOTFOUND;
outResult[counter].hasSourceTimestamp = true;
if (start == LLONG_MIN) {
outResult[counter].sourceTimestamp = end;
} else {
outResult[counter].sourceTimestamp = start;
}
++counter;
}
}
UA_ByteString backendOutContinuationPoint;
UA_ByteString_init(&backendOutContinuationPoint);
if (endIndex != storeEnd && startIndex != storeEnd) {
size_t retval = 0;
size_t valueSize = *resultSize - counter;
if (valueSize + skip > _resultSize - addFirst - addLast) {
if (skip == 0) {
valueSize = _resultSize - addFirst - addLast;
} else {
valueSize = _resultSize - skip - addLast;
}
}
UA_StatusCode ret = UA_STATUSCODE_GOOD;
if (valueSize > 0)
ret = backend->copyDataValues(server,
backend->context,
sessionId,
sessionContext,
nodeId,
startIndex,
endIndex,
reverse,
valueSize,
range,
releaseContinuationPoints,
&backendContinuationPoint,
&backendOutContinuationPoint,
&retval,
&outResult[counter]);
if (ret != UA_STATUSCODE_GOOD) {
UA_Array_delete(outResult, *resultSize, &UA_TYPES[UA_TYPES_DATAVALUE]);
*result = NULL;
*resultSize = 0;
return ret;
}
counter += retval;
}
if (addLast && counter < *resultSize) {
outResult[counter].hasStatus = true;
outResult[counter].status = UA_STATUSCODE_BADBOUNDNOTFOUND;
outResult[counter].hasSourceTimestamp = true;
if (start == LLONG_MIN && storeEnd != backend->firstIndex(server, backend->context, sessionId, sessionContext, nodeId)) {
outResult[counter].sourceTimestamp = backend->getDataValue(server, backend->context, sessionId, sessionContext, nodeId, endIndex)->sourceTimestamp - UA_DATETIME_SEC;
} else if (end == LLONG_MIN && storeEnd != backend->firstIndex(server, backend->context, sessionId, sessionContext, nodeId)) {
outResult[counter].sourceTimestamp = backend->getDataValue(server, backend->context, sessionId, sessionContext, nodeId, endIndex)->sourceTimestamp + UA_DATETIME_SEC;
} else {
outResult[counter].sourceTimestamp = end;
}
}
// there are more values
if (skip + *resultSize < _resultSize
// there are not more values for this request, but there are more values in database
|| (backendOutContinuationPoint.length > 0
&& numValuesPerNode != 0)
// we deliver just one value which is a FIRST/LAST value
|| (skip == 0
&& addFirst == true
&& *resultSize == 1)) {
if(UA_ByteString_allocBuffer(outContinuationPoint, backendOutContinuationPoint.length + sizeof(size_t))
!= UA_STATUSCODE_GOOD) {
return UA_STATUSCODE_BADOUTOFMEMORY;
}
*((size_t*)(outContinuationPoint->data)) = skip + *resultSize;
if(backendOutContinuationPoint.length > 0)
memcpy(outContinuationPoint->data + sizeof(size_t), backendOutContinuationPoint.data, backendOutContinuationPoint.length);
}
UA_ByteString_deleteMembers(&backendOutContinuationPoint);
return UA_STATUSCODE_GOOD;
}
static void
updateData_service_default(UA_Server *server,
void *hdbContext,
const UA_NodeId *sessionId,
void *sessionContext,
const UA_RequestHeader *requestHeader,
const UA_UpdateDataDetails *details,
UA_HistoryUpdateResult *result)
{
UA_HistoryDatabaseContext_default *ctx = (UA_HistoryDatabaseContext_default*)hdbContext;
UA_Byte accessLevel = 0;
UA_Server_readAccessLevel(server,
details->nodeId,
&accessLevel);
if (!(accessLevel & UA_ACCESSLEVELMASK_HISTORYWRITE)) {
result->statusCode = UA_STATUSCODE_BADUSERACCESSDENIED;
return;
}
UA_Boolean historizing = false;
UA_Server_readHistorizing(server,
details->nodeId,
&historizing);
if (!historizing) {
result->statusCode = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
return;
}
const UA_HistorizingNodeIdSettings *setting = ctx->gathering.getHistorizingSetting(
server,
ctx->gathering.context,
&details->nodeId);
if (!setting) {
result->statusCode = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
return;
}
result->operationResultsSize = details->updateValuesSize;
result->operationResults = (UA_StatusCode*)UA_Array_new(result->operationResultsSize, &UA_TYPES[UA_TYPES_STATUSCODE]);
for (size_t i = 0; i < details->updateValuesSize; ++i) {
if (!UA_Server_AccessControl_allowHistoryUpdateUpdateData(server,
sessionId,
sessionContext,
&details->nodeId,
details->performInsertReplace,
&details->updateValues[i])) {
result->operationResults[i] = UA_STATUSCODE_BADUSERACCESSDENIED;
continue;
}
switch (details->performInsertReplace) {
case UA_PERFORMUPDATETYPE_INSERT:
if (!setting->historizingBackend.insertDataValue) {
result->operationResults[i] = UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED;
continue;
}
result->operationResults[i]
= setting->historizingBackend.insertDataValue(server,
setting->historizingBackend.context,
sessionId,
sessionContext,
&details->nodeId,
&details->updateValues[i]);
continue;
case UA_PERFORMUPDATETYPE_REPLACE:
if (!setting->historizingBackend.replaceDataValue) {
result->operationResults[i] = UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED;
continue;
}
result->operationResults[i]
= setting->historizingBackend.replaceDataValue(server,
setting->historizingBackend.context,
sessionId,
sessionContext,
&details->nodeId,
&details->updateValues[i]);
continue;
case UA_PERFORMUPDATETYPE_UPDATE:
if (!setting->historizingBackend.updateDataValue) {
result->operationResults[i] = UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED;
continue;
}
result->operationResults[i]
= setting->historizingBackend.updateDataValue(server,
setting->historizingBackend.context,
sessionId,
sessionContext,
&details->nodeId,
&details->updateValues[i]);
continue;
default:
result->operationResults[i] = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
continue;
}
}
}
static void
deleteRawModified_service_default(UA_Server *server,
void *hdbContext,
const UA_NodeId *sessionId,
void *sessionContext,
const UA_RequestHeader *requestHeader,
const UA_DeleteRawModifiedDetails *details,
UA_HistoryUpdateResult *result)
{
if (details->isDeleteModified) {
result->statusCode = UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED;
return;
}
UA_HistoryDatabaseContext_default *ctx = (UA_HistoryDatabaseContext_default*)hdbContext;
UA_Byte accessLevel = 0;
UA_Server_readAccessLevel(server,
details->nodeId,
&accessLevel);
if (!(accessLevel & UA_ACCESSLEVELMASK_HISTORYWRITE)) {
result->statusCode = UA_STATUSCODE_BADUSERACCESSDENIED;
return;
}
UA_Boolean historizing = false;
UA_Server_readHistorizing(server,
details->nodeId,
&historizing);
if (!historizing) {
result->statusCode = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
return;
}
const UA_HistorizingNodeIdSettings *setting = ctx->gathering.getHistorizingSetting(
server,
ctx->gathering.context,
&details->nodeId);
if (!setting) {
result->statusCode = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
return;
}
if (!setting->historizingBackend.removeDataValue) {
result->statusCode = UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED;
return;
}
if (!UA_Server_AccessControl_allowHistoryUpdateDeleteRawModified(server,
sessionId,
sessionContext,
&details->nodeId,
details->startTime,
details->endTime,
details->isDeleteModified)) {
result->statusCode = UA_STATUSCODE_BADUSERACCESSDENIED;
return;
}
result->statusCode
= setting->historizingBackend.removeDataValue(server,
setting->historizingBackend.context,
sessionId,
sessionContext,
&details->nodeId,
details->startTime,
details->endTime);
}
static void
readRaw_service_default(UA_Server *server,
void *context,
const UA_NodeId *sessionId,
void *sessionContext,
const UA_RequestHeader *requestHeader,
const UA_ReadRawModifiedDetails *historyReadDetails,
UA_TimestampsToReturn timestampsToReturn,
UA_Boolean releaseContinuationPoints,
size_t nodesToReadSize,
const UA_HistoryReadValueId *nodesToRead,
UA_HistoryReadResponse *response,
UA_HistoryData * const * const historyData)
{
UA_HistoryDatabaseContext_default *ctx = (UA_HistoryDatabaseContext_default*)context;
for (size_t i = 0; i < nodesToReadSize; ++i) {
UA_Byte accessLevel = 0;
UA_Server_readAccessLevel(server,
nodesToRead[i].nodeId,
&accessLevel);
if (!(accessLevel & UA_ACCESSLEVELMASK_HISTORYREAD)) {
response->results[i].statusCode = UA_STATUSCODE_BADUSERACCESSDENIED;
continue;
}
UA_Boolean historizing = false;
UA_Server_readHistorizing(server,
nodesToRead[i].nodeId,
&historizing);
if (!historizing) {
response->results[i].statusCode = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
continue;
}
const UA_HistorizingNodeIdSettings *setting = ctx->gathering.getHistorizingSetting(
server,
ctx->gathering.context,
&nodesToRead[i].nodeId);
if (!setting) {
response->results[i].statusCode = UA_STATUSCODE_BADHISTORYOPERATIONINVALID;
continue;
}
if (historyReadDetails->returnBounds && !setting->historizingBackend.boundSupported(
server,
setting->historizingBackend.context,
sessionId,
sessionContext,
&nodesToRead[i].nodeId)) {
response->results[i].statusCode = UA_STATUSCODE_BADBOUNDNOTSUPPORTED;
continue;
}
if (!setting->historizingBackend.timestampsToReturnSupported(
server,
setting->historizingBackend.context,
sessionId,
sessionContext,
&nodesToRead[i].nodeId,
timestampsToReturn)) {
response->results[i].statusCode = UA_STATUSCODE_BADTIMESTAMPNOTSUPPORTED;
continue;
}
UA_NumericRange range;
range.dimensionsSize = 0;
range.dimensions = NULL;
if (nodesToRead[i].indexRange.length > 0) {
UA_StatusCode rangeParseResult = UA_NumericRange_parseFromString(&range, &nodesToRead[i].indexRange);
if (rangeParseResult != UA_STATUSCODE_GOOD) {
response->results[i].statusCode = rangeParseResult;
continue;
}
}
UA_StatusCode getHistoryDataStatusCode;
if (setting->historizingBackend.getHistoryData) {
getHistoryDataStatusCode = setting->historizingBackend.getHistoryData(
server,
sessionId,
sessionContext,
&setting->historizingBackend,
historyReadDetails->startTime,
historyReadDetails->endTime,
&nodesToRead[i].nodeId,
setting->maxHistoryDataResponseSize,
historyReadDetails->numValuesPerNode,
historyReadDetails->returnBounds,
timestampsToReturn,
range,
releaseContinuationPoints,
&nodesToRead[i].continuationPoint,
&response->results[i].continuationPoint,
historyData[i]);
} else {
getHistoryDataStatusCode = getHistoryData_service_default(
&setting->historizingBackend,
historyReadDetails->startTime,
historyReadDetails->endTime,
server,
sessionId,
sessionContext,
&nodesToRead[i].nodeId,
setting->maxHistoryDataResponseSize,
historyReadDetails->numValuesPerNode,
historyReadDetails->returnBounds,
timestampsToReturn,
range,
releaseContinuationPoints,
&nodesToRead[i].continuationPoint,
&response->results[i].continuationPoint,
&historyData[i]->dataValuesSize,
&historyData[i]->dataValues);
}
if (getHistoryDataStatusCode != UA_STATUSCODE_GOOD) {
response->results[i].statusCode = getHistoryDataStatusCode;
continue;
}
}
response->responseHeader.serviceResult = UA_STATUSCODE_GOOD;
return;
}
static void
setValue_service_default(UA_Server *server,
void *context,
const UA_NodeId *sessionId,
void *sessionContext,
const UA_NodeId *nodeId,
UA_Boolean historizing,
const UA_DataValue *value)
{
UA_HistoryDatabaseContext_default *ctx = (UA_HistoryDatabaseContext_default*)context;
if (ctx->gathering.setValue)
ctx->gathering.setValue(server,
ctx->gathering.context,
sessionId,
sessionContext,
nodeId,
historizing,
value);
}
static void
deleteMembers_service_default(UA_HistoryDatabase *hdb)
{
if (hdb == NULL || hdb->context == NULL)
return;
UA_HistoryDatabaseContext_default *ctx = (UA_HistoryDatabaseContext_default*)hdb->context;
ctx->gathering.deleteMembers(&ctx->gathering);
UA_free(ctx);
}
UA_HistoryDatabase
UA_HistoryDatabase_default(UA_HistoryDataGathering gathering)
{
UA_HistoryDatabase hdb;
UA_HistoryDatabaseContext_default *context =
(UA_HistoryDatabaseContext_default*)
UA_calloc(1, sizeof(UA_HistoryDatabaseContext_default));
context->gathering = gathering;
hdb.context = context;
hdb.readRaw = &readRaw_service_default;
hdb.setValue = &setValue_service_default;
hdb.updateData = &updateData_service_default;
hdb.deleteRawModified = &deleteRawModified_service_default;
hdb.deleteMembers = &deleteMembers_service_default;
return hdb;
}