Coverage Report

Created: 2025-10-28 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/S2OPC/src/PubSub/common/sopc_pubsub_helpers.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 "sopc_pubsub_helpers.h"
21
22
#include <string.h>
23
24
#include "sopc_assert.h"
25
#include "sopc_dataset_ll_layer.h"
26
#include "sopc_helper_string.h"
27
#include "sopc_helper_uri.h"
28
#include "sopc_logger.h"
29
#include "sopc_mem_alloc.h"
30
#include "sopc_pubsub_protocol.h"
31
32
0
#define TCP_PREFIX "opc.tcp://"
33
34
static bool SOPC_Internal_PubSubHelpers_ParseAddressUDP(const char* address,
35
                                                        SOPC_Socket_AddressInfo** multicastAddr,
36
                                                        char** port)
37
0
{
38
0
    SOPC_ASSERT(NULL != port);
39
0
    SOPC_ASSERT(NULL != multicastAddr);
40
41
0
    size_t hostLen, portIdx, portLen;
42
0
    bool result = SOPC_Helper_URI_ParseUri_WithPrefix(UADP_PREFIX, address, &hostLen, &portIdx, &portLen);
43
0
    if (result)
44
0
    {
45
0
        char* ip = SOPC_Calloc(hostLen + 1, sizeof(*ip));
46
0
        char* ipRes = NULL;
47
0
        *port = SOPC_Calloc(portLen + 1, sizeof(**port));
48
0
        char* portRes = NULL;
49
0
        if (NULL != ip && NULL != *port)
50
0
        {
51
0
            ipRes = strncpy(ip, address + strlen(UADP_PREFIX), hostLen);
52
0
            portRes = strncpy(*port, address + portIdx, portLen);
53
0
        }
54
55
0
        if (NULL != ip && NULL != *port && ip == ipRes && *port == portRes)
56
0
        {
57
0
            ipRes[hostLen] = '\0';
58
0
            portRes[portLen] = '\0';
59
            // TODO: detect IPv4 / IPv6 addresses ? => force IPv4
60
0
            *multicastAddr = SOPC_UDP_SocketAddress_Create(false, ip, *port);
61
0
        }
62
63
0
        SOPC_Free(ip);
64
0
    }
65
66
0
    return (NULL != *multicastAddr);
67
0
}
68
69
bool SOPC_PubSubHelpers_ParseAddressUDP(const char* address, SOPC_Socket_AddressInfo** multicastAddr)
70
0
{
71
0
    char* port = NULL;
72
0
    bool result = SOPC_Internal_PubSubHelpers_ParseAddressUDP(address, multicastAddr, &port);
73
0
    SOPC_Free(port);
74
0
    return result;
75
0
}
76
77
bool SOPC_Helper_URI_ParseUri_WithPrefix(const char* prefix,
78
                                         const char* uri,
79
                                         size_t* hostnameLength,
80
                                         size_t* portIdx,
81
                                         size_t* portLength)
82
0
{
83
0
    bool result = false;
84
0
    size_t idx = 0;
85
0
    bool isPort = false;
86
0
    bool endOfPort = false;
87
0
    bool hasPort = false;
88
0
    bool hasName = false;
89
0
    bool invalid = false;
90
0
    bool startIPv6 = false;
91
92
0
    if (NULL == prefix || NULL == uri || NULL == hostnameLength || NULL == portIdx || NULL == portLength)
93
0
    {
94
0
        return false;
95
0
    }
96
    // We only accept UADP or MQTT prefix in this function
97
0
    if (0 != strncmp(prefix, UADP_PREFIX, strlen(UADP_PREFIX)) &&
98
0
        0 != strncmp(prefix, MQTT_PREFIX, strlen(MQTT_PREFIX)) && 0 != strncmp(prefix, TCP_PREFIX, strlen(TCP_PREFIX)))
99
0
    {
100
0
        SOPC_Logger_TraceError(SOPC_LOG_MODULE_PUBSUB, "Failed to determine the protocol from prefix '%s'", prefix);
101
0
        return false;
102
0
    }
103
104
0
    const size_t PREFIX_LENGTH = strlen(prefix);
105
0
    *hostnameLength = 0;
106
0
    *portIdx = 0;
107
0
    *portLength = 0;
108
0
    if (strlen(uri) + 4 > TCP_UA_MAX_URL_LENGTH)
109
0
    {
110
        // Encoded value shall be less than 4096 bytes
111
0
        return false;
112
0
    }
113
0
    else if (strlen(uri) <= PREFIX_LENGTH || SOPC_strncmp_ignore_case(uri, prefix, PREFIX_LENGTH) != 0)
114
0
    {
115
0
        return false;
116
0
    }
117
118
    // search for a ':' defining port for given IP
119
    // search for a '/' defining endpoint name for given IP => at least 1 char after it (len - 1)
120
0
    for (idx = PREFIX_LENGTH; idx < strlen(uri) - 1 && !invalid; idx++)
121
0
    {
122
0
        if (false != isPort && false == endOfPort)
123
0
        {
124
0
            if (uri[idx] >= '0' && uri[idx] <= '9')
125
0
            {
126
0
                if (false == hasPort)
127
0
                {
128
                    // port definition
129
0
                    hasPort = true;
130
0
                    *portIdx = idx;
131
0
                }
132
0
            }
133
0
            else if (uri[idx] == '/' && false == invalid)
134
0
            {
135
                // Name of the endpoint after port, invalid otherwise
136
0
                if (false == hasPort)
137
0
                {
138
0
                    invalid = true;
139
0
                }
140
0
                else
141
0
                {
142
0
                    *portLength = idx - *portIdx;
143
0
                    hasName = true;
144
0
                    endOfPort = true; // End of port definition
145
0
                }
146
0
            }
147
0
            else
148
0
            {
149
0
                if (false == hasPort || false == hasName)
150
0
                {
151
                    // unexpected character: we do not expect a endpoint name
152
0
                    invalid = true;
153
0
                }
154
0
            }
155
0
        }
156
0
        else
157
0
        {
158
0
            if (false == endOfPort)
159
0
            {
160
                // Treatment before the port parsing
161
0
                if (uri[idx] == ':' && false == startIPv6)
162
0
                {
163
0
                    *hostnameLength = idx - PREFIX_LENGTH;
164
0
                    isPort = true;
165
0
                }
166
0
                else if (uri[idx] == '[')
167
0
                {
168
0
                    startIPv6 = true;
169
0
                }
170
0
                else if (uri[idx] == ']')
171
0
                {
172
0
                    if (false == startIPv6)
173
0
                    {
174
0
                        invalid = true;
175
0
                    }
176
0
                    else
177
0
                    {
178
0
                        startIPv6 = false;
179
0
                    }
180
0
                }
181
0
            }
182
0
            else if (hasPort)
183
0
            {
184
                // Treatment after the port parsing
185
                // TODO: check absence of forbidden characters
186
0
            }
187
0
        }
188
0
    }
189
190
0
    if (hasPort != false && false == invalid)
191
0
    {
192
0
        result = true;
193
0
        if (*portLength == 0)
194
0
        {
195
            // No endpoint name after port provided
196
0
            *portLength = idx - *portIdx + 1;
197
0
        }
198
0
    }
199
200
0
    return result;
201
0
}
202
203
/* Check if array dimension of src_variant respect limitations of dest_arrayDimensions.*/
204
static bool SOPC_ArrayDimensions_isCompatible(const SOPC_PubSub_ArrayDimension* dest_arrayDimensions,
205
                                              const SOPC_Variant* src_variant)
206
11
{
207
11
    if (NULL == dest_arrayDimensions || NULL == src_variant)
208
0
    {
209
0
        return false;
210
0
    }
211
11
    bool res = true;
212
11
    int32_t src_valueRank = SOPC_Variant_Get_ValueRank(src_variant);
213
214
    // If variant is a scalar nothing to check
215
11
    if (SOPC_VariantArrayType_SingleValue != src_variant->ArrayType)
216
0
    {
217
0
        if (NULL == dest_arrayDimensions->arrayDimensions)
218
0
        {
219
0
            res = false;
220
0
        }
221
0
        else if (dest_arrayDimensions->valueRank == src_valueRank)
222
0
        {
223
0
            if (SOPC_VariantArrayType_Array == src_variant->ArrayType)
224
0
            {
225
0
                res = ((dest_arrayDimensions->arrayDimensions[0] == 0) ||
226
0
                       (src_variant->Value.Array.Length <= (int32_t) dest_arrayDimensions->arrayDimensions[0]));
227
0
            }
228
0
            else
229
0
            {
230
0
                for (int i = 0; res && i < src_valueRank; i++)
231
0
                {
232
                    // Array dimension equal to zero means no up boundary
233
0
                    const uint32_t dimI = dest_arrayDimensions->arrayDimensions[i];
234
0
                    res = ((dimI == 0) || (src_variant->Value.Matrix.ArrayDimensions[i] <= (int32_t) dimI));
235
0
                }
236
0
            }
237
0
        }
238
0
        else
239
0
        {
240
0
            res = false;
241
0
        }
242
0
    }
243
11
    return res;
244
11
}
245
246
bool SOPC_PubSubHelpers_IsCompatibleVariant(const SOPC_FieldMetaData* fieldMetaData,
247
                                            const SOPC_Variant* variant,
248
                                            bool* out_isBad)
249
467
{
250
467
    SOPC_ASSERT(NULL != fieldMetaData);
251
467
    SOPC_ASSERT(NULL != variant);
252
467
    bool res = true;
253
467
    if (NULL != out_isBad)
254
0
    {
255
0
        *out_isBad = false;
256
0
    }
257
258
467
    SOPC_BuiltinId expBuiltInType = SOPC_FieldMetaData_Get_BuiltinType(fieldMetaData);
259
467
    if (SOPC_Null_Id == expBuiltInType)
260
0
    {
261
0
        return true;
262
0
    }
263
264
467
    if (expBuiltInType == variant->BuiltInTypeId)
265
14
    {
266
14
        int32_t expValueRank = SOPC_FieldMetaData_Get_ValueRank(fieldMetaData);
267
14
        int32_t actualValueRank = SOPC_Variant_Get_ValueRank(variant);
268
269
14
        res = SOPC_ValueRank_IsAssignableInto(expValueRank, actualValueRank);
270
14
        if (res)
271
11
        {
272
11
            const SOPC_PubSub_ArrayDimension* expectedArrayDimensions =
273
11
                SOPC_FieldMetaData_Get_ArrayDimension(fieldMetaData);
274
11
            SOPC_ASSERT(NULL != expectedArrayDimensions);
275
11
            res = SOPC_ArrayDimensions_isCompatible(expectedArrayDimensions, variant);
276
11
        }
277
14
        return res;
278
14
    }
279
453
    else if (SOPC_Null_Id == variant->BuiltInTypeId)
280
390
    {
281
        // Consider NULL variant compatible with any type:
282
        // it might be used when status is Bad or misused (NULL usage unclear in OpcUa spec part 6)
283
390
        return true;
284
390
    }
285
63
    else if (SOPC_StatusCode_Id == variant->BuiltInTypeId && (0 != (variant->Value.Status & SOPC_BadStatusMask)))
286
42
    {
287
        // Consider Bad status variant compatible with any value
288
        // (see spec part 14 - 1.05: Table 26: DataSetMessage field representation options)
289
42
        if (NULL != out_isBad)
290
0
        {
291
0
            *out_isBad = true;
292
0
        }
293
        // It shall be a single value that indicate variant status
294
42
        return (SOPC_VariantArrayType_SingleValue == variant->ArrayType);
295
42
    }
296
21
    return false;
297
467
}
298
299
bool SOPC_PubSubHelpers_IsPreencodeCompatibleVariant(const SOPC_FieldMetaData* fieldMetaData,
300
                                                     const SOPC_Variant* variant)
301
0
{
302
0
    SOPC_ASSERT(NULL != fieldMetaData);
303
0
    SOPC_ASSERT(NULL != variant);
304
305
0
    const SOPC_BuiltinId expBuiltInType = SOPC_FieldMetaData_Get_BuiltinType(fieldMetaData);
306
0
    bool res = false;
307
0
    if (expBuiltInType == variant->BuiltInTypeId)
308
0
    {
309
0
        const SOPC_PubSub_ArrayDimension* arrDimension = SOPC_FieldMetaData_Get_ArrayDimension(fieldMetaData);
310
0
        const int32_t actualValueRank = SOPC_Variant_Get_ValueRank(variant);
311
0
        if (NULL != arrDimension)
312
0
        {
313
0
            if (arrDimension->valueRank == actualValueRank)
314
0
            {
315
0
                if (-1 == actualValueRank)
316
0
                {
317
0
                    res = true;
318
0
                }
319
0
                else if (1 == actualValueRank)
320
0
                {
321
0
                    SOPC_ASSERT(NULL != arrDimension->arrayDimensions);
322
0
                    res = ((int32_t) *arrDimension->arrayDimensions == variant->Value.Array.Length);
323
0
                }
324
0
            }
325
0
        }
326
0
    }
327
0
    return res;
328
0
}
329
330
SOPC_ReturnStatus SOPC_Helper_PublisherId_Compare(const SOPC_Conf_PublisherId* pubIdLeft,
331
                                                  const SOPC_Conf_PublisherId* pubIdRight,
332
                                                  bool* comp)
333
0
{
334
0
    if (pubIdLeft == NULL || NULL == pubIdRight || NULL == comp)
335
0
    {
336
0
        return SOPC_STATUS_INVALID_PARAMETERS;
337
0
    }
338
0
    SOPC_ReturnStatus status = SOPC_STATUS_OK;
339
0
    *comp = false;
340
0
    if (pubIdLeft->type == pubIdRight->type)
341
0
    {
342
0
        int32_t match = -1;
343
0
        switch (pubIdLeft->type)
344
0
        {
345
0
        case SOPC_UInteger_PublisherId:
346
0
            if (pubIdLeft->data.uint == pubIdRight->data.uint)
347
0
            {
348
0
                *comp = true;
349
0
            }
350
0
            break;
351
0
        case SOPC_String_PublisherId:
352
0
            status = SOPC_String_Compare(&pubIdLeft->data.string, &pubIdRight->data.string, false, &match);
353
0
            if (SOPC_STATUS_OK == status)
354
0
            {
355
0
                *comp = (0 == match);
356
0
            }
357
0
            break;
358
0
        case SOPC_Null_PublisherId:
359
0
        default:
360
0
            *comp = true;
361
0
            break;
362
0
        }
363
0
    }
364
0
    return status;
365
0
}
366
367
SOPC_Conf_PublisherId SOPC_Helper_Convert_PublisherId(const SOPC_Dataset_LL_PublisherId* src)
368
7.10k
{
369
7.10k
    SOPC_Conf_PublisherId result;
370
7.10k
    if (NULL == src)
371
6.74k
    {
372
6.74k
        static const SOPC_Conf_PublisherId nullPubId = {.type = SOPC_Null_PublisherId};
373
6.74k
        return nullPubId;
374
6.74k
    }
375
376
361
    switch (src->type)
377
361
    {
378
257
    case DataSet_LL_PubId_Byte_Id:
379
257
    {
380
257
        result.type = SOPC_UInteger_PublisherId;
381
257
        result.data.uint = src->data.byte;
382
257
        break;
383
0
    }
384
59
    case DataSet_LL_PubId_UInt16_Id:
385
59
    {
386
59
        result.type = SOPC_UInteger_PublisherId;
387
59
        result.data.uint = src->data.uint16;
388
59
        break;
389
0
    }
390
20
    case DataSet_LL_PubId_UInt32_Id:
391
20
    {
392
20
        result.type = SOPC_UInteger_PublisherId;
393
20
        result.data.uint = src->data.uint32;
394
20
        break;
395
0
    }
396
25
    case DataSet_LL_PubId_UInt64_Id:
397
25
    {
398
25
        result.type = SOPC_UInteger_PublisherId;
399
25
        result.data.uint = src->data.uint64;
400
25
        break;
401
0
    }
402
0
    case DataSet_LL_PubId_String_Id:
403
0
    {
404
0
        result.type = SOPC_String_PublisherId;
405
0
        result.data.string = src->data.string;
406
0
        break;
407
0
    }
408
0
    default:
409
0
        result.type = SOPC_Null_PublisherId;
410
361
    }
411
361
    return result;
412
361
}