| /* 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_backend_memory.h> |
| |
| #include <limits.h> |
| #include <string.h> |
| |
| typedef struct { |
| UA_DateTime timestamp; |
| UA_DataValue value; |
| } UA_DataValueMemoryStoreItem; |
| |
| static void |
| UA_DataValueMemoryStoreItem_deleteMembers(UA_DataValueMemoryStoreItem* item) { |
| UA_DateTime_deleteMembers(&item->timestamp); |
| UA_DataValue_deleteMembers(&item->value); |
| } |
| |
| typedef struct { |
| UA_NodeId nodeId; |
| UA_DataValueMemoryStoreItem **dataStore; |
| size_t storeEnd; |
| size_t storeSize; |
| } UA_NodeIdStoreContextItem_backend_memory; |
| |
| static void |
| UA_NodeIdStoreContextItem_deleteMembers(UA_NodeIdStoreContextItem_backend_memory* item) { |
| UA_NodeId_deleteMembers(&item->nodeId); |
| for (size_t i = 0; i < item->storeEnd; ++i) { |
| UA_DataValueMemoryStoreItem_deleteMembers(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_deleteMembers(UA_MemoryStoreContext* ctx) { |
| for (size_t i = 0; i < ctx->storeEnd; ++i) { |
| UA_NodeIdStoreContextItem_deleteMembers(&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_deleteMembers(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_deleteMembers(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_deleteMembers(&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_deleteMembers(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_deleteMembers((UA_MemoryStoreContext*)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_deleteMembers(UA_HistoryDataBackend *backend) |
| { |
| UA_MemoryStoreContext *ctx = (UA_MemoryStoreContext*)backend->context; |
| UA_MemoryStoreContext_delete(ctx); |
| memset(backend, 0, sizeof(UA_HistoryDataBackend)); |
| } |