ソースを参照

Cleanup client historical api to meet ci and pr requirements.

The following commits where squashed in this commit:
* client_historical_data: Bugfix isInverse
* client_historical: Cleanup and refactoring to meet pr comments.
* client_historialdata: Add a test for client historical read raw.
* historical_client: support index range
* historical_client: refactoring and cleanup methods.
* historical: Make the build system configurable and rename server test.
* historical_data: cleanup
* client_historical: Add code for events and modified history
* client_historical: fix memoryleak
* hda_client: Fix client_historical example.
Peter Rustler 5 年 前
コミット
cf193cf41d

+ 11 - 1
CMakeLists.txt

@@ -129,7 +129,8 @@ endforeach(arch_ ${architectures})
 
 # Options
 set(UA_LOGLEVEL 300 CACHE STRING "Level at which logs shall be reported")
-option(UA_ENABLE_HISTORIZING "Enable server to provide historical access." ON)
+option(UA_ENABLE_HISTORIZING "Enable server and client to provide historical access." ON)
+option(UA_ENABLE_EXPERIMENTAL_HISTORIZING "Enable client experimental historical access features." OFF)
 option(UA_ENABLE_METHODCALLS "Enable the Method service set" ON)
 option(UA_ENABLE_NODEMANAGEMENT "Enable dynamic addition and removal of nodes at runtime" ON)
 option(UA_ENABLE_SUBSCRIPTIONS "Enable subscriptions support." ON)
@@ -278,6 +279,12 @@ if(UA_BUILD_FUZZING)
     add_definitions(-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
 endif()
 
+if(UA_ENABLE_EXPERIMENTAL_HISTORIZING)
+    if(NOT UA_ENABLE_HISTORIZING)
+        message(FATAL_ERROR "UA_ENABLE_EXPERIMENTAL_HISTORIZING cannot be used with disabled UA_ENABLE_HISTORIZING.")
+    endif()
+endif()
+
 if(UA_ENABLE_HISTORIZING)
     set(historizing_exported_headers
         ${PROJECT_SOURCE_DIR}/include/ua_plugin_historydatabase.h
@@ -1016,6 +1023,9 @@ endif()
 if(UA_ENABLE_HISTORIZING)
     list(APPEND open62541_enabled_components "Historizing")
 endif()
+if(UA_ENABLE_EXPERIMENTAL_HISTORIZING)
+    list(APPEND open62541_enabled_components "ExperimentalHistorizing")
+endif()
 if(UA_ENABLE_SUBSCRIPTIONS_EVENTS)
     list(APPEND open62541_enabled_components "Events")
 endif()

+ 4 - 2
examples/CMakeLists.txt

@@ -84,14 +84,16 @@ install(PROGRAMS $<TARGET_FILE:server_ctt>
 
 add_example(client client.c)
 
+if(UA_ENABLE_HISTORIZING)
+    add_example(client_historical client_historical.c)
+endif()
+
 install(PROGRAMS $<TARGET_FILE:client>
         DESTINATION bin
         RENAME ua_client)
 
 add_example(client_async client_async.c)
 
-add_example(client_historical client_historical.c)
-
 add_example(client_connect_loop client_connect_loop.c)
 
 add_example(client_connectivitycheck_loop client_connectivitycheck_loop.c)

+ 138 - 120
examples/client_historical.c

@@ -1,159 +1,177 @@
 /* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
  * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
 
-/* Enable POSIX features */
-#if !defined(_XOPEN_SOURCE) && !defined(_WRS_KERNEL)
-# define _XOPEN_SOURCE 600
-#endif
-#ifndef _DEFAULT_SOURCE
-# define _DEFAULT_SOURCE
-#endif
-/* On older systems we need to define _BSD_SOURCE.
- * _DEFAULT_SOURCE is an alias for that. */
-#ifndef _BSD_SOURCE
-# define _BSD_SOURCE
-#endif
-
 #include <stdio.h>
-#include "open62541.h"
-
-#ifdef _WIN32
-# include <windows.h>
-# define UA_sleep_ms(X) Sleep(X)
-#else
-# include <unistd.h>
-# define UA_sleep_ms(X) usleep(X * 1000)
+#include <ua_client_highlevel.h>
+#include <ua_client.h>
+#include <ua_config_default.h>
+
+#ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING
+static void
+printUpdateType(UA_HistoryUpdateType type) {
+    switch (type) {
+    case UA_HISTORYUPDATETYPE_INSERT:
+        printf("Insert\n");
+        return;
+    case UA_HISTORYUPDATETYPE_REPLACE:
+        printf("Replace\n");
+        return;
+    case UA_HISTORYUPDATETYPE_UPDATE:
+        printf("Update\n");
+        return;
+    case UA_HISTORYUPDATETYPE_DELETE:
+        printf("Delete\n");
+        return;
+    default:
+        printf("Unknown\n");
+        return;
+    }
+}
 #endif
 
-static UA_Boolean
-readHist(const UA_NodeId nodeId, const UA_Boolean isInverse,
-         const UA_Boolean moreDataAvailable,
-         const UA_HistoryData *data, void *isDouble) {
+static void
+printTimestamp(char *name, UA_DateTime date) {
+    UA_DateTimeStruct dts = UA_DateTime_toStruct(date);
+    if (name)
+        printf("%s: %02u-%02u-%04u %02u:%02u:%02u.%03u, ", name,
+               dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);
+    else
+        printf("%02u-%02u-%04u %02u:%02u:%02u.%03u, ",
+               dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);
+}
 
-    printf("\nRead historical callback:\n");
-    printf("\tValue count:\t%u\n", (UA_UInt32)data->dataValuesSize);
-    printf("\tIs inverse:\t%d\n", isInverse);
-    printf("\tHas more data:\t%d\n\n", moreDataAvailable);
+static void
+printDataValue(UA_DataValue *value) {
+    /* Print status and timestamps */
+    if (value->hasServerTimestamp)
+        printTimestamp("ServerTime", value->serverTimestamp);
+
+    if (value->hasSourceTimestamp)
+        printTimestamp("SourceTime", value->sourceTimestamp);
+
+    if (value->hasStatus)
+        printf("Status 0x%08x, ", value->status);
+
+    if (value->value.type == &UA_TYPES[UA_TYPES_UINT32]) {
+        UA_UInt32 hrValue = *(UA_UInt32 *)value->value.data;
+        printf("Uint32Value %u\n", hrValue);
+    }
+
+    if (value->value.type == &UA_TYPES[UA_TYPES_DOUBLE]) {
+        UA_Double hrValue = *(UA_Double *)value->value.data;
+        printf("DoubleValue %f\n", hrValue);
+    }
+}
+
+static UA_Boolean
+readRaw(const UA_HistoryData *data) {
+    printf("readRaw Value count: %lu\n", (long unsigned)data->dataValuesSize);
 
     /* Iterate over all values */
     for (UA_UInt32 i = 0; i < data->dataValuesSize; ++i)
     {
-        UA_DataValue val = data->dataValues[i];
-        
-        /* If there is no value, we are out of bounds or something bad hapened */
-        if (!val.hasValue) {
-            if (val.hasStatus) {
-                if (val.status == UA_STATUSCODE_BADBOUNDNOTFOUND)
-                    printf("Skipping bounds (i=%u)\n\n", i);
-                else
-                    printf("Skipping (i=%u) (status=0x%08x -> %s)\n\n", i, val.status, UA_StatusCode_name(val.status));
-            }
-
-            continue;
-        }
-        
-        /* The handle is used to determine double and byte request */
-        if ((UA_Boolean)isDouble) {
-            UA_Double hrValue = *(UA_Double *)val.value.data;
-            printf("ByteValue (i=%u) %f\n", i, hrValue);
-        }
-        else {
-            UA_Byte hrValue = *(UA_Byte *)val.value.data;
-            printf("DoubleValue (i=%u) %d\n", i, hrValue);
-        }
+        printDataValue(&data->dataValues[i]);
+    }
 
-        /* Print status and timestamps */
-        if (val.hasStatus)
-            printf("Status 0x%08x\n", val.status);
-        if (val.hasServerTimestamp) {
-            UA_DateTimeStruct dts = UA_DateTime_toStruct(val.serverTimestamp);
-            printf("ServerTime: %02u-%02u-%04u %02u:%02u:%02u.%03u\n",
-                dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);
-        }
-        if (val.hasSourceTimestamp) {
-            UA_DateTimeStruct dts = UA_DateTime_toStruct(val.sourceTimestamp);
-            printf("ServerTime: %02u-%02u-%04u %02u:%02u:%02u.%03u\n",
-                dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);
-        }
-        printf("\n");
+    /* We want more data! */
+    return true;
+}
+
+#ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING
+static UA_Boolean
+readRawModified(const UA_HistoryModifiedData *data) {
+    printf("readRawModified Value count: %lu\n", (long unsigned)data->dataValuesSize);
+
+    /* Iterate over all values */
+    for (size_t i = 0; i < data->dataValuesSize; ++i) {
+        printDataValue(&data->dataValues[i]);
+    }
+    printf("Modificaton Value count: %lu\n", data->modificationInfosSize);
+    for (size_t j = 0; j < data->modificationInfosSize; ++j) {
+        if (data->modificationInfos[j].userName.data)
+            printf("Username: %s, ", data->modificationInfos[j].userName.data);
+
+        printTimestamp("Modtime", data->modificationInfos[j].modificationTime);
+        printUpdateType(data->modificationInfos[j].updateType);
     }
 
     /* We want more data! */
     return true;
 }
 
+static UA_Boolean
+readEvents(const UA_HistoryEvent *data) {
+    printf("readEvent Value count: %lu\n", (long unsigned)data->eventsSize);
+    for (size_t i = 0; i < data->eventsSize; ++i) {
+        printf("Processing event: %lu\n", (long unsigned)i);
+        for (size_t j = 0; j < data->events[i].eventFieldsSize; ++j) {
+             printf("Processing %lu: %s\n", (long unsigned)j, data->events[i].eventFields[j].type->typeName);
+        }
+    }
+    return true;
+}
+#endif
+
+static UA_Boolean
+readHist(UA_Client *client, const UA_NodeId *nodeId,
+         UA_Boolean moreDataAvailable,
+         const UA_ExtensionObject *data, void *unused) {
+    printf("\nRead historical callback:\n");
+    printf("\tHas more data:\t%d\n\n", moreDataAvailable);
+    if (data->content.decoded.type == &UA_TYPES[UA_TYPES_HISTORYDATA]) {
+        return readRaw((UA_HistoryData*)data->content.decoded.data);
+    }
+#ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING
+    if (data->content.decoded.type == &UA_TYPES[UA_TYPES_HISTORYMODIFIEDDATA]) {
+        return readRawModified((UA_HistoryModifiedData*)data->content.decoded.data);
+    }
+    if (data->content.decoded.type == &UA_TYPES[UA_TYPES_HISTORYEVENT]) {
+        return readEvents((UA_HistoryEvent*)data->content.decoded.data);
+    }
+#endif
+    return true;
+}
+
 int main(int argc, char *argv[]) {
     UA_Client *client = UA_Client_new(UA_ClientConfig_default);
 
     /* Connect to the Unified Automation demo server */
-    UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:48020");
+    UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:53530/OPCUA/SimulationServer");
     if(retval != UA_STATUSCODE_GOOD) {
         UA_Client_delete(client);
         return (int)retval;
     }
 
-    /* Read, if data logging is active */
-    UA_Boolean active = UA_FALSE;
-    printf("Reading, if data logging is active (4, \"Demo.History.DataLoggerActive\"):\n");
-    UA_Variant *val = UA_Variant_new();
-    retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(4, "Demo.History.DataLoggerActive"), val);
-    if (retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) &&
-        val->type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
-        active = *(UA_Boolean*)val->data;
-        if (active) 
-            printf("The data logging is active. Continue.\n");
-        else
-            printf("The data logging is not active!\n");
-    }
-    else {
-        printf("Failed to read data logging status.\n");
-        UA_Variant_delete(val);
-        goto cleanup;
-    }
-    UA_Variant_delete(val);
-
-#ifdef UA_ENABLE_METHODCALLS
-    /* Active server side data logging via remote method call */
-    if (!active) {
-        printf("Activate data logging.\n");
-        retval = UA_Client_call(client, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
-            UA_NODEID_STRING(4, "Demo.History.StartLogging"), 0, NULL, NULL, NULL);
-        if (retval != UA_STATUSCODE_GOOD) {
-            printf("Start method call failed.\n");
-            goto cleanup;
-        }
+    /* Read historical values (uint32) */
+    printf("\nStart historical read (1, \"myUintValue\"):\n");
+    UA_NodeId node = UA_NODEID_STRING(2, "MyLevel");
+    retval = UA_Client_HistoryRead_raw(client, &node, readHist,
+                                       UA_DateTime_fromUnixTime(0), UA_DateTime_now(), UA_STRING_NULL, false, 10, UA_TIMESTAMPSTORETURN_BOTH, (void *)UA_FALSE);
 
-        /* The server logs a value every 50ms by default */
-        printf("Successfully started data logging. Let the server collect data for 2000ms..\n");
-        UA_sleep_ms(2000);
-    }
-#else
-    if (!active) {
-        printf("Method calling is not allowed, you have to active the data logging manually.\n");
-        goto cleanup;
+    if (retval != UA_STATUSCODE_GOOD) {
+        printf("Failed. %s\n", UA_StatusCode_name(retval));
     }
-#endif
 
-    /* Read historical values (Byte) */
-    printf("\nStart historical read (4, \"Demo.History.ByteWithHistory\"):\n");
-    retval = UA_Client_readHistorical_raw(client, UA_NODEID_STRING(4, "Demo.History.ByteWithHistory"), readHist,
-        UA_DateTime_fromUnixTime(0), UA_DateTime_now(), false, 10, UA_TIMESTAMPSTORETURN_BOTH, (void *)UA_FALSE);
-    
+#ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING
+    printf("\nStart historical modified read (1, \"myUintValue\"):\n");
+    retval = UA_Client_HistoryRead_modified(client, &node, readHist,
+                                       UA_DateTime_fromUnixTime(0), UA_DateTime_now(), UA_STRING_NULL, false, 10, UA_TIMESTAMPSTORETURN_BOTH, (void *)UA_FALSE);
+
     if (retval != UA_STATUSCODE_GOOD) {
-        printf("Failed.\n");
-        goto cleanup;
+        printf("Failed. %s\n", UA_StatusCode_name(retval));
     }
 
-    /* Read historical values (Double) */
-    printf("\nStart historical read (4, \"Demo.History.DoubleWithHistory\"):\n");
-    retval = UA_Client_readHistorical_raw(client, UA_NODEID_STRING(4, "Demo.History.DoubleWithHistory"), readHist,
-        UA_DateTime_fromUnixTime(0), UA_DateTime_now(), false, 10, UA_TIMESTAMPSTORETURN_BOTH, (void *)UA_TRUE);
+    printf("\nStart historical event read (1, \"myUintValue\"):\n");
+    UA_EventFilter filter;
+    UA_EventFilter_init(&filter);
+    UA_NodeId eventNode = UA_NODEID_NUMERIC(0, 2253);
+    retval = UA_Client_HistoryRead_events(client, &eventNode, readHist,
+                                       UA_DateTime_fromUnixTime(0), UA_DateTime_now(), UA_STRING_NULL, filter, 10, UA_TIMESTAMPSTORETURN_BOTH, (void *)UA_FALSE);
 
     if (retval != UA_STATUSCODE_GOOD) {
-        printf("Failed.\n");
+        printf("Failed. %s\n", UA_StatusCode_name(retval));
     }
-
-cleanup:
+#endif
     UA_Client_disconnect(client);
     UA_Client_delete(client);
     return (int) retval;

+ 5 - 3
include/ua_client.h

@@ -257,18 +257,20 @@ UA_Client_Service_write(UA_Client *client, const UA_WriteRequest request) {
     return response;
 }
 
-/**
+/*
 * Historical Access Service Set
 * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
+#ifdef UA_ENABLE_HISTORIZING
 static UA_INLINE UA_HistoryReadResponse
-UA_Client_Service_history_read(UA_Client *client, const UA_HistoryReadRequest request) {
+UA_Client_Service_historyRead(UA_Client *client, const UA_HistoryReadRequest request) {
     UA_HistoryReadResponse response;
     __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_HISTORYREADREQUEST],
         &response, &UA_TYPES[UA_TYPES_HISTORYREADRESPONSE]);
     return response;
 }
+#endif
 
-/**
+/*
  * Method Service Set
  * ^^^^^^^^^^^^^^^^^^ */
 #ifdef UA_ENABLE_METHODCALLS

+ 24 - 45
include/ua_client_highlevel.h

@@ -8,6 +8,8 @@
  *    Copyright 2016 (c) Chris Iatrou
  *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
  *    Copyright 2017 (c) Frank Meerkötter
+ *    Copyright 2018 (c) Fabian Arndt
+ *    Copyright 2018 (c) Peter Rustler, basyskom GmbH
  */
 
 #ifndef UA_CLIENT_HIGHLEVEL_H_
@@ -216,61 +218,38 @@ UA_Client_readUserExecutableAttribute(UA_Client *client, const UA_NodeId nodeId,
  * The following functions can be used to read a single node historically.
  * Use the regular service to read several nodes at once.
  */
+#ifdef UA_ENABLE_HISTORIZING
 typedef UA_Boolean
-(*UA_HistoricalIteratorCallback)(const UA_NodeId nodeId, const UA_Boolean isInverse,
-                                 const UA_Boolean moreDataAvailable,
-                                 const UA_HistoryData *data, void *handle);
+(*UA_HistoricalIteratorCallback)(UA_Client *client,
+                                 const UA_NodeId *nodeId,
+                                 UA_Boolean moreDataAvailable,
+                                 const UA_ExtensionObject *data, void *callbackContext);
 
-/* Don't call this function, use the typed versions */
-UA_HistoryReadResponse UA_EXPORT
-__UA_Client_readHistorical(UA_Client *client, const UA_NodeId nodeId,
-                           void* details, const UA_TimestampsToReturn timestampsToReturn,
-                           const UA_ByteString continuationPoint, const UA_Boolean releaseConti);
-
-/* Don't call this function, use the typed versions */
-UA_StatusCode UA_EXPORT
-__UA_Client_readHistorical_service(UA_Client *client, const UA_NodeId nodeId,
-                                   const UA_HistoricalIteratorCallback callback,
-                                   void *details, const UA_TimestampsToReturn timestampsToReturn,
-                                   UA_Boolean isInverse, UA_UInt32 maxItems, void *handle);
-
-/* Don't call this function, use the typed versions */
+#ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING
 UA_StatusCode UA_EXPORT
-__UA_Client_readHistorical_service_rawMod(UA_Client *client, const UA_NodeId nodeId,
-                                          const UA_HistoricalIteratorCallback callback,
-                                          const UA_DateTime startTime, const UA_DateTime endTime,
-                                          const UA_Boolean returnBounds, const UA_UInt32 maxItems,
-                                          const UA_Boolean readModified, const UA_TimestampsToReturn timestampsToReturn,
-                                          void *handle);
-
-UA_StatusCode UA_EXPORT
-UA_Client_readHistorical_events(UA_Client *client, const UA_NodeId nodeId,
+UA_Client_HistoryRead_events(UA_Client *client, const UA_NodeId *nodeId,
                                 const UA_HistoricalIteratorCallback callback,
-                                const UA_DateTime startTime, const UA_DateTime endTime,
-                                const UA_EventFilter filter, const UA_UInt32 maxItems,
-                                const UA_TimestampsToReturn timestampsToReturn, void* handle);
+                                UA_DateTime startTime, UA_DateTime endTime,
+                                UA_String indexRange, const UA_EventFilter filter, UA_UInt32 numValuesPerNode,
+                                UA_TimestampsToReturn timestampsToReturn, void *callbackContext);
+#endif // UA_ENABLE_EXPERIMENTAL_HISTORIZING
 
 UA_StatusCode UA_EXPORT
-UA_Client_readHistorical_raw(UA_Client *client, const UA_NodeId nodeId,
+UA_Client_HistoryRead_raw(UA_Client *client, const UA_NodeId *nodeId,
                              const UA_HistoricalIteratorCallback callback,
-                             const UA_DateTime startTime, const UA_DateTime endTime,
-                             const UA_Boolean returnBounds, const UA_UInt32 maxItems,
-                             const UA_TimestampsToReturn timestampsToReturn, void *handle);
+                             UA_DateTime startTime, UA_DateTime endTime,
+                             UA_String indexRange, UA_Boolean returnBounds, UA_UInt32 numValuesPerNode,
+                             UA_TimestampsToReturn timestampsToReturn, void *callbackContext);
 
+#ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING
 UA_StatusCode UA_EXPORT
-UA_Client_readHistorical_modified(UA_Client *client, const UA_NodeId nodeId,
+UA_Client_HistoryRead_modified(UA_Client *client, const UA_NodeId *nodeId,
                                   const UA_HistoricalIteratorCallback callback,
-                                  const UA_DateTime startTime, const UA_DateTime endTime,
-                                  const UA_Boolean returnBounds, const UA_UInt32 maxItems,
-                                  const UA_TimestampsToReturn timestampsToReturn, void *handle);
-
-UA_StatusCode UA_EXPORT
-UA_Client_readHistorical_atTime(UA_Client *client, const UA_NodeId nodeId,
-                                UA_DateTime *timestamps, const size_t size,
-                                const UA_Boolean interpolate, const UA_TimestampsToReturn timestampsToReturn,
-                                UA_HistoryData *outData);
-
-
+                                  UA_DateTime startTime, UA_DateTime endTime,
+                                  UA_String indexRange, UA_Boolean returnBounds, UA_UInt32 numValuesPerNode,
+                                  UA_TimestampsToReturn timestampsToReturn, void *callbackContext);
+#endif // UA_ENABLE_EXPERIMENTAL_HISTORIZING
+#endif // UA_ENABLE_HISTORIZING
 /**
  * Write Attributes
  * ^^^^^^^^^^^^^^^^

+ 1 - 0
include/ua_config.h.in

@@ -33,6 +33,7 @@
 #cmakedefine UA_ENABLE_PUBSUB_INFORMATIONMODEL_METHODS
 #cmakedefine UA_ENABLE_ENCRYPTION
 #cmakedefine UA_ENABLE_HISTORIZING
+#cmakedefine UA_ENABLE_EXPERIMENTAL_HISTORIZING
 #cmakedefine UA_ENABLE_SUBSCRIPTIONS_EVENTS
 
 /* Multithreading */

+ 168 - 159
src/client/ua_client_highlevel.c

@@ -7,6 +7,8 @@
  *    Copyright 2017 (c) Florian Palm
  *    Copyright 2016 (c) Chris Iatrou
  *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
+ *    Copyright 2018 (c) Fabian Arndt
+ *    Copyright 2018 (c) Peter Rustler, basyskom GmbH
  */
 
 #include "ua_client.h"
@@ -457,6 +459,172 @@ UA_Client_readArrayDimensionsAttribute(UA_Client *client, const UA_NodeId nodeId
     return retval;
 }
 
+/*********************/
+/* Historical Access */
+/*********************/
+#ifdef UA_ENABLE_HISTORIZING
+static UA_HistoryReadResponse
+__UA_Client_HistoryRead(UA_Client *client, const UA_NodeId *nodeId,
+                        UA_ExtensionObject* details, UA_String indexRange,
+                        UA_TimestampsToReturn timestampsToReturn,
+                        UA_ByteString continuationPoint, UA_Boolean releaseConti) {
+
+    UA_HistoryReadValueId item;
+    UA_HistoryReadValueId_init(&item);
+
+    item.nodeId = *nodeId;
+    item.indexRange = indexRange;
+    item.continuationPoint = continuationPoint;
+    item.dataEncoding = UA_QUALIFIEDNAME(0, "Default Binary");
+
+    UA_HistoryReadRequest request;
+    UA_HistoryReadRequest_init(&request);
+
+    request.nodesToRead = &item;
+    request.nodesToReadSize = 1;
+    request.timestampsToReturn = timestampsToReturn; // Defaults to Source
+    request.releaseContinuationPoints = releaseConti; // No values are returned, if true
+
+    /* Build ReadDetails */
+    request.historyReadDetails = *details;
+
+    return UA_Client_Service_historyRead(client, request);
+}
+
+static UA_StatusCode
+__UA_Client_HistoryRead_service(UA_Client *client, const UA_NodeId *nodeId,
+                                   const UA_HistoricalIteratorCallback callback,
+                                   UA_ExtensionObject *details, UA_String indexRange,
+                                   UA_TimestampsToReturn timestampsToReturn,
+                                   void *callbackContext) {
+
+    UA_ByteString continuationPoint = UA_BYTESTRING_NULL;
+    UA_Boolean continuationAvail = UA_FALSE;
+    UA_Boolean fetchMore = UA_FALSE;
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+
+    do {
+        /* We release the continuation point, if no more data is requested by the user */
+        UA_Boolean cleanup = !fetchMore && continuationAvail;
+        UA_HistoryReadResponse response =
+            __UA_Client_HistoryRead(client, nodeId, details, indexRange, timestampsToReturn, continuationPoint, cleanup);
+
+        if (cleanup) {
+            retval = response.responseHeader.serviceResult;
+cleanup:    UA_HistoryReadResponse_deleteMembers(&response);
+            UA_ByteString_deleteMembers(&continuationPoint);
+            return retval;
+        }
+
+        retval = response.responseHeader.serviceResult;
+        if (retval == UA_STATUSCODE_GOOD) {
+            if (response.resultsSize == 1)
+                retval = response.results[0].statusCode;
+            else
+                retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
+        }
+        if (retval != UA_STATUSCODE_GOOD)
+            goto cleanup;
+
+        UA_HistoryReadResult *res = response.results;
+
+        /* Clear old and check / store new continuation point */
+        UA_ByteString_deleteMembers(&continuationPoint);
+        UA_ByteString_copy(&res->continuationPoint, &continuationPoint);
+        continuationAvail = !UA_ByteString_equal(&continuationPoint, &UA_BYTESTRING_NULL);
+
+        /* Client callback with possibility to request further values */
+        fetchMore = callback(client, nodeId, continuationAvail, &res->historyData, callbackContext);
+
+        /* Regular cleanup */
+        UA_HistoryReadResponse_deleteMembers(&response);
+    } while (continuationAvail);
+
+    return retval;
+}
+
+#ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING
+UA_StatusCode
+UA_Client_HistoryRead_events(UA_Client *client, const UA_NodeId *nodeId,
+                                const UA_HistoricalIteratorCallback callback,
+                                UA_DateTime startTime, UA_DateTime endTime,
+                                UA_String indexRange, const UA_EventFilter filter, UA_UInt32 numValuesPerNode,
+                                UA_TimestampsToReturn timestampsToReturn, void *callbackContext) {
+
+    UA_ReadEventDetails details;
+    UA_ReadEventDetails_init(&details);
+    details.filter = filter;
+
+    // At least two of the following parameters must be set
+    details.numValuesPerNode = numValuesPerNode; // 0 = return all / max server is capable of
+    details.startTime = startTime;
+    details.endTime = endTime;
+
+    UA_ExtensionObject detailsExtensionObject;
+    UA_ExtensionObject_init(&detailsExtensionObject);
+    detailsExtensionObject.content.decoded.type = &UA_TYPES[UA_TYPES_READEVENTDETAILS];
+    detailsExtensionObject.content.decoded.data = &details;
+    detailsExtensionObject.encoding = UA_EXTENSIONOBJECT_DECODED;
+
+    return __UA_Client_HistoryRead_service(client, nodeId, callback, &detailsExtensionObject,
+                                              indexRange, timestampsToReturn, callbackContext);
+}
+#endif // UA_ENABLE_EXPERIMENTAL_HISTORIZING
+
+static UA_StatusCode
+__UA_Client_HistoryRead_service_rawMod(UA_Client *client, const UA_NodeId *nodeId,
+                                          const UA_HistoricalIteratorCallback callback,
+                                          UA_DateTime startTime,UA_DateTime endTime,
+                                          UA_String indexRange, UA_Boolean returnBounds, UA_UInt32 numValuesPerNode,
+                                          UA_Boolean readModified, UA_TimestampsToReturn timestampsToReturn,
+                                          void *callbackContext) {
+
+    UA_ReadRawModifiedDetails details;
+    UA_ReadRawModifiedDetails_init(&details);
+    details.isReadModified = readModified; // Return only modified values
+    details.returnBounds = returnBounds;   // Return values pre / post given range
+
+    // At least two of the following parameters must be set
+    details.numValuesPerNode = numValuesPerNode;   // 0 = return all / max server is capable of
+    details.startTime = startTime;
+    details.endTime = endTime;
+
+    UA_ExtensionObject detailsExtensionObject;
+    UA_ExtensionObject_init(&detailsExtensionObject);
+    detailsExtensionObject.content.decoded.type = &UA_TYPES[UA_TYPES_READRAWMODIFIEDDETAILS];
+    detailsExtensionObject.content.decoded.data = &details;
+    detailsExtensionObject.encoding = UA_EXTENSIONOBJECT_DECODED;
+
+    return __UA_Client_HistoryRead_service(client, nodeId, callback,
+                                              &detailsExtensionObject, indexRange,
+                                              timestampsToReturn, callbackContext);
+}
+
+UA_StatusCode
+UA_Client_HistoryRead_raw(UA_Client *client, const UA_NodeId *nodeId,
+                             const UA_HistoricalIteratorCallback callback,
+                             UA_DateTime startTime, UA_DateTime endTime,
+                             UA_String indexRange, UA_Boolean returnBounds, UA_UInt32 numValuesPerNode,
+                             UA_TimestampsToReturn timestampsToReturn, void *callbackContext) {
+
+    return __UA_Client_HistoryRead_service_rawMod(client, nodeId, callback, startTime, endTime, indexRange, returnBounds,
+                                                     numValuesPerNode, UA_FALSE, timestampsToReturn, callbackContext);
+}
+
+#ifdef UA_ENABLE_EXPERIMENTAL_HISTORIZING
+UA_StatusCode
+UA_Client_HistoryRead_modified(UA_Client *client, const UA_NodeId *nodeId,
+                                  const UA_HistoricalIteratorCallback callback,
+                                  UA_DateTime startTime, UA_DateTime endTime,
+                                  UA_String indexRange, UA_Boolean returnBounds, UA_UInt32 maxItems,
+                                  UA_TimestampsToReturn timestampsToReturn, void *callbackContext) {
+
+    return __UA_Client_HistoryRead_service_rawMod(client, nodeId, callback, startTime, endTime, indexRange, returnBounds,
+                                                     maxItems, UA_TRUE, timestampsToReturn, callbackContext);
+}
+#endif // UA_ENABLE_EXPERIMENTAL_HISTORIZING
+#endif // UA_ENABLE_HISTORIZING
+
 /* Async Functions */
 
 static
@@ -651,162 +819,3 @@ UA_StatusCode __UA_Client_translateBrowsePathsToNodeIds_async(UA_Client *client,
     UA_BrowsePath_deleteMembers(&browsePath);
     return retval;
 }
-
-/*********************/
-/* Historical Access */
-/*********************/
-
-// FIXME: Function for ReadProcessedDetails is missing
-UA_HistoryReadResponse
-__UA_Client_readHistorical(UA_Client *client, const UA_NodeId nodeId,
-                           void* details, const UA_TimestampsToReturn timestampsToReturn,
-                           const UA_ByteString continuationPoint, const UA_Boolean releaseConti) {
-
-    UA_HistoryReadValueId item;
-    UA_HistoryReadValueId_init(&item);
-
-    item.nodeId = nodeId;
-    item.indexRange = UA_STRING_NULL; // TODO: NumericRange (?)
-    item.continuationPoint = continuationPoint;
-    item.dataEncoding = UA_QUALIFIEDNAME(0, "Default Binary");
-
-    UA_HistoryReadRequest request;
-    UA_HistoryReadRequest_init(&request);
-
-    request.nodesToRead = &item;
-    request.nodesToReadSize = 1;
-    request.timestampsToReturn = timestampsToReturn; // Defaults to Source
-    request.releaseContinuationPoints = releaseConti; // No values are returned, if true
-
-    /* Build ReadDetails */
-    request.historyReadDetails.content.decoded.type = &UA_TYPES[UA_TYPES_READRAWMODIFIEDDETAILS];
-    request.historyReadDetails.content.decoded.data = details;
-    request.historyReadDetails.encoding = UA_EXTENSIONOBJECT_DECODED;
-
-    return UA_Client_Service_history_read(client, request);
-}
-
-UA_StatusCode
-__UA_Client_readHistorical_service(UA_Client *client, const UA_NodeId nodeId,
-                                   const UA_HistoricalIteratorCallback callback,
-                                   void *details, const UA_TimestampsToReturn timestampsToReturn,
-                                   UA_Boolean isInverse, UA_UInt32 maxItems, void *handle) {
-
-    UA_ByteString continuationPoint = UA_BYTESTRING_NULL;
-    UA_Boolean continuationAvail = UA_FALSE;
-    UA_Boolean fetchMore = UA_FALSE;
-    UA_StatusCode retval = UA_STATUSCODE_GOOD;
-
-    do {
-        /* We release the continuation point, if no more data is requested by the user */
-        UA_Boolean cleanup = !fetchMore && continuationAvail;
-        UA_HistoryReadResponse response =
-            __UA_Client_readHistorical(client, nodeId, details, timestampsToReturn, continuationPoint, cleanup);
-
-        if (cleanup) {
-            retval = response.responseHeader.serviceResult;
-cleanup:    UA_HistoryReadResponse_deleteMembers(&response);
-            return retval;
-        }
-
-        retval = response.responseHeader.serviceResult;
-        if (retval == UA_STATUSCODE_GOOD) {
-            if (response.resultsSize == 1)
-                retval = response.results[0].statusCode;
-            else
-                retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
-        }
-        if (retval != UA_STATUSCODE_GOOD)
-            goto cleanup;
-
-        UA_HistoryReadResult *res = response.results;
-        UA_HistoryData *data = (UA_HistoryData*)res->historyData.content.decoded.data;
-
-        /* We should never receive more values, than requested */
-        if (maxItems && data->dataValuesSize > maxItems) {
-            retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
-            goto cleanup;
-        }
-
-        /* Clear old and check / store new continuation point */
-        UA_ByteString_deleteMembers(&continuationPoint);
-        UA_ByteString_copy(&res->continuationPoint, &continuationPoint);
-        continuationAvail = !UA_ByteString_equal(&continuationPoint, &UA_BYTESTRING_NULL);
-
-        /* Client callback with posibility to request further values */
-        fetchMore = callback(nodeId, isInverse, continuationAvail, data, handle);
-
-        /* Regular cleanup */
-        UA_HistoryReadResponse_deleteMembers(&response);
-    } while (continuationAvail);
-
-    return retval;
-}
-
-UA_StatusCode
-UA_Client_readHistorical_events(UA_Client *client, const UA_NodeId nodeId,
-                                const UA_HistoricalIteratorCallback callback,
-                                const UA_DateTime startTime, const UA_DateTime endTime,
-                                const UA_EventFilter filter, const UA_UInt32 maxItems,
-                                const UA_TimestampsToReturn timestampsToReturn, void* handle) {
-
-    // TODO: ReadProcessedDetails / ReadAtTimeDetails
-    UA_ReadEventDetails details;
-    UA_ReadEventDetails_init(&details);
-    details.filter = filter;
-
-    // At least two of the following parameters must be set
-    details.numValuesPerNode = maxItems; // 0 = return all / max server is capable of
-    details.startTime = startTime;
-    details.endTime = endTime;
-
-    UA_Boolean isInverse = !startTime || (endTime && (startTime > endTime));
-    return __UA_Client_readHistorical_service(client, nodeId, callback, &details,
-                                              timestampsToReturn, isInverse, 0, handle);
-}
-
-UA_StatusCode
-__UA_Client_readHistorical_service_rawMod(UA_Client *client, const UA_NodeId nodeId,
-                                          const UA_HistoricalIteratorCallback callback,
-                                          const UA_DateTime startTime, const UA_DateTime endTime,
-                                          const UA_Boolean returnBounds, const UA_UInt32 maxItems,
-                                          const UA_Boolean readModified, const UA_TimestampsToReturn timestampsToReturn,
-                                          void *handle) {
-
-    // TODO: ReadProcessedDetails / ReadAtTimeDetails
-    UA_ReadRawModifiedDetails details;
-    UA_ReadRawModifiedDetails_init(&details);
-    details.isReadModified = readModified; // Return only modified values
-    details.returnBounds = returnBounds;   // Return values pre / post given range
-
-    // At least two of the following parameters must be set
-    details.numValuesPerNode = maxItems;   // 0 = return all / max server is capable of
-    details.startTime = startTime;
-    details.endTime = endTime;
-
-    UA_Boolean isInverse = !startTime || (endTime && (startTime > endTime));
-    return __UA_Client_readHistorical_service(client, nodeId, callback, &details,
-                                              timestampsToReturn, isInverse, maxItems, handle);
-}
-
-UA_StatusCode
-UA_Client_readHistorical_raw(UA_Client *client, const UA_NodeId nodeId,
-                             const UA_HistoricalIteratorCallback callback,
-                             const UA_DateTime startTime, const UA_DateTime endTime,
-                             const UA_Boolean returnBounds, const UA_UInt32 maxItems,
-                             const UA_TimestampsToReturn timestampsToReturn, void *handle) {
-
-    return __UA_Client_readHistorical_service_rawMod(client, nodeId, callback, startTime, endTime, returnBounds,
-                                                     maxItems, UA_FALSE, timestampsToReturn, handle);
-}
-
-UA_StatusCode
-UA_Client_readHistorical_modified(UA_Client *client, const UA_NodeId nodeId,
-                                  const UA_HistoricalIteratorCallback callback,
-                                  const UA_DateTime startTime, const UA_DateTime endTime,
-                                  const UA_Boolean returnBounds, const UA_UInt32 maxItems,
-                                  const UA_TimestampsToReturn timestampsToReturn, void *handle) {
-
-    return __UA_Client_readHistorical_service_rawMod(client, nodeId, callback, startTime, endTime, returnBounds,
-                                                     maxItems, UA_TRUE, timestampsToReturn, handle);
-}

+ 7 - 3
tests/CMakeLists.txt

@@ -178,9 +178,9 @@ add_executable(check_nodestore server/check_nodestore.c $<TARGET_OBJECTS:open625
 target_link_libraries(check_nodestore ${LIBS})
 add_test_valgrind(nodestore ${TESTS_BINARY_DIR}/check_nodestore)
 
-add_executable(check_historical_data server/check_historical_data.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
-target_link_libraries(check_historical_data ${LIBS})
-add_test_valgrind(historical_data ${TESTS_BINARY_DIR}/check_historical_data)
+add_executable(check_server_historical_data server/check_server_historical_data.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+target_link_libraries(check_server_historical_data ${LIBS})
+add_test_valgrind(server_historical_data ${TESTS_BINARY_DIR}/check_server_historical_data)
 
 add_executable(check_session server/check_session.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
 target_link_libraries(check_session ${LIBS})
@@ -279,6 +279,10 @@ add_executable(check_client_highlevel client/check_client_highlevel.c $<TARGET_O
 target_link_libraries(check_client_highlevel ${LIBS})
 add_test_valgrind(client_highlevel ${TESTS_BINARY_DIR}/check_client_highlevel)
 
+add_executable(check_client_historical_data client/check_client_historical_data.c $<TARGET_OBJECTS:open62541-object> $<TARGET_OBJECTS:open62541-testplugins>)
+target_link_libraries(check_client_historical_data ${LIBS})
+add_test_valgrind(client_historical_data ${TESTS_BINARY_DIR}/check_client_historical_data)
+
 # Test Encryption
 
 if(UA_ENABLE_ENCRYPTION)

+ 332 - 0
tests/client/check_client_historical_data.c

@@ -0,0 +1,332 @@
+/* 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 "ua_types.h"
+#include "ua_server.h"
+#include "server/ua_server_internal.h"
+#include "ua_client.h"
+#include "client/ua_client_internal.h"
+#include "ua_client_highlevel.h"
+#include "ua_config_default.h"
+#include "ua_network_tcp.h"
+
+#include "check.h"
+#include "testing_clock.h"
+#include "testing_networklayers.h"
+#include "thread_wrapper.h"
+#include "ua_plugin_historydatabase.h"
+#include "ua_historydatabase_default.h"
+#include "ua_plugin_history_data_gathering.h"
+#include "ua_historydatabackend_memory.h"
+#include "ua_historydatagathering_default.h"
+#ifdef UA_ENABLE_HISTORIZING
+#include "historical_read_test_data.h"
+#endif
+#include <stddef.h>
+
+
+static UA_Server *server;
+static UA_ServerConfig *config;
+#ifdef UA_ENABLE_HISTORIZING
+static UA_HistoryDataGathering *gathering;
+#endif
+static UA_Boolean running;
+static THREAD_HANDLE server_thread;
+
+static UA_Client *client;
+static UA_NodeId parentNodeId;
+static UA_NodeId parentReferenceNodeId;
+static UA_NodeId outNodeId;
+#ifdef UA_ENABLE_HISTORIZING
+static UA_HistoryDataBackend serverBackend;
+
+// same size as the test data
+static const size_t testDataSize = sizeof(testData) / sizeof(testData[0]);
+static UA_DateTime receivedTestData[sizeof(testData) / sizeof(testData[0])];
+static size_t receivedTestDataPos;
+#endif
+
+THREAD_CALLBACK(serverloop)
+{
+    while(running)
+        UA_Server_run_iterate(server, true);
+    return 0;
+}
+#ifdef UA_ENABLE_HISTORIZING
+static UA_Boolean
+fillHistoricalDataBackend(UA_HistoryDataBackend backend);
+#endif
+static void
+setup(void)
+{
+    running = true;
+    config = UA_ServerConfig_new_default();
+#ifdef UA_ENABLE_HISTORIZING
+    receivedTestDataPos = 0;
+    memset(receivedTestData, 0, sizeof(receivedTestData));
+
+    gathering = (UA_HistoryDataGathering*)UA_calloc(1, sizeof(UA_HistoryDataGathering));
+    *gathering = UA_HistoryDataGathering_Default(1);
+    config->historyDatabase = UA_HistoryDatabase_default(*gathering);
+#endif
+    server = UA_Server_new(config);
+    UA_StatusCode retval = UA_Server_run_startup(server);
+    ck_assert_str_eq(UA_StatusCode_name(retval), UA_StatusCode_name(UA_STATUSCODE_GOOD));
+    THREAD_CREATE(server_thread, serverloop);
+    /* Define the attribute of the uint32 variable node */
+    UA_VariableAttributes attr = UA_VariableAttributes_default;
+    UA_UInt32 myUint32 = 40;
+    UA_Variant_setScalar(&attr.value, &myUint32, &UA_TYPES[UA_TYPES_UINT32]);
+    attr.description = UA_LOCALIZEDTEXT("en-US","the answer");
+    attr.displayName = UA_LOCALIZEDTEXT("en-US","the answer");
+    attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
+    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE | UA_ACCESSLEVELMASK_HISTORYREAD;
+    attr.historizing = true;
+
+    /* Add the variable node to the information model */
+    UA_NodeId uint32NodeId = UA_NODEID_STRING(1, "the.answer");
+    UA_QualifiedName uint32Name = UA_QUALIFIEDNAME(1, "the answer");
+    parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
+    parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
+    UA_NodeId_init(&outNodeId);
+    ck_assert_uint_eq(UA_Server_addVariableNode(server,
+                                                uint32NodeId,
+                                                parentNodeId,
+                                                parentReferenceNodeId,
+                                                uint32Name,
+                                                UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
+                                                attr,
+                                                NULL,
+                                                &outNodeId)
+                      , UA_STATUSCODE_GOOD);
+
+#ifdef UA_ENABLE_HISTORIZING
+    UA_HistorizingNodeIdSettings setting;
+    serverBackend = UA_HistoryDataBackend_Memory(1, 100);
+    setting.historizingBackend = serverBackend;
+    setting.maxHistoryDataResponseSize = 100;
+    setting.historizingUpdateStrategy = UA_HISTORIZINGUPDATESTRATEGY_USER;
+    retval = gathering->registerNodeId(server, gathering->context, &outNodeId, setting);
+    ck_assert_str_eq(UA_StatusCode_name(retval), UA_StatusCode_name(UA_STATUSCODE_GOOD));
+
+    ck_assert(fillHistoricalDataBackend(setting.historizingBackend));
+#endif
+    client = UA_Client_new(UA_ClientConfig_default);
+    retval = UA_Client_connect(client, "opc.tcp://localhost:4840");
+    ck_assert_str_eq(UA_StatusCode_name(retval), UA_StatusCode_name(UA_STATUSCODE_GOOD));
+
+    UA_Client_recv = client->connection.recv;
+    client->connection.recv = UA_Client_recvTesting;
+}
+
+static void
+teardown(void)
+{
+    /* cleanup */
+#ifdef UA_ENABLE_HISTORIZING
+    UA_HistoryDataBackend_Memory_deleteMembers(&serverBackend);
+#endif
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+    UA_NodeId_deleteMembers(&parentNodeId);
+    UA_NodeId_deleteMembers(&parentReferenceNodeId);
+    UA_NodeId_deleteMembers(&outNodeId);
+    running = false;
+    THREAD_JOIN(server_thread);
+    UA_Server_run_shutdown(server);
+    UA_Server_delete(server);
+    UA_ServerConfig_delete(config);
+#ifdef UA_ENABLE_HISTORIZING
+    UA_free(gathering);
+#endif
+}
+
+#ifdef UA_ENABLE_HISTORIZING
+
+#include <stdio.h>
+#include "ua_session.h"
+
+static UA_Boolean
+fillHistoricalDataBackend(UA_HistoryDataBackend backend)
+{
+    fprintf(stderr, "Adding to historical data backend: ");
+    for (size_t i = 0; i < testDataSize; ++i) {
+        fprintf(stderr, "%lld, ", testData[i] / UA_DATETIME_SEC);
+        UA_DataValue value;
+        UA_DataValue_init(&value);
+        value.hasValue = true;
+        UA_Int64 d = testData[i];
+        UA_Variant_setScalarCopy(&value.value, &d, &UA_TYPES[UA_TYPES_INT64]);
+        value.hasSourceTimestamp = true;
+        value.sourceTimestamp = testData[i];
+        value.hasServerTimestamp = true;
+        value.serverTimestamp = testData[i];
+        value.hasStatus = true;
+        value.status = UA_STATUSCODE_GOOD;
+        if (backend.serverSetHistoryData(server, backend.context, NULL, NULL, &outNodeId, UA_FALSE, &value) != UA_STATUSCODE_GOOD) {
+            fprintf(stderr, "\n");
+            return false;
+        }
+        UA_DataValue_deleteMembers(&value);
+    }
+    fprintf(stderr, "\n");
+    return true;
+}
+
+static UA_Boolean checkTestData(UA_Boolean inverse) {
+    for (size_t i = 0; i < testDataSize; ++i) {
+        if (testDataSize != receivedTestDataPos)
+            return false;
+        if (!inverse && testData[i] != receivedTestData[i])
+            return false;
+        if (inverse && testData[i] != receivedTestData[testDataSize-i-1])
+            return false;
+    }
+    return true;
+}
+static UA_Boolean
+receiveCallback(UA_Client *clt,
+                const UA_NodeId *nodeId,
+                UA_Boolean moreDataAvailable,
+                const UA_ExtensionObject *_data,
+                void *callbackContext) {
+    UA_HistoryData *data = (UA_HistoryData*)_data->content.decoded.data;
+    fprintf(stderr, "Received %lu at pos %lu. moreDataAvailable %d\n", data->dataValuesSize, receivedTestDataPos, moreDataAvailable);
+    if (receivedTestDataPos + data->dataValuesSize > testDataSize)
+        return false;
+    for (size_t i = 0; i < data->dataValuesSize; ++i) {
+        receivedTestData[i+receivedTestDataPos] = *((UA_Int64*)data->dataValues[i].value.data);
+    }
+    receivedTestDataPos += data->dataValuesSize;
+    return true;
+}
+
+START_TEST(Client_HistorizingReadRawAll)
+{
+    UA_Client_HistoryRead_raw(client,
+                              &outNodeId,
+                              receiveCallback,
+                              TESTDATA_START_TIME,
+                              TESTDATA_STOP_TIME,
+                              UA_STRING_NULL,
+                              false,
+                              100,
+                              UA_TIMESTAMPSTORETURN_BOTH,
+                              (void*)false);
+    ck_assert(checkTestData(false));
+}
+END_TEST
+
+START_TEST(Client_HistorizingReadRawOne)
+{
+    UA_Client_HistoryRead_raw(client,
+                              &outNodeId,
+                              receiveCallback,
+                              TESTDATA_START_TIME,
+                              TESTDATA_STOP_TIME,
+                              UA_STRING_NULL,
+                              false,
+                              1,
+                              UA_TIMESTAMPSTORETURN_BOTH,
+                              (void*)false);
+    ck_assert(checkTestData(false));
+}
+END_TEST
+
+START_TEST(Client_HistorizingReadRawTwo)
+{
+    UA_Client_HistoryRead_raw(client,
+                              &outNodeId,
+                              receiveCallback,
+                              TESTDATA_START_TIME,
+                              TESTDATA_STOP_TIME,
+                              UA_STRING_NULL,
+                              false,
+                              2,
+                              UA_TIMESTAMPSTORETURN_BOTH,
+                              (void*)false);
+    ck_assert(checkTestData(false));
+}
+END_TEST
+START_TEST(Client_HistorizingReadRawAllInv)
+{
+    UA_Client_HistoryRead_raw(client,
+                              &outNodeId,
+                              receiveCallback,
+                              TESTDATA_STOP_TIME,
+                              TESTDATA_START_TIME,
+                              UA_STRING_NULL,
+                              false,
+                              100,
+                              UA_TIMESTAMPSTORETURN_BOTH,
+                              (void*)true);
+    ck_assert(checkTestData(true));
+}
+END_TEST
+
+START_TEST(Client_HistorizingReadRawOneInv)
+{
+    UA_Client_HistoryRead_raw(client,
+                              &outNodeId,
+                              receiveCallback,
+                              TESTDATA_STOP_TIME,
+                              TESTDATA_START_TIME,
+                              UA_STRING_NULL,
+                              false,
+                              1,
+                              UA_TIMESTAMPSTORETURN_BOTH,
+                              (void*)true);
+    ck_assert(checkTestData(true));
+}
+END_TEST
+
+START_TEST(Client_HistorizingReadRawTwoInv)
+{
+    UA_Client_HistoryRead_raw(client,
+                              &outNodeId,
+                              receiveCallback,
+                              TESTDATA_STOP_TIME,
+                              TESTDATA_START_TIME,
+                              UA_STRING_NULL,
+                              false,
+                              2,
+                              UA_TIMESTAMPSTORETURN_BOTH,
+                              (void*)true);
+    ck_assert(checkTestData(true));
+}
+END_TEST
+
+#endif /*UA_ENABLE_HISTORIZING*/
+
+static Suite* testSuite_Client(void)
+{
+    Suite *s = suite_create("Client Historical Data");
+    TCase *tc_client = tcase_create("Client Historical Data read_raw");
+    tcase_add_checked_fixture(tc_client, setup, teardown);
+#ifdef UA_ENABLE_HISTORIZING
+    tcase_add_test(tc_client, Client_HistorizingReadRawAll);
+    tcase_add_test(tc_client, Client_HistorizingReadRawOne);
+    tcase_add_test(tc_client, Client_HistorizingReadRawTwo);
+    tcase_add_test(tc_client, Client_HistorizingReadRawAllInv);
+    tcase_add_test(tc_client, Client_HistorizingReadRawOneInv);
+    tcase_add_test(tc_client, Client_HistorizingReadRawTwoInv);
+#endif /* UA_ENABLE_HISTORIZING */
+    suite_add_tcase(s, tc_client);
+
+    return s;
+}
+
+int main(void)
+{
+    Suite *s = testSuite_Client();
+    SRunner *sr = srunner_create(s);
+    srunner_set_fork_status(sr, CK_NOFORK);
+    srunner_run_all(sr,CK_NORMAL);
+    int number_failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}

+ 26 - 0
tests/client/historical_read_test_data.h

@@ -0,0 +1,26 @@
+/* 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)
+ */
+
+/* Data in this header is gathered from OPC Unified Architecture, Part 11, Release
+1.03 Page 5-6 from OPC Foundation */
+
+#ifndef UA_HISTORICAL_READ_TEST_DATA_H_
+#define UA_HISTORICAL_READ_TEST_DATA_H_
+
+#include "ua_types.h"
+
+#define TESTDATA_START_TIME 99
+#define TESTDATA_STOP_TIME 501
+static UA_DateTime testData[] = {
+    100,
+    200,
+    300,
+    400,
+    500
+};
+
+#endif /*UA_HISTORICAL_READ_TEST_DATA_H_*/

tests/server/check_historical_data.c → tests/server/check_server_historical_data.c


+ 0 - 19
tools/schema/datatypes_minimal.txt

@@ -115,22 +115,3 @@ UtcTime
 LocaleId
 RedundancySupport
 ServerDiagnosticsSummaryDataType
-RedundantServerDataType
-NetworkGroupDataType
-NumericRange
-EndpointUrlListDataType
-ModelChangeStructureDataType
-SemanticChangeStructureDataType
-TimeZoneDataType
-AggregateConfiguration
-AggregateFilter
-SetTriggeringRequest
-SetTriggeringResponse
-HistoryReadValueId
-HistoryReadResult
-HistoryReadDetails
-ReadRawModifiedDetails
-ReadEventDetails
-HistoryData
-HistoryReadRequest
-HistoryReadResponse