Coverage Report

Created: 2026-10-03 06:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/open62541_15/src/server/ua_subscription_datachange.c
Line
Count
Source
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
 *
5
 *    Copyright 2017-2020 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
6
 *    Copyright 2017 (c) Stefan Profanter, fortiss GmbH
7
 *    Copyright 2018 (c) Ari Breitkreuz, fortiss GmbH
8
 *    Copyright 2018 (c) Thomas Stalder, Blue Time Concept SA
9
 *    Copyright 2018 (c) Fabian Arndt, Root-Core
10
 */
11
12
#include "ua_server_internal.h"
13
#include "ua_subscription.h"
14
#include "../ua_types_encoding_binary.h"
15
16
#ifdef UA_ENABLE_SUBSCRIPTIONS /* conditional compilation */
17
18
/* Detect value changes outside the deadband.
19
 *
20
 * Integer types: compute the absolute difference in a wide unsigned type
21
 * (UA_UInt64) first, then widen only the *difference* to UA_Double for the
22
 * deadband comparison. This avoids both signed integer overflow (e.g.
23
 * (UA_SByte)(100 - (-50)) wraps to -106 instead of 150) and the 2^53
24
 * precision loss that comes from casting the operands to UA_Double before
25
 * subtracting: two huge but close Int64/UInt64 values would each round to a
26
 * nearby representable double and the subtraction would accumulate the
27
 * rounding error. Computing the exact integer magnitude first keeps the
28
 * difference exact (it fits in UA_Double's 53-bit mantissa for any realistic
29
 * deadband), so only the (usually small) delta is widened. The (UA_UInt64)
30
 * cast of a negative signed value yields the correct unsigned magnitude on
31
 * every platform. */
32
0
#define UA_DETECT_DEADBAND(TYPE) do {                                      \
33
0
    TYPE v1 = *(const TYPE*)data1;                                         \
34
0
    TYPE v2 = *(const TYPE*)data2;                                         \
35
0
    UA_UInt64 mag = (v1 > v2) ? (UA_UInt64)v1 - (UA_UInt64)v2               \
36
0
                              : (UA_UInt64)v2 - (UA_UInt64)v1;              \
37
0
    UA_Double diff = (UA_Double)mag;                                       \
38
0
    return (diff > deadband);                                              \
39
0
} while(false)
40
41
/* Floating-point types: the integer magnitude approach would truncate the
42
 * fractional part, so subtract directly in UA_Double. This is safe from
43
 * overflow and exact (Float widens to Double without loss). */
44
0
#define UA_DETECT_DEADBAND_FLOAT(TYPE) do {                                 \
45
0
    TYPE v1 = *(const TYPE*)data1;                                         \
46
0
    TYPE v2 = *(const TYPE*)data2;                                         \
47
0
    UA_Double diff = (v1 > v2) ? (UA_Double)v1 - (UA_Double)v2             \
48
0
                               : (UA_Double)v2 - (UA_Double)v1;            \
49
0
    return (diff > deadband);                                              \
50
0
} while(false)
51
52
static UA_Boolean
53
detectScalarDeadBand(const void *data1, const void *data2,
54
0
                     const UA_DataType *type, const UA_Double deadband) {
55
0
    if(type->typeKind == UA_DATATYPEKIND_SBYTE) {
56
0
        UA_DETECT_DEADBAND(UA_SByte);
57
0
    } else if(type->typeKind == UA_DATATYPEKIND_BYTE) {
58
0
        UA_DETECT_DEADBAND(UA_Byte);
59
0
    } else if(type->typeKind == UA_DATATYPEKIND_INT16) {
60
0
        UA_DETECT_DEADBAND(UA_Int16);
61
0
    } else if(type->typeKind == UA_DATATYPEKIND_UINT16) {
62
0
        UA_DETECT_DEADBAND(UA_UInt16);
63
0
    } else if(type->typeKind == UA_DATATYPEKIND_INT32) {
64
0
        UA_DETECT_DEADBAND(UA_Int32);
65
0
    } else if(type->typeKind == UA_DATATYPEKIND_UINT32) {
66
0
        UA_DETECT_DEADBAND(UA_UInt32);
67
0
    } else if(type->typeKind == UA_DATATYPEKIND_INT64) {
68
0
        UA_DETECT_DEADBAND(UA_Int64);
69
0
    } else if(type->typeKind == UA_DATATYPEKIND_UINT64) {
70
0
        UA_DETECT_DEADBAND(UA_UInt64);
71
0
    } else if(type->typeKind == UA_DATATYPEKIND_FLOAT) {
72
0
        UA_DETECT_DEADBAND_FLOAT(UA_Float);
73
0
    } else if(type->typeKind == UA_DATATYPEKIND_DOUBLE) {
74
0
        UA_DETECT_DEADBAND_FLOAT(UA_Double);
75
0
    } else {
76
0
        return false; /* Not a known numerical type */
77
0
    }
78
0
}
79
80
static UA_Boolean
81
detectVariantDeadband(const UA_Variant *value, const UA_Variant *oldValue,
82
0
                      const UA_Double deadbandValue) {
83
    /* Be careful to avoid a NULL access. We could have the value a scalar and
84
     * oldValue an empty array. Both define a type and arrayLength == 0. */
85
0
    if(value->arrayLength != oldValue->arrayLength)
86
0
        return true;
87
0
    if(value->type != oldValue->type)
88
0
        return true;
89
0
    if(UA_Variant_isScalar(value) != UA_Variant_isScalar(oldValue))
90
0
        return true;
91
92
    /* Treat scalars as an array of length 1 and iterate */
93
0
    size_t length = 1;
94
0
    if(!UA_Variant_isScalar(value))
95
0
        length = value->arrayLength;
96
97
0
    uintptr_t data = (uintptr_t)value->data;
98
0
    uintptr_t oldData = (uintptr_t)oldValue->data;
99
0
    UA_UInt32 memSize = value->type->memSize;
100
0
    for(size_t i = 0; i < length; ++i) {
101
0
        if(detectScalarDeadBand((const void*)data, (const void*)oldData,
102
0
                                value->type, deadbandValue))
103
0
            return true;
104
0
        data += memSize;
105
0
        oldData += memSize;
106
0
    }
107
0
    return false;
108
0
}
109
110
static UA_Boolean
111
0
detectValueChange(UA_Server *server, UA_MonitoredItem *mon, const UA_DataValue *dv) {
112
0
    UA_LOCK_ASSERT(&server->serviceMutex);
113
114
    /* Status changes are always reported */
115
0
    if(dv->hasStatus != mon->lastValue.hasStatus ||
116
0
       dv->status != mon->lastValue.status) {
117
0
        return true;
118
0
    }
119
120
    /* Default trigger is Status + Value */
121
0
    UA_DataChangeTrigger trigger = UA_DATACHANGETRIGGER_STATUSVALUE;
122
123
    /* Use the configured trigger */
124
0
    const UA_DataChangeFilter *dcf = NULL;
125
0
    const UA_ExtensionObject *filter = &mon->parameters.filter;
126
0
    if(filter->content.decoded.type == &UA_TYPES[UA_TYPES_DATACHANGEFILTER]) {
127
0
        dcf = (UA_DataChangeFilter*)filter->content.decoded.data;
128
0
        trigger = dcf->trigger;
129
0
    }
130
131
    /* The status was already tested above */
132
0
    if(trigger == UA_DATACHANGETRIGGER_STATUS)
133
0
        return false;
134
135
0
    UA_assert(trigger == UA_DATACHANGETRIGGER_STATUSVALUE ||
136
0
              trigger == UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP);
137
138
    /* Can we compare values? */
139
0
    if(dv->hasValue != mon->lastValue.hasValue)
140
0
       return true;
141
142
    /* Test absolute deadband */
143
0
    if(dcf && dcf->deadbandType == UA_DEADBANDTYPE_ABSOLUTE &&
144
0
       dv->value.type != NULL && UA_DataType_isNumeric(dv->value.type))
145
0
        return detectVariantDeadband(&dv->value, &mon->lastValue.value,
146
0
                                     dcf->deadbandValue);
147
148
    /* Compare the source timestamp if the trigger requires that */
149
0
    if(trigger == UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP) {
150
0
        if(dv->hasSourceTimestamp != mon->lastValue.hasSourceTimestamp)
151
0
            return true;
152
0
        if(dv->hasSourceTimestamp &&
153
0
           dv->sourceTimestamp != mon->lastValue.sourceTimestamp)
154
0
            return true;
155
0
    }
156
157
    /* Has the value changed? */
158
0
    return !UA_equal(&dv->value, &mon->lastValue.value,
159
0
                     &UA_TYPES[UA_TYPES_VARIANT]);
160
0
}
161
162
UA_StatusCode
163
UA_MonitoredItem_createDataChangeNotification(UA_Server *server, UA_MonitoredItem *mon,
164
0
                                              const UA_DataValue *dv) {
165
    /* Copy the value */
166
0
    UA_DataValue valueCopy;
167
0
    UA_StatusCode retval = UA_DataValue_copy(dv, &valueCopy);
168
0
    if(retval != UA_STATUSCODE_GOOD)
169
0
        return retval;
170
171
    /* Allocate a new notification */
172
0
    UA_Notification *n = UA_Notification_new();
173
0
    if(!n) {
174
0
        UA_DataValue_clear(&valueCopy);
175
0
        return UA_STATUSCODE_BADOUTOFMEMORY;
176
0
    }
177
178
    /* Prepare and enqueue the notification */
179
0
    n->mon = mon;
180
0
    n->data.dataChange.value = valueCopy;
181
0
    n->data.dataChange.clientHandle = mon->parameters.clientHandle;
182
0
    UA_Notification_enqueueAndTrigger(server, n);
183
0
    return UA_STATUSCODE_GOOD;
184
0
}
185
186
void
187
UA_MonitoredItem_processSampledValue(UA_Server *server, UA_MonitoredItem *mon,
188
0
                                     UA_DataValue *value) {
189
0
    UA_assert(mon->itemToMonitor.attributeId != UA_ATTRIBUTEID_EVENTNOTIFIER);
190
0
    UA_LOCK_ASSERT(&server->serviceMutex);
191
192
    /* Has the value changed (with the filters applied)? */
193
0
    UA_Boolean changed = detectValueChange(server, mon, value);
194
0
    if(!changed) {
195
0
        UA_LOG_DEBUG_SUBSCRIPTION(server->config.logging, mon->subscription,
196
0
                                  "MonitoredItem %" PRIi32 " | "
197
0
                                  "The value has not changed", mon->monitoredItemId);
198
0
        UA_DataValue_clear(value);
199
0
        return;
200
0
    }
201
202
    /* Prepare a notification and enqueue it */
203
0
    UA_StatusCode res =
204
0
        UA_MonitoredItem_createDataChangeNotification(server, mon, value);
205
0
    if(res != UA_STATUSCODE_GOOD) {
206
0
        UA_LOG_WARNING_SUBSCRIPTION(server->config.logging, mon->subscription,
207
0
                                    "MonitoredItem %" PRIi32 " | "
208
0
                                    "Processing the sample returned the statuscode %s",
209
0
                                    mon->monitoredItemId, UA_StatusCode_name(res));
210
0
        UA_DataValue_clear(value);
211
0
        return;
212
0
    }
213
214
    /* Move/store the value for filter comparison and TransferSubscription */
215
0
    UA_DataValue_clear(&mon->lastValue);
216
0
    mon->lastValue = *value;
217
218
    /* Call the local callback if the MonitoredItem is not attached to a
219
     * subscription. Do this at the very end. Because the callback might delete
220
     * the subscription. */
221
0
    if(!mon->subscription) {
222
0
        UA_LocalMonitoredItem *localMon = (UA_LocalMonitoredItem*) mon;
223
0
        void *nodeContext = NULL;
224
0
        getNodeContext(server, mon->itemToMonitor.nodeId, &nodeContext);
225
0
        localMon->callback.dataChangeCallback(server,
226
0
                                              mon->monitoredItemId, localMon->context,
227
0
                                              &mon->itemToMonitor.nodeId, nodeContext,
228
0
                                              mon->itemToMonitor.attributeId, value);
229
0
    }
230
0
}
231
232
/* We know the result is a deep-copy. So we can abuse the const result-pointer
233
 * and take ownership of the value. */
234
static void
235
processMonitoredItemAsyncRead(UA_Server *server, UA_MonitoredItem *mon,
236
0
                              const UA_DataValue *result) {
237
0
    mon->outstandingAsyncReads--;
238
0
    UA_DataValue *mut_result = (UA_DataValue*)(uintptr_t)result;
239
0
    if(mut_result->status == UA_STATUSCODE_BADREQUESTCANCELLEDBYREQUEST)
240
0
        return; /* Controlled shut-down */
241
0
    UA_MonitoredItem_processSampledValue(server, mon, mut_result);
242
0
    UA_DataValue_init(mut_result);
243
0
}
244
245
void
246
0
UA_MonitoredItem_sample(UA_Server *server, UA_MonitoredItem *mon) {
247
0
    UA_LOCK_ASSERT(&server->serviceMutex);
248
0
    UA_assert(mon->itemToMonitor.attributeId != UA_ATTRIBUTEID_EVENTNOTIFIER);
249
250
0
    UA_Subscription *sub = mon->subscription;
251
0
    UA_LOG_DEBUG_SUBSCRIPTION(server->config.logging, sub, "MonitoredItem %" PRIi32
252
0
                              " | Sample callback called", mon->monitoredItemId);
253
254
    /* Sample the current value.
255
     * sub->session can be NULL when the subscription is detached. Then
256
     * readWithSession returns the error-code BADUSERACCESSDENIED. */
257
0
    UA_Session *session = (sub) ? sub->session : &server->adminSession;
258
259
    /* Read the value possibly asynchronous */
260
0
    UA_StatusCode res = UA_STATUSCODE_BADTOOMANYOPERATIONS;
261
0
    if(UA_LIKELY(mon->outstandingAsyncReads < UA_MONITOREDITEM_ASYNC_MAX)) {
262
0
        res = read_async(server, session, &mon->itemToMonitor, mon->timestampsToReturn,
263
0
                         (UA_ServerAsyncReadResultCallback)processMonitoredItemAsyncRead, mon, 0);
264
0
    }
265
0
    if(res == UA_STATUSCODE_GOOD) {
266
0
        mon->outstandingAsyncReads++;
267
0
    } else {
268
        /* Reading failed, process with the StatusCode */
269
0
        UA_DataValue dv;
270
0
        UA_DataValue_init(&dv);
271
        dv.hasStatus = true;
272
0
        dv.status = res;
273
0
        UA_MonitoredItem_processSampledValue(server, mon, &dv);
274
0
    }
275
0
}
276
277
#endif /* UA_ENABLE_SUBSCRIPTIONS */