/* 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 (Author: Peter Rustler) */ #include #include #include typedef struct { UA_DateTime timestamp; UA_DataValue value; } UA_DataValueMemoryStoreItem; static void UA_DataValueMemoryStoreItem_clear(UA_DataValueMemoryStoreItem* item) { UA_DateTime_clear(&item->timestamp); UA_DataValue_clear(&item->value); } typedef struct { UA_NodeId nodeId; UA_DataValueMemoryStoreItem **dataStore; size_t storeEnd; size_t storeSize; } UA_NodeIdStoreContextItem_backend_memory; static void UA_NodeIdStoreContextItem_clear(UA_NodeIdStoreContextItem_backend_memory* item) { UA_NodeId_clear(&item->nodeId); for (size_t i = 0; i < item->storeEnd; ++i) { UA_DataValueMemoryStoreItem_clear(item->dataStore[i]); UA_free(item->dataStore[i]); } UA_free(item->dataStore); } typedef struct { UA_NodeIdStoreContextItem_backend_memory *dataStore; size_t storeEnd; size_t storeSize; size_t initialStoreSize; } UA_MemoryStoreContext; static void UA_MemoryStoreContext_clear(UA_MemoryStoreContext* ctx) { for (size_t i = 0; i < ctx->storeEnd; ++i) { UA_NodeIdStoreContextItem_clear(&ctx->dataStore[i]); } UA_free(ctx->dataStore); memset(ctx, 0, sizeof(UA_MemoryStoreContext)); } static UA_NodeIdStoreContextItem_backend_memory * getNewNodeIdContext_backend_memory(UA_MemoryStoreContext* context, UA_Server *server, const UA_NodeId *nodeId) { UA_MemoryStoreContext *ctx = (UA_MemoryStoreContext*)context; if (ctx->storeEnd >= ctx->storeSize) { size_t newStoreSize = ctx->storeSize * 2; if (newStoreSize == 0) return NULL; ctx->dataStore = (UA_NodeIdStoreContextItem_backend_memory*)UA_realloc(ctx->dataStore, (newStoreSize * sizeof(UA_NodeIdStoreContextItem_backend_memory))); if (!ctx->dataStore) { ctx->storeSize = 0; return NULL; } ctx->storeSize = newStoreSize; } UA_NodeIdStoreContextItem_backend_memory *item = &ctx->dataStore[ctx->storeEnd]; UA_NodeId_copy(nodeId, &item->nodeId); UA_DataValueMemoryStoreItem ** store = (UA_DataValueMemoryStoreItem **)UA_calloc(ctx->initialStoreSize, sizeof(UA_DataValueMemoryStoreItem*)); if (!store) { UA_NodeIdStoreContextItem_clear(item); return NULL; } item->dataStore = store; item->storeSize = ctx->initialStoreSize; item->storeEnd = 0; ++ctx->storeEnd; return item; } static UA_NodeIdStoreContextItem_backend_memory * getNodeIdStoreContextItem_backend_memory(UA_MemoryStoreContext* context, UA_Server *server, const UA_NodeId *nodeId) { for (size_t i = 0; i < context->storeEnd; ++i) { if (UA_NodeId_equal(nodeId, &context->dataStore[i].nodeId)) { return &context->dataStore[i]; } } return getNewNodeIdContext_backend_memory(context, server, nodeId); } static UA_Boolean binarySearch_backend_memory(const UA_NodeIdStoreContextItem_backend_memory* item, const UA_DateTime timestamp, size_t *index) { if (item->storeEnd == 0) { *index = item->storeEnd; return false; } size_t min = 0; size_t max = item->storeEnd - 1; while (min <= max) { *index = (min + max) / 2; if (item->dataStore[*index]->timestamp == timestamp) { return true; } else if (item->dataStore[*index]->timestamp < timestamp) { if (*index == item->storeEnd - 1) { *index = item->storeEnd; return false; } min = *index + 1; } else { if (*index == 0) return false; max = *index - 1; } } *index = min; return false; } static size_t resultSize_backend_memory(UA_Server *server, void *context, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId * nodeId, size_t startIndex, size_t endIndex) { const UA_NodeIdStoreContextItem_backend_memory* item = getNodeIdStoreContextItem_backend_memory((UA_MemoryStoreContext*)context, server, nodeId); if (item->storeEnd == 0 || startIndex == item->storeEnd || endIndex == item->storeEnd) return 0; return endIndex - startIndex + 1; } static size_t getDateTimeMatch_backend_memory(UA_Server *server, void *context, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId * nodeId, const UA_DateTime timestamp, const MatchStrategy strategy) { const UA_NodeIdStoreContextItem_backend_memory* item = getNodeIdStoreContextItem_backend_memory((UA_MemoryStoreContext*)context, server, nodeId); size_t current; UA_Boolean retval = binarySearch_backend_memory(item, timestamp, ¤t); if ((strategy == MATCH_EQUAL || strategy == MATCH_EQUAL_OR_AFTER || strategy == MATCH_EQUAL_OR_BEFORE) && retval) return current; switch (strategy) { case MATCH_AFTER: if (retval) return current+1; return current; case MATCH_EQUAL_OR_AFTER: return current; case MATCH_EQUAL_OR_BEFORE: // retval == true aka "equal" is handled before // Fall through if !retval case MATCH_BEFORE: if (current > 0) return current-1; else return item->storeEnd; default: break; } return item->storeEnd; } static UA_StatusCode serverSetHistoryData_backend_memory(UA_Server *server, void *context, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId * nodeId, UA_Boolean historizing, const UA_DataValue *value) { UA_NodeIdStoreContextItem_backend_memory *item = getNodeIdStoreContextItem_backend_memory((UA_MemoryStoreContext*)context, server, nodeId); if (item->storeEnd >= item->storeSize) { size_t newStoreSize = item->storeSize == 0 ? INITIAL_MEMORY_STORE_SIZE : item->storeSize * 2; item->dataStore = (UA_DataValueMemoryStoreItem **)UA_realloc(item->dataStore, (newStoreSize * sizeof(UA_DataValueMemoryStoreItem*))); if (!item->dataStore) { item->storeSize = 0; return UA_STATUSCODE_BADOUTOFMEMORY; } item->storeSize = newStoreSize; } UA_DateTime timestamp = 0; if (value->hasSourceTimestamp) { timestamp = value->sourceTimestamp; } else if (value->hasServerTimestamp) { timestamp = value->serverTimestamp; } else { timestamp = UA_DateTime_now(); } UA_DataValueMemoryStoreItem *newItem = (UA_DataValueMemoryStoreItem *)UA_calloc(1, sizeof(UA_DataValueMemoryStoreItem)); newItem->timestamp = timestamp; UA_DataValue_copy(value, &newItem->value); size_t index = getDateTimeMatch_backend_memory(server, context, NULL, NULL, nodeId, timestamp, MATCH_EQUAL_OR_AFTER); if (item->storeEnd > 0 && index < item->storeEnd) { memmove(&item->dataStore[index+1], &item->dataStore[index], sizeof(UA_DataValueMemoryStoreItem*) * (item->storeEnd - index)); } item->dataStore[index] = newItem; ++item->storeEnd; return UA_STATUSCODE_GOOD; } static void UA_MemoryStoreContext_delete(UA_MemoryStoreContext* ctx) { UA_MemoryStoreContext_clear(ctx); UA_free(ctx); } static size_t getEnd_backend_memory(UA_Server *server, void *context, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId * nodeId) { const UA_NodeIdStoreContextItem_backend_memory* item = getNodeIdStoreContextItem_backend_memory((UA_MemoryStoreContext*)context, server, nodeId); return item->storeEnd; } static size_t lastIndex_backend_memory(UA_Server *server, void *context, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId * nodeId) { const UA_NodeIdStoreContextItem_backend_memory* item = getNodeIdStoreContextItem_backend_memory((UA_MemoryStoreContext*)context, server, nodeId); if (item->storeEnd == 0) return 0; return item->storeEnd - 1; } static size_t firstIndex_backend_memory(UA_Server *server, void *context, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId * nodeId) { return 0; } static UA_Boolean boundSupported_backend_memory(UA_Server *server, void *context, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId * nodeId) { return true; } static UA_Boolean timestampsToReturnSupported_backend_memory(UA_Server *server, void *context, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, const UA_TimestampsToReturn timestampsToReturn) { const UA_NodeIdStoreContextItem_backend_memory* item = getNodeIdStoreContextItem_backend_memory((UA_MemoryStoreContext*)context, server, nodeId); if (item->storeEnd == 0) { return true; } if (timestampsToReturn == UA_TIMESTAMPSTORETURN_NEITHER || timestampsToReturn == UA_TIMESTAMPSTORETURN_INVALID || (timestampsToReturn == UA_TIMESTAMPSTORETURN_SERVER && !item->dataStore[0]->value.hasServerTimestamp) || (timestampsToReturn == UA_TIMESTAMPSTORETURN_SOURCE && !item->dataStore[0]->value.hasSourceTimestamp) || (timestampsToReturn == UA_TIMESTAMPSTORETURN_BOTH && !(item->dataStore[0]->value.hasSourceTimestamp && item->dataStore[0]->value.hasServerTimestamp))) { return false; } return true; } static const UA_DataValue* getDataValue_backend_memory(UA_Server *server, void *context, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId * nodeId, size_t index) { const UA_NodeIdStoreContextItem_backend_memory* item = getNodeIdStoreContextItem_backend_memory((UA_MemoryStoreContext*)context, server, nodeId); return &item->dataStore[index]->value; } static UA_StatusCode UA_DataValue_backend_copyRange(const UA_DataValue *src, UA_DataValue *dst, const UA_NumericRange range) { memcpy(dst, src, sizeof(UA_DataValue)); if (src->hasValue) return UA_Variant_copyRange(&src->value, &dst->value, range); return UA_STATUSCODE_BADDATAUNAVAILABLE; } static UA_StatusCode copyDataValues_backend_memory(UA_Server *server, void *context, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId * nodeId, size_t startIndex, size_t endIndex, UA_Boolean reverse, size_t maxValues, UA_NumericRange range, UA_Boolean releaseContinuationPoints, const UA_ByteString *continuationPoint, UA_ByteString *outContinuationPoint, size_t * providedValues, UA_DataValue * values) { size_t skip = 0; if (continuationPoint->length > 0) { if (continuationPoint->length == sizeof(size_t)) { skip = *((size_t*)(continuationPoint->data)); } else { return UA_STATUSCODE_BADCONTINUATIONPOINTINVALID; } } const UA_NodeIdStoreContextItem_backend_memory* item = getNodeIdStoreContextItem_backend_memory((UA_MemoryStoreContext*)context, server, nodeId); size_t index = startIndex; size_t counter = 0; size_t skipedValues = 0; if (reverse) { while (index >= endIndex && index < item->storeEnd && counter < maxValues) { if (skipedValues++ >= skip) { if (range.dimensionsSize > 0) { UA_DataValue_backend_copyRange(&item->dataStore[index]->value, &values[counter], range); } else { UA_DataValue_copy(&item->dataStore[index]->value, &values[counter]); } ++counter; } --index; } } else { while (index <= endIndex && counter < maxValues) { if (skipedValues++ >= skip) { if (range.dimensionsSize > 0) { UA_DataValue_backend_copyRange(&item->dataStore[index]->value, &values[counter], range); } else { UA_DataValue_copy(&item->dataStore[index]->value, &values[counter]); } ++counter; } ++index; } } if (providedValues) *providedValues = counter; if ((!reverse && (endIndex-startIndex-skip+1) > counter) || (reverse && (startIndex-endIndex-skip+1) > counter)) { outContinuationPoint->length = sizeof(size_t); size_t t = sizeof(size_t); outContinuationPoint->data = (UA_Byte*)UA_malloc(t); *((size_t*)(outContinuationPoint->data)) = skip + counter; } return UA_STATUSCODE_GOOD; } static UA_StatusCode insertDataValue_backend_memory(UA_Server *server, void *hdbContext, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, const UA_DataValue *value) { if (!value->hasSourceTimestamp && !value->hasServerTimestamp) return UA_STATUSCODE_BADINVALIDTIMESTAMP; const UA_DateTime timestamp = value->hasSourceTimestamp ? value->sourceTimestamp : value->serverTimestamp; UA_NodeIdStoreContextItem_backend_memory* item = getNodeIdStoreContextItem_backend_memory((UA_MemoryStoreContext*)hdbContext, server, nodeId); size_t index = getDateTimeMatch_backend_memory(server, hdbContext, sessionId, sessionContext, nodeId, timestamp, MATCH_EQUAL_OR_AFTER); if (item->storeEnd != index && item->dataStore[index]->timestamp == timestamp) return UA_STATUSCODE_BADENTRYEXISTS; if (item->storeEnd >= item->storeSize) { size_t newStoreSize = item->storeSize == 0 ? INITIAL_MEMORY_STORE_SIZE : item->storeSize * 2; item->dataStore = (UA_DataValueMemoryStoreItem **)UA_realloc(item->dataStore, (newStoreSize * sizeof(UA_DataValueMemoryStoreItem*))); if (!item->dataStore) { item->storeSize = 0; return UA_STATUSCODE_BADOUTOFMEMORY; } item->storeSize = newStoreSize; } UA_DataValueMemoryStoreItem *newItem = (UA_DataValueMemoryStoreItem *)UA_calloc(1, sizeof(UA_DataValueMemoryStoreItem)); newItem->timestamp = timestamp; UA_DataValue_copy(value, &newItem->value); if (item->storeEnd > 0 && index < item->storeEnd) { memmove(&item->dataStore[index+1], &item->dataStore[index], sizeof(UA_DataValueMemoryStoreItem*) * (item->storeEnd - index)); } item->dataStore[index] = newItem; ++item->storeEnd; return UA_STATUSCODE_GOOD; } static UA_StatusCode replaceDataValue_backend_memory(UA_Server *server, void *hdbContext, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, const UA_DataValue *value) { if (!value->hasSourceTimestamp && !value->hasServerTimestamp) return UA_STATUSCODE_BADINVALIDTIMESTAMP; const UA_DateTime timestamp = value->hasSourceTimestamp ? value->sourceTimestamp : value->serverTimestamp; UA_NodeIdStoreContextItem_backend_memory* item = getNodeIdStoreContextItem_backend_memory((UA_MemoryStoreContext*)hdbContext, server, nodeId); size_t index = getDateTimeMatch_backend_memory(server, hdbContext, sessionId, sessionContext, nodeId, timestamp, MATCH_EQUAL); if (index == item->storeEnd) return UA_STATUSCODE_BADNOENTRYEXISTS; UA_DataValue_clear(&item->dataStore[index]->value); UA_DataValue_copy(value, &item->dataStore[index]->value); return UA_STATUSCODE_GOOD; } static UA_StatusCode updateDataValue_backend_memory(UA_Server *server, void *hdbContext, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, const UA_DataValue *value) { // we first try to replace, because it is cheap UA_StatusCode ret = replaceDataValue_backend_memory(server, hdbContext, sessionId, sessionContext, nodeId, value); if (ret == UA_STATUSCODE_GOOD) return UA_STATUSCODE_GOODENTRYREPLACED; ret = insertDataValue_backend_memory(server, hdbContext, sessionId, sessionContext, nodeId, value); if (ret == UA_STATUSCODE_GOOD) return UA_STATUSCODE_GOODENTRYINSERTED; return ret; } static UA_StatusCode removeDataValue_backend_memory(UA_Server *server, void *hdbContext, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, UA_DateTime startTimestamp, UA_DateTime endTimestamp) { UA_NodeIdStoreContextItem_backend_memory* item = getNodeIdStoreContextItem_backend_memory((UA_MemoryStoreContext*)hdbContext, server, nodeId); size_t storeEnd = item->storeEnd; // The first index which will be deleted size_t index1; // the first index which is not deleted size_t index2; if (startTimestamp > endTimestamp) { return UA_STATUSCODE_BADTIMESTAMPNOTSUPPORTED; } if (startTimestamp == endTimestamp) { index1 = getDateTimeMatch_backend_memory(server, hdbContext, sessionId, sessionContext, nodeId, startTimestamp, MATCH_EQUAL); if (index1 == storeEnd) return UA_STATUSCODE_BADNODATA; index2 = index1 + 1; } else { index1 = getDateTimeMatch_backend_memory(server, hdbContext, sessionId, sessionContext, nodeId, startTimestamp, MATCH_EQUAL_OR_AFTER); index2 = getDateTimeMatch_backend_memory(server, hdbContext, sessionId, sessionContext, nodeId, endTimestamp, MATCH_BEFORE); if (index2 == storeEnd || index1 == storeEnd || index1 > index2 ) return UA_STATUSCODE_BADNODATA; ++index2; } #ifndef __clang_analyzer__ for (size_t i = index1; i < index2; ++i) { UA_DataValueMemoryStoreItem_clear(item->dataStore[i]); UA_free(item->dataStore[i]); } memmove(&item->dataStore[index1], &item->dataStore[index2], sizeof(UA_DataValueMemoryStoreItem*) * (item->storeEnd - index2)); item->storeEnd -= index2 - index1; #else (void)index1; (void)index2; #endif return UA_STATUSCODE_GOOD; } static void deleteMembers_backend_memory(UA_HistoryDataBackend *backend) { if (backend == NULL || backend->context == NULL) return; UA_MemoryStoreContext_clear((UA_MemoryStoreContext*)backend->context); UA_free(backend->context); } UA_HistoryDataBackend UA_HistoryDataBackend_Memory(size_t initialNodeIdStoreSize, size_t initialDataStoreSize) { if (initialNodeIdStoreSize == 0) initialNodeIdStoreSize = 1; if (initialDataStoreSize == 0) initialDataStoreSize = 1; UA_HistoryDataBackend result; memset(&result, 0, sizeof(UA_HistoryDataBackend)); UA_MemoryStoreContext *ctx = (UA_MemoryStoreContext *)UA_calloc(1, sizeof(UA_MemoryStoreContext)); if (!ctx) return result; ctx->dataStore = (UA_NodeIdStoreContextItem_backend_memory*)UA_calloc(initialNodeIdStoreSize, sizeof(UA_NodeIdStoreContextItem_backend_memory)); ctx->initialStoreSize = initialDataStoreSize; ctx->storeSize = initialNodeIdStoreSize; ctx->storeEnd = 0; result.serverSetHistoryData = &serverSetHistoryData_backend_memory; result.resultSize = &resultSize_backend_memory; result.getEnd = &getEnd_backend_memory; result.lastIndex = &lastIndex_backend_memory; result.firstIndex = &firstIndex_backend_memory; result.getDateTimeMatch = &getDateTimeMatch_backend_memory; result.copyDataValues = ©DataValues_backend_memory; result.getDataValue = &getDataValue_backend_memory; result.boundSupported = &boundSupported_backend_memory; result.timestampsToReturnSupported = ×tampsToReturnSupported_backend_memory; result.insertDataValue = &insertDataValue_backend_memory; result.updateDataValue = &updateDataValue_backend_memory; result.replaceDataValue = &replaceDataValue_backend_memory; result.removeDataValue = &removeDataValue_backend_memory; result.deleteMembers = &deleteMembers_backend_memory; result.getHistoryData = NULL; result.context = ctx; return result; } void UA_HistoryDataBackend_Memory_clear(UA_HistoryDataBackend *backend) { UA_MemoryStoreContext *ctx = (UA_MemoryStoreContext*)backend->context; UA_MemoryStoreContext_delete(ctx); memset(backend, 0, sizeof(UA_HistoryDataBackend)); }