ソースを参照

server_historical_update: Add api to alter raw data.

Peter Rustler 5 年 前
コミット
0d939f4762

+ 18 - 0
include/ua_plugin_historydatabase.h

@@ -73,6 +73,24 @@ struct UA_HistoryDatabase {
                UA_HistoryReadResponse *response,
                UA_HistoryData * const * const historyData);
 
+    void
+    (*updateData)(UA_Server *server,
+                  void *hdbContext,
+                  const UA_NodeId *sessionId,
+                  void *sessionContext,
+                  const UA_RequestHeader *requestHeader,
+                  const UA_UpdateDataDetails *details,
+                  UA_HistoryUpdateResult *result);
+
+    void
+    (*deleteRawModified)(UA_Server *server,
+                         void *hdbContext,
+                         const UA_NodeId *sessionId,
+                         void *sessionContext,
+                         const UA_RequestHeader *requestHeader,
+                         const UA_DeleteRawModifiedDetails *details,
+                         UA_HistoryUpdateResult *result);
+
     /* Add more function pointer here.
      * For example for read_event, read_modified, read_processed, read_at_time */
 };

+ 167 - 1
plugins/historydata/ua_historydatabackend_memory.c

@@ -385,6 +385,168 @@ copyDataValues_backend_memory(UA_Server *server,
     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)
 {
@@ -420,7 +582,11 @@ UA_HistoryDataBackend_Memory(size_t initialNodeIdStoreSize, size_t initialDataSt
     result.getDataValue = &getDataValue_backend_memory;
     result.boundSupported = &boundSupported_backend_memory;
     result.timestampsToReturnSupported = &timestampsToReturnSupported_backend_memory;
-    result.deleteMembers = deleteMembers_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;

+ 144 - 0
plugins/historydata/ua_historydatabase_default.c

@@ -289,6 +289,148 @@ getHistoryData_service_default(const UA_HistoryDataBackend* backend,
     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) {
+        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;
+    }
+    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,
@@ -454,6 +596,8 @@ UA_HistoryDatabase_default(UA_HistoryDataGathering 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;
 }

+ 30 - 0
plugins/historydata/ua_plugin_history_data_backend.h

@@ -255,6 +255,36 @@ struct UA_HistoryDataBackend {
                                    void *sessionContext,
                                    const UA_NodeId *nodeId,
                                    const UA_TimestampsToReturn timestampsToReturn);
+
+    UA_StatusCode
+    (*insertDataValue)(UA_Server *server,
+                       void *hdbContext,
+                       const UA_NodeId *sessionId,
+                       void *sessionContext,
+                       const UA_NodeId *nodeId,
+                       const UA_DataValue *value);
+    UA_StatusCode
+    (*replaceDataValue)(UA_Server *server,
+                        void *hdbContext,
+                        const UA_NodeId *sessionId,
+                        void *sessionContext,
+                        const UA_NodeId *nodeId,
+                        const UA_DataValue *value);
+    UA_StatusCode
+    (*updateDataValue)(UA_Server *server,
+                       void *hdbContext,
+                       const UA_NodeId *sessionId,
+                       void *sessionContext,
+                       const UA_NodeId *nodeId,
+                       const UA_DataValue *value);
+    UA_StatusCode
+    (*removeDataValue)(UA_Server *server,
+                       void *hdbContext,
+                       const UA_NodeId *sessionId,
+                       void *sessionContext,
+                       const UA_NodeId *nodeId,
+                       UA_DateTime startTimestamp,
+                       UA_DateTime endTimestamp);
 };
 
 _UA_END_DECLS

+ 6 - 0
src/server/ua_server_binary.c

@@ -226,6 +226,12 @@ getServicePointers(UA_UInt32 requestTypeId, const UA_DataType **requestType,
         *requestType = &UA_TYPES[UA_TYPES_HISTORYREADREQUEST];
         *responseType = &UA_TYPES[UA_TYPES_HISTORYREADRESPONSE];
         break;
+        /* For History update */
+    case UA_NS0ID_HISTORYUPDATEREQUEST_ENCODING_DEFAULTBINARY:
+        *service = (UA_Service)Service_HistoryUpdate;
+        *requestType = &UA_TYPES[UA_TYPES_HISTORYUPDATEREQUEST];
+        *responseType = &UA_TYPES[UA_TYPES_HISTORYUPDATERESPONSE];
+        break;
 #endif
 
 #ifdef UA_ENABLE_METHODCALLS

+ 4 - 1
src/server/ua_services.h

@@ -336,7 +336,10 @@ void Service_HistoryRead(UA_Server *server, UA_Session *session,
  * Used to update historical values or Events of one or more Nodes. Several
  * request parameters indicate how the Server is to update the historical value
  * or Event. Valid actions are Insert, Replace or Delete. */
-/* Not Implemented */
+void
+Service_HistoryUpdate(UA_Server *server, UA_Session *session,
+                      const UA_HistoryUpdateRequest *request,
+                      UA_HistoryUpdateResponse *response);
 
 /**
  * .. _method-services:

+ 53 - 0
src/server/ua_services_attribute.c

@@ -1514,6 +1514,59 @@ Service_HistoryRead(UA_Server *server, UA_Session *session,
                                            response, historyData);
     UA_free(historyData);
 }
+
+void
+Service_HistoryUpdate(UA_Server *server, UA_Session *session,
+                    const UA_HistoryUpdateRequest *request,
+                    UA_HistoryUpdateResponse *response) {
+    response->resultsSize = request->historyUpdateDetailsSize;
+    response->results = (UA_HistoryUpdateResult*)UA_Array_new(response->resultsSize, &UA_TYPES[UA_TYPES_HISTORYUPDATERESULT]);
+    if (!response->results) {
+        response->resultsSize = 0;
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
+        return;
+    }
+    for (size_t i = 0; i < request->historyUpdateDetailsSize; ++i) {
+        UA_HistoryUpdateResult_init(&response->results[i]);
+        if(request->historyUpdateDetails[i].encoding != UA_EXTENSIONOBJECT_DECODED) {
+            response->results[i].statusCode = UA_STATUSCODE_BADNOTSUPPORTED;
+            continue;
+        }
+        if (request->historyUpdateDetails[i].content.decoded.type
+                == &UA_TYPES[UA_TYPES_UPDATEDATADETAILS]) {
+            if (server->config.historyDatabase.updateData) {
+                server->config.historyDatabase.updateData(server,
+                                                          server->config.historyDatabase.context,
+                                                          &session->sessionId, session->sessionHandle,
+                                                          &request->requestHeader,
+                                                          (UA_UpdateDataDetails*)request->historyUpdateDetails[i].content.decoded.data,
+                                                          &response->results[i]);
+            } else {
+                response->results[i].statusCode = UA_STATUSCODE_BADNOTSUPPORTED;
+            }
+            continue;
+        } else
+        if (request->historyUpdateDetails[i].content.decoded.type
+                == &UA_TYPES[UA_TYPES_DELETERAWMODIFIEDDETAILS]) {
+            if (server->config.historyDatabase.deleteRawModified) {
+                server->config.historyDatabase.deleteRawModified(server,
+                                                                 server->config.historyDatabase.context,
+                                                                 &session->sessionId, session->sessionHandle,
+                                                                 &request->requestHeader,
+                                                                 (UA_DeleteRawModifiedDetails*)request->historyUpdateDetails[i].content.decoded.data,
+                                                                 &response->results[i]);
+            } else {
+                response->results[i].statusCode = UA_STATUSCODE_BADNOTSUPPORTED;
+            }
+            continue;
+        } else {
+            response->results[i].statusCode = UA_STATUSCODE_BADNOTSUPPORTED;
+            continue;
+        }
+    }
+    response->responseHeader.serviceResult = UA_STATUSCODE_GOOD;
+}
+
 #endif
 
 UA_StatusCode UA_EXPORT

+ 6 - 0
tools/schema/datatypes_historizing.txt

@@ -12,3 +12,9 @@ HistoryUpdateType
 ModificationInfo
 HistoryEvent
 HistoryEventFieldList
+HistoryUpdateRequest
+HistoryUpdateResponse
+HistoryUpdateResult
+UpdateDataDetails
+DeleteRawModifiedDetails
+PerformUpdateType