Coverage Report

Created: 2026-01-10 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/S2OPC/src/ClientServer/services/b2c/util_event.c
Line
Count
Source
1
/*
2
 * Licensed to Systerel under one or more contributor license
3
 * agreements. See the NOTICE file distributed with this work
4
 * for additional information regarding copyright ownership.
5
 * Systerel licenses this file to you under the Apache
6
 * License, Version 2.0 (the "License"); you may not use this
7
 * file except in compliance with the License. You may obtain
8
 * a copy of the License at
9
 *
10
 *   http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19
20
#include "util_event.h"
21
22
#include "constants.h"
23
#include "opcua_statuscodes.h"
24
#include "sopc_assert.h"
25
#include "sopc_date_time.h"
26
#include "sopc_macros.h"
27
#include "sopc_mem_alloc.h"
28
#include "sopc_toolkit_config_internal.h"
29
#include "util_b2c.h"
30
#include "util_variant.h"
31
32
const SOPC_Server_Event_Types* initEventTypes = NULL;
33
34
static const SOPC_NodeId EventQueueOverflowTypeId = SOPC_NODEID_NS0_NUMERIC(OpcUaId_EventQueueOverflowEventType);
35
static const SOPC_NodeId ServerId = SOPC_NODEID_NS0_NUMERIC(OpcUaId_Server);
36
static const SOPC_NodeId BaseEventTypeId = SOPC_NODEID_NS0_NUMERIC(OpcUaId_BaseEventType);
37
38
static const SOPC_Event* queueOverflowEvent = NULL;
39
40
bool init_event_types(SOPC_EndpointConfigIdx epIdx)
41
0
{
42
0
    if (NULL == initEventTypes)
43
0
    {
44
0
        SOPC_Endpoint_Config* endpointConfig = SOPC_ToolkitServer_GetEndpointConfig(epIdx);
45
0
        SOPC_ASSERT(NULL != endpointConfig && NULL != endpointConfig->serverConfigPtr);
46
0
        SOPC_Server_Config* serverConfig = endpointConfig->serverConfigPtr;
47
0
        initEventTypes = serverConfig->eventTypes;
48
0
    }
49
    // It should not be NULL when EventNotifier attribute is supported (necessary to reach this point).
50
    // But since it is only guaranteed to be initialized when using server wrapper we still have to check it.
51
0
    return NULL != initEventTypes;
52
0
}
53
54
bool util_event__alloc_event_field_list(uint32_t clientHandle,
55
                                        int32_t nbSelectClauses,
56
                                        OpcUa_EventFieldList** ppEventFieldList)
57
0
{
58
0
    SOPC_ASSERT(NULL != ppEventFieldList);
59
0
    bool res = false;
60
0
    OpcUa_EventFieldList* eventFieldList = NULL;
61
0
    SOPC_ReturnStatus status =
62
0
        SOPC_EncodeableObject_Create(&OpcUa_EventFieldList_EncodeableType, (void**) &eventFieldList);
63
0
    if (SOPC_STATUS_OK == status)
64
0
    {
65
0
        eventFieldList->ClientHandle = clientHandle;
66
0
        eventFieldList->EventFields = SOPC_Calloc((size_t) nbSelectClauses, sizeof(*eventFieldList->EventFields));
67
0
        eventFieldList->NoOfEventFields = nbSelectClauses;
68
0
        if (NULL != eventFieldList->EventFields)
69
0
        {
70
0
            res = true;
71
0
            *ppEventFieldList = eventFieldList;
72
0
        }
73
0
        else
74
0
        {
75
0
            SOPC_Free(eventFieldList);
76
0
        }
77
0
    }
78
0
    return res;
79
0
}
80
81
static void applyDataValueTsRemoval(SOPC_DataValue* dv, bool removeSrc, bool removeSrv)
82
0
{
83
0
    SOPC_ASSERT(NULL != dv);
84
0
    if (removeSrc)
85
0
    {
86
0
        dv->SourceTimestamp = 0;
87
0
        dv->SourcePicoSeconds = 0;
88
0
    }
89
0
    if (removeSrv)
90
0
    {
91
0
        dv->ServerTimestamp = 0;
92
0
        dv->ServerPicoSeconds = 0;
93
0
    }
94
0
}
95
96
void util_event__set_event_field_list_elt(char** preferredLocalesIds,
97
                                          const constants__t_TimestampsToReturn_i timestampToReturn,
98
                                          bool userAccessGranted,
99
                                          int32_t selectClauseIdx,
100
                                          const SOPC_InternalMonitoredItemFilterCtx* pFilterCtx,
101
                                          const SOPC_Event* pEvent,
102
                                          OpcUa_EventFieldList* pEventFieldList)
103
0
{
104
0
    SOPC_Variant* selectVal = &pEventFieldList->EventFields[selectClauseIdx];
105
106
    /*
107
     * From part 4 (v1.05) §7.22.3:
108
     * For example, the Server shall set the event field to Bad_UserAccessDenied
109
     * if the value is not accessible to the user associated with the Session.
110
     * If a Value Attribute has an uncertain or bad StatusCode associated with it then the Server
111
     * shall provide the StatusCode instead of the Value Attribute.
112
     */
113
    // eventNotifier attribute is considered not accessible, return a bad status instead of value
114
0
    if (!userAccessGranted)
115
0
    {
116
0
        SOPC_Variant_Initialize(selectVal);
117
0
        selectVal->BuiltInTypeId = SOPC_StatusCode_Id;
118
0
        selectVal->Value.Status = OpcUa_BadUserAccessDenied;
119
0
        return;
120
0
    }
121
122
0
    SOPC_ASSERT(NULL != pFilterCtx && !pFilterCtx->isDataFilter);
123
0
    char* qnPath = pFilterCtx->Filter.Event.qnPathStrSelectClauses[selectClauseIdx];
124
0
    SOPC_NumericRange* indexRange = pFilterCtx->Filter.Event.indexRangeSelectClauses[selectClauseIdx];
125
0
    const SOPC_Variant* eventVar = SOPC_Event_GetVariableFromStrPath(pEvent, qnPath);
126
    // Restricts empty browse path for NodeId attribute
127
    // (other browse paths are for Value attribute only which is already checked on creation)
128
0
    if (NULL != eventVar && '\0' == *qnPath &&
129
0
        SOPC_AttributeId_NodeId != pFilterCtx->Filter.Event.eventFilter->SelectClauses[selectClauseIdx].AttributeId)
130
0
    {
131
0
        eventVar = NULL;
132
0
    }
133
134
0
    if (NULL != eventVar)
135
0
    {
136
0
        SOPC_ReturnStatus status =
137
0
            util_variant__copy_and_apply_locales_and_index_range(selectVal, eventVar, preferredLocalesIds, indexRange);
138
139
        /* Apply the TimestampToReturn in case of DataValue built it type:
140
         * "When monitoring Events, this applies only to Event fields that are of type DataValue.
141
         */
142
0
        if (SOPC_STATUS_OK == status && SOPC_DataValue_Id == selectVal->BuiltInTypeId)
143
0
        {
144
0
            bool removeSrc = false;
145
0
            bool removeSrv = false;
146
0
            switch (timestampToReturn)
147
0
            {
148
0
            case constants__e_ttr_source:
149
0
                removeSrv = true;
150
0
                break;
151
0
            case constants__e_ttr_server:
152
0
                removeSrc = true;
153
0
                break;
154
0
            case constants__e_ttr_neither:
155
0
                removeSrc = true;
156
0
                removeSrv = true;
157
0
                break;
158
0
            default:
159
                // Keep both in other cases
160
0
                break;
161
0
            }
162
163
0
            if (removeSrc || removeSrv)
164
0
            {
165
0
                if (SOPC_VariantArrayType_SingleValue == selectVal->ArrayType)
166
0
                {
167
0
                    applyDataValueTsRemoval(selectVal->Value.DataValue, removeSrc, removeSrv);
168
0
                }
169
0
                else
170
0
                {
171
0
                    int32_t length = SOPC_Variant_GetArrayOrMatrixLength(selectVal);
172
0
                    for (int32_t i = 0; i < length; i++)
173
0
                    {
174
0
                        applyDataValueTsRemoval(SOPC_VARIANT_GET_ARRAY_VALUES_PTR(selectVal, DataValue), removeSrc,
175
0
                                                removeSrv);
176
0
                    }
177
0
                }
178
0
            }
179
0
        }
180
0
    } // else: keep NULL variant selectVal content
181
0
}
182
183
static bool is_event_type_compatible_with_select_clause(const OpcUa_SimpleAttributeOperand* selectClause,
184
                                                        const SOPC_NodeId* eventTypeId)
185
0
{
186
0
    bool res = SOPC_NodeId_Equal(&BaseEventTypeId, &selectClause->TypeDefinitionId);
187
0
    if (!res)
188
0
    {
189
0
        res = SOPC_NodeId_Equal(&selectClause->TypeDefinitionId, eventTypeId);
190
0
    }
191
0
    return res;
192
0
}
193
194
bool util_event__alloc_and_fill_event_field_list(const SOPC_InternalMonitoredItemFilterCtx* pFilterCtx,
195
                                                 uint32_t clientHandle,
196
                                                 char** preferredLocalesIds,
197
                                                 const constants__t_TimestampsToReturn_i timestampToReturn,
198
                                                 bool userAccessGranted,
199
                                                 const SOPC_Event* pEvent,
200
                                                 OpcUa_EventFieldList** ppEventFieldList)
201
0
{
202
0
    SOPC_ASSERT(NULL != pFilterCtx);
203
0
    SOPC_ASSERT(NULL != pEvent);
204
0
    SOPC_ASSERT(NULL != ppEventFieldList);
205
0
    SOPC_ASSERT(!pFilterCtx->isDataFilter);
206
0
    int32_t nbSelectClauses = pFilterCtx->Filter.Event.eventFilter->NoOfSelectClauses;
207
0
    bool res = nbSelectClauses > 0;
208
0
    bool setField = false;
209
0
    if (res)
210
0
    {
211
0
        res = util_event__alloc_event_field_list(clientHandle, nbSelectClauses, ppEventFieldList);
212
0
    }
213
0
    if (res)
214
0
    {
215
0
        const SOPC_NodeId* eventTypeId = SOPC_Event_GetEventTypeId(pEvent);
216
0
        for (int32_t i = 0; i < nbSelectClauses; i++)
217
0
        {
218
0
            setField = is_event_type_compatible_with_select_clause(
219
0
                &pFilterCtx->Filter.Event.eventFilter->SelectClauses[i], eventTypeId);
220
0
            if (setField)
221
0
            {
222
0
                util_event__set_event_field_list_elt(preferredLocalesIds, timestampToReturn, userAccessGranted, i,
223
0
                                                     pFilterCtx, pEvent, *ppEventFieldList);
224
0
            }
225
0
        }
226
0
    }
227
228
0
    return res;
229
0
}
230
231
static SOPC_Event* create_queueOverflowEvent(void)
232
0
{
233
0
    SOPC_ASSERT(NULL != initEventTypes);
234
0
    if (NULL == queueOverflowEvent)
235
0
    {
236
0
        SOPC_Event* QOfEvent = SOPC_EventManager_CreateEventInstance(initEventTypes, &EventQueueOverflowTypeId);
237
0
        if (NULL == QOfEvent)
238
0
        {
239
            // Impossible to instantiate the event type
240
0
            return false;
241
0
        }
242
        /**
243
         * From part 5 (v1.05) §6.4.34:
244
         * The SourceNode Property for Events of this type shall be assigned to the NodeId of the Server
245
         * Object. The SourceName for Events of this type shall be "Internal/EventQueueOverflow".
246
         */
247
0
        SOPC_ReturnStatus status = SOPC_Event_SetSourceNode(QOfEvent, &ServerId);
248
0
        SOPC_String sourceName;
249
0
        SOPC_String_Initialize(&sourceName);
250
0
        if (SOPC_STATUS_OK == status)
251
0
        {
252
0
            status = SOPC_String_AttachFromCstring(&sourceName, "Internal/EventQueueOverflow");
253
0
        }
254
0
        if (SOPC_STATUS_OK == status)
255
0
        {
256
0
            status = SOPC_Event_SetSourceName(QOfEvent, &sourceName);
257
0
        }
258
0
        if (SOPC_STATUS_OK == status)
259
0
        {
260
0
            queueOverflowEvent = QOfEvent;
261
0
        }
262
0
        else
263
0
        {
264
0
            SOPC_Event_Delete(&QOfEvent);
265
0
        }
266
0
    }
267
268
0
    SOPC_Event* event = SOPC_Event_CreateCopy(queueOverflowEvent, true);
269
0
    if (NULL == event)
270
0
    {
271
0
        return NULL;
272
0
    }
273
274
0
    SOPC_ReturnStatus status = SOPC_Event_SetTime(event, SOPC_Time_GetCurrentTimeUTC());
275
    // We still want to generate the created queueOverflowEvent even if it fails
276
0
    SOPC_UNUSED_RESULT(status);
277
0
    return event;
278
0
}
279
280
bool util_event__gen_event_queue_overflow_notification(const SOPC_InternalMonitoredItemFilterCtx* pFilterCtx,
281
                                                       uint32_t clientHandle,
282
                                                       OpcUa_EventFieldList** ppEventFieldList)
283
0
{
284
0
    SOPC_Event* queueOverflowEventInst = create_queueOverflowEvent();
285
0
    if (NULL == queueOverflowEventInst)
286
0
    {
287
0
        return false;
288
0
    }
289
0
    bool res = util_event__alloc_and_fill_event_field_list(pFilterCtx, clientHandle, NULL, constants__e_ttr_both, true,
290
0
                                                           queueOverflowEventInst, ppEventFieldList);
291
0
    SOPC_Event_Delete(&queueOverflowEventInst);
292
0
    return res;
293
0
}