• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wirenboard / wb-mqtt-opcua / 1

20 Sep 2026 09:11AM UTC coverage: 66.203% (-0.04%) from 66.246%
1

push

github

sikmir
Fix undefined behaviors

274 of 386 branches covered (70.98%)

9 of 12 new or added lines in 2 files covered. (75.0%)

476 of 719 relevant lines covered (66.2%)

534.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.91
/src/OPCUAServer.cpp
1
#include "OPCUAServer.h"
2

3
#include <cstring>
4
#include <functional>
5
#include <stdexcept>
6
#include <vector>
7

8
#include "log.h"
9

10
#define LOG(logger) ::logger.Log() << "[OPCUA] "
11

12
namespace
13
{
14
    const char* LogCategoryNames[UA_LOGCATEGORIES] = {"network",
15
                                                      "channel",
16
                                                      "session",
17
                                                      "server",
18
                                                      "client",
19
                                                      "userland",
20
                                                      "securitypolicy",
21
                                                      "eventloop",
22
                                                      "pubsub",
23
                                                      "discovery"};
24

25
    static_assert(UA_LOGCATEGORIES == 10, "Update LogCategoryNames for the current open62541 UA_LOGCATEGORIES");
26

27
    void PrintLogMessage(WBMQTT::TLogger& logger, UA_LogCategory category, const char* msg, va_list args)
25,364✔
28
    {
29
        va_list args2;
30
        va_copy(args2, args);
25,364✔
31
        auto bufSize = 1 + vsnprintf(nullptr, 0, msg, args);
25,364✔
32
        std::string str(bufSize, '\0');
50,728✔
33
        vsnprintf(&str[0], bufSize, msg, args2);
25,364✔
34
        va_end(args2);
25,364✔
35
        logger.Log() << "[OPCUA] " << LogCategoryNames[category] << ": " << str;
25,364✔
36
    }
25,364✔
37

38
    extern "C" {
39
    void Log(void* context, UA_LogLevel level, UA_LogCategory category, const char* msg, va_list args)
25,364✔
40
    {
41
        switch (level) {
25,364✔
42
            case UA_LOGLEVEL_TRACE:
25,236✔
43
            case UA_LOGLEVEL_DEBUG:
44
                PrintLogMessage(Debug, category, msg, args);
25,236✔
45
                break;
25,236✔
46
            case UA_LOGLEVEL_INFO:
112✔
47
                PrintLogMessage(Info, category, msg, args);
112✔
48
                break;
112✔
49
            case UA_LOGLEVEL_WARNING:
16✔
50
                PrintLogMessage(Warn, category, msg, args);
16✔
51
                break;
16✔
52
            case UA_LOGLEVEL_ERROR:
×
53
            case UA_LOGLEVEL_FATAL:
54
                PrintLogMessage(Error, category, msg, args);
×
55
                break;
×
56
        }
57
    }
25,364✔
58

59
    void LogClear(UA_Logger* logger)
8✔
60
    {}
8✔
61

62
    UA_StatusCode ReadVariableCallback(UA_Server* sserver,
8✔
63
                                       const UA_NodeId* ssessionId,
64
                                       void* ssessionContext,
65
                                       const UA_NodeId* snodeId,
66
                                       void* snodeContext,
67
                                       UA_Boolean ssourceTimeStamp,
68
                                       const UA_NumericRange* range,
69
                                       UA_DataValue* dataValue)
70
    {
71
        OPCUA::TServerImpl* server = (OPCUA::TServerImpl*)(snodeContext);
8✔
72
        return server->ReadVariable(snodeId, dataValue);
8✔
73
    }
74

75
    UA_StatusCode WriteVariableCallback(UA_Server* server,
×
76
                                        const UA_NodeId* sessionId,
77
                                        void* sessionContext,
78
                                        const UA_NodeId* nodeId,
79
                                        void* nodeContext,
80
                                        const UA_NumericRange* range,
81
                                        const UA_DataValue* data)
82
    {
83
        OPCUA::TServerImpl* s = (OPCUA::TServerImpl*)(nodeContext);
×
84
        return s->WriteVariable(nodeId, data);
×
85
    }
86
    }
87

88
    UA_Logger* GetLogger()
8✔
89
    {
90
        static UA_Logger logger = {Log, nullptr, LogClear};
91
        return &logger;
8✔
92
    }
93

94
    void SetVariableAttributes(UA_VariableAttributes& attr, WBMQTT::PControl control)
4✔
95
    {
96
        attr.accessLevel =
4✔
97
            control->IsReadonly() ? UA_ACCESSLEVELMASK_READ : UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
4✔
98
        attr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)control->GetId().c_str());
4✔
99
        attr.valueRank = UA_VALUERANK_SCALAR;
4✔
100
        attr.dataType = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE);
4✔
101
        try {
102
            auto v = control->GetValue();
4✔
103
            if (v.Is<bool>()) {
4✔
104
                attr.dataType = UA_NODEID_NUMERIC(0, UA_NS0ID_BOOLEAN);
×
105
                return;
×
106
            }
107
            if (v.Is<double>()) {
4✔
108
                attr.dataType = UA_NODEID_NUMERIC(0, UA_NS0ID_DOUBLE);
2✔
109
                return;
2✔
110
            }
111
            return;
2✔
112
        } catch (...) {
4✔
113
        }
×
114
    }
115

116
    void SetServerUrl(UA_ServerConfig* serverCfg, const std::string& url)
×
117
    {
118
        UA_Array_delete(serverCfg->serverUrls, serverCfg->serverUrlsSize, &UA_TYPES[UA_TYPES_STRING]);
×
119
        serverCfg->serverUrls = nullptr;
×
120
        serverCfg->serverUrlsSize = 0;
×
121

122
        auto urls = static_cast<UA_String*>(UA_Array_new(1, &UA_TYPES[UA_TYPES_STRING]));
×
123
        if (!urls) {
×
124
            throw std::runtime_error("OPC UA server URL allocation failed");
×
125
        }
126
        urls[0] = UA_String_fromChars(url.c_str());
×
127
        serverCfg->serverUrls = urls;
×
128
        serverCfg->serverUrlsSize = 1;
×
129
    }
×
130

131
    void ConfigureOpcUaServer(UA_ServerConfig* serverCfg, const OPCUA::TServerConfig& config)
8✔
132
    {
133
        serverCfg->logging = GetLogger();
8✔
134

135
        auto res = UA_ServerConfig_setBasics_withPort(serverCfg, static_cast<UA_UInt16>(config.BindPort));
8✔
136
        if (res != UA_STATUSCODE_GOOD) {
8✔
137
            throw std::runtime_error(std::string("OPC UA server configuration failed: ") + UA_StatusCode_name(res));
×
138
        }
139
        serverCfg->allowEmptyVariables = UA_RULEHANDLING_ACCEPT;
8✔
140

141
        UA_BuildInfo_clear(&serverCfg->buildInfo);
8✔
142
        UA_ApplicationDescription_clear(&serverCfg->applicationDescription);
8✔
143
        serverCfg->applicationDescription.applicationUri = UA_STRING_ALLOC("urn:wb-mqtt-opcua.server.application");
8✔
144
        serverCfg->applicationDescription.productUri = UA_STRING_ALLOC("https://wirenboard.com");
8✔
145
        serverCfg->applicationDescription.applicationName =
146
            UA_LOCALIZEDTEXT_ALLOC("en", "Wiren Board MQTT to OPC UA gateway");
8✔
147
        serverCfg->applicationDescription.applicationType = UA_APPLICATIONTYPE_SERVER;
8✔
148

149
        if (!config.BindIp.empty()) {
8✔
150
            SetServerUrl(serverCfg, "opc.tcp://" + config.BindIp + ":" + std::to_string(config.BindPort));
×
151
        }
152

153
        res = UA_ServerConfig_addSecurityPolicyNone(serverCfg, nullptr);
8✔
154
        if (res != UA_STATUSCODE_GOOD) {
8✔
155
            throw std::runtime_error(std::string("OPC UA security policy addition failed: ") + UA_StatusCode_name(res));
×
156
        }
157

158
        res = UA_AccessControl_default(serverCfg,
16✔
159
                                       true,
160
                                       &serverCfg->securityPolicies[serverCfg->securityPoliciesSize - 1].policyUri,
8✔
161
                                       0,
162
                                       nullptr);
163
        if (res != UA_STATUSCODE_GOOD) {
8✔
164
            throw std::runtime_error(std::string("OPC UA access control configuration failed: ") +
×
165
                                     UA_StatusCode_name(res));
×
166
        }
167

168
        res = UA_ServerConfig_addEndpoint(serverCfg, UA_SECURITY_POLICY_NONE_URI, UA_MESSAGESECURITYMODE_NONE);
8✔
169
        if (res != UA_STATUSCODE_GOOD) {
8✔
170
            throw std::runtime_error(std::string("OPC UA server endpoint allocation failed: ") +
×
171
                                     UA_StatusCode_name(res));
×
172
        }
173
    }
8✔
174

175
    UA_Server* MakeOpcUaServer(const OPCUA::TServerConfig& config)
8✔
176
    {
177
        UA_ServerConfig serverCfg;
178
        memset(&serverCfg, 0, sizeof(serverCfg));
8✔
179
        try {
180
            ConfigureOpcUaServer(&serverCfg, config);
8✔
181
        } catch (...) {
×
182
            UA_ServerConfig_clean(&serverCfg);
×
183
            throw;
×
184
        }
×
185
        auto server = UA_Server_newWithConfig(&serverCfg);
8✔
186
        if (!server) {
8✔
187
            throw std::runtime_error("OPC UA server initilization failed");
×
188
        }
189
        return server;
8✔
190
    }
191

192
    struct TBrowsePathResult
193
    {
194
        UA_BrowsePathResult Result;
195

196
        explicit TBrowsePathResult(UA_BrowsePathResult result): Result(result)
22✔
197
        {}
22✔
198

199
        ~TBrowsePathResult()
22✔
200
        {
201
            UA_BrowsePathResult_clear(&Result);
22✔
202
        }
22✔
203

204
        TBrowsePathResult(const TBrowsePathResult&) = delete;
205
        TBrowsePathResult& operator=(const TBrowsePathResult&) = delete;
206
    };
207

208
}
209

210
namespace OPCUA
211
{
212
    TServerImpl::TServerImpl(const TServerConfig& config, WBMQTT::PDeviceDriver driver)
8✔
213
        : Server(MakeOpcUaServer(config)),
8✔
214
          IsRunning(true),
8✔
215
          Config(config),
8✔
216
          Driver(driver)
16✔
217
    {
218
        Driver->On<WBMQTT::TControlValueEvent>(
16✔
219
            [&](const WBMQTT::TControlValueEvent& event) { ControlValueEventCallback(event); });
14✔
220

221
        // Load external controls
222
        std::vector<std::string> deviceIds;
8✔
223
        for (const auto& device: config.ObjectNodes) {
16✔
224
            LOG(Debug) << "'" << device.first << "' is added to filter";
8✔
225
            deviceIds.emplace_back(device.first);
8✔
226
        }
227
        Driver->SetFilter(WBMQTT::GetDeviceListFilter(deviceIds));
8✔
228
        Driver->WaitForReady();
8✔
229

230
        // Run OPC UA server
231
        ServerThread = std::thread([this]() {
8✔
232
            auto res = UA_Server_run_startup(Server);
8✔
233
            if (res != UA_STATUSCODE_GOOD) {
8✔
NEW
234
                LOG(Error) << UA_StatusCode_name(res);
×
NEW
235
                exit(1);
×
236
            }
237
            while (IsRunning.load()) {
16✔
238
                UA_Server_run_iterate(Server, true);
8✔
239
            }
240
            res = UA_Server_run_shutdown(Server);
8✔
241
            if (res != UA_STATUSCODE_GOOD) {
8✔
242
                LOG(Error) << UA_StatusCode_name(res);
×
243
                exit(1);
×
244
            }
245
        });
16✔
246
    }
8✔
247

248
    TServerImpl::~TServerImpl()
28✔
249
    {
250
        IsRunning.store(false);
16✔
251
        if (ServerThread.joinable()) {
16✔
252
            ServerThread.join();
16✔
253
        }
254
        if (Server) {
16✔
255
            UA_Server_delete(Server);
16✔
256
        }
257
    }
28✔
258

259
    bool TServerImpl::ControlExists(const std::string& nodeName)
18✔
260
    {
261
        std::unique_lock<std::mutex> lock(Mutex);
18✔
262
        return ControlMap.find(nodeName) != ControlMap.end();
36✔
263
    }
18✔
264

265
    void TServerImpl::AddControl(const std::string& nodeName, WBMQTT::PControl control)
6✔
266
    {
267
        std::unique_lock<std::mutex> lock(Mutex);
6✔
268
        ControlMap[nodeName] = control;
6✔
269
    }
6✔
270

271
    void TServerImpl::RemoveControl(const std::string& nodeName)
2✔
272
    {
273
        std::unique_lock<std::mutex> lock(Mutex);
2✔
274
        ControlMap.erase(nodeName);
2✔
275
    }
2✔
276

277
    WBMQTT::PControl TServerImpl::GetControl(const std::string& nodeName)
22✔
278
    {
279
        std::unique_lock<std::mutex> lock(Mutex);
22✔
280
        auto it = ControlMap.find(nodeName);
22✔
281
        return it != ControlMap.end() ? it->second : nullptr;
44✔
282
    }
22✔
283

284
    UA_StatusCode TServerImpl::WriteVariable(const UA_NodeId* snodeId, const UA_DataValue* dataValue)
6✔
285
    {
286
        std::string nodeIdName((const char*)snodeId->identifier.string.data, snodeId->identifier.string.length);
12✔
287
        auto ctrl = GetControl(nodeIdName);
6✔
288
        if (!ctrl || ctrl->IsReadonly()) {
6✔
289
            LOG(Error) << "Variable node '" + nodeIdName + "' writing failed. "
×
290
                       << (ctrl ? "It is read only" : "It is not presented in MQTT");
×
291
            return UA_STATUSCODE_BADDEVICEFAILURE;
×
292
        }
293
        auto tx = Driver->BeginTx();
6✔
294
        try {
295
            if (dataValue->hasValue) {
6✔
296
                if (UA_Variant_hasScalarType(&dataValue->value, &UA_TYPES[UA_TYPES_BOOLEAN])) {
6✔
297
                    auto value = *(UA_Boolean*)dataValue->value.data;
×
298
                    ctrl->SetValue(tx, value).Sync();
×
299
                    LOG(Info) << "Variable node '" + nodeIdName + "' = " << value;
×
300
                    return UA_STATUSCODE_GOOD;
×
301
                }
302
                if (UA_Variant_hasScalarType(&dataValue->value, &UA_TYPES[UA_TYPES_DOUBLE])) {
6✔
303
                    auto value = *(UA_Double*)dataValue->value.data;
×
304
                    ctrl->SetValue(tx, value).Sync();
×
305
                    LOG(Info) << "Variable node '" + nodeIdName + "' = " << value;
×
306
                    return UA_STATUSCODE_GOOD;
×
307
                }
308
                if (UA_Variant_hasScalarType(&dataValue->value, &UA_TYPES[UA_TYPES_STRING])) {
6✔
309
                    // UA_String is not null-terminated, its length must be used explicitly.
310
                    // An empty string has data pointing to UA_EMPTY_ARRAY_SENTINEL, not to a buffer.
311
                    auto rawValue = (const UA_String*)dataValue->value.data;
6✔
312
                    std::string value;
6✔
313
                    if (rawValue->length) {
6✔
314
                        value.assign((const char*)rawValue->data, rawValue->length);
2✔
315
                    }
316
                    ctrl->SetRawValue(tx, value).Sync();
6✔
317
                    LOG(Info) << "Variable node '" + nodeIdName + "' = " << value;
6✔
318
                    return UA_STATUSCODE_GOOD;
6✔
319
                }
6✔
320
            }
321
            return UA_STATUSCODE_BADDATATYPEIDUNKNOWN;
×
322
        } catch (const std::exception& e) {
×
323
            LOG(Error) << "Variable node '" + nodeIdName + "' write error: " << e.what();
×
324
            return UA_STATUSCODE_BADDEVICEFAILURE;
×
325
        }
×
326
    }
6✔
327

328
    UA_StatusCode TServerImpl::ReadVariable(const UA_NodeId* snodeId, UA_DataValue* dataValue)
8✔
329
    {
330
        std::string nodeIdName((const char*)snodeId->identifier.string.data, snodeId->identifier.string.length);
16✔
331
        auto ctrl = GetControl(nodeIdName);
8✔
332
        if (!ctrl) {
8✔
333
            LOG(Error) << "Control is not found '" + nodeIdName + "'";
×
334
            dataValue->hasStatus = true;
×
335
            dataValue->status = UA_STATUSCODE_BADNOCOMMUNICATION;
×
336
            return UA_STATUSCODE_GOOD;
×
337
        }
338
        try {
339
            dataValue->hasStatus = true;
8✔
340
            if (ctrl->GetError().find("r") != std::string::npos) {
8✔
341
                dataValue->status = UA_STATUSCODE_BAD;
×
342
            } else {
343
                dataValue->status = UA_STATUSCODE_GOOD;
8✔
344
            }
345
            auto v = ctrl->GetValue();
8✔
346
            if (v.Is<bool>()) {
8✔
347
                auto value = v.As<bool>();
×
348
                UA_Variant_setScalarCopy(&dataValue->value, &value, &UA_TYPES[UA_TYPES_BOOLEAN]);
×
349
            } else {
350
                if (v.Is<double>()) {
8✔
351
                    auto value = v.As<double>();
4✔
352
                    UA_Variant_setScalarCopy(&dataValue->value, &value, &UA_TYPES[UA_TYPES_DOUBLE]);
4✔
353
                } else {
354
                    UA_String stringValue = UA_String_fromChars((char*)v.As<std::string>().c_str());
4✔
355
                    UA_Variant_setScalarCopy(&dataValue->value, &stringValue, &UA_TYPES[UA_TYPES_STRING]);
4✔
356
                    UA_String_clear(&stringValue);
4✔
357
                }
358
            }
359
            dataValue->hasValue = true;
8✔
360
        } catch (const std::exception& e) {
8✔
361
            LOG(Error) << "Variable node '" + nodeIdName + "' read error: " << e.what();
×
362
            dataValue->hasStatus = true;
×
363
            dataValue->status = UA_STATUSCODE_BADNOCOMMUNICATION;
×
364
        }
×
365
        return UA_STATUSCODE_GOOD;
8✔
366
    }
8✔
367

368
    void TServerImpl::ControlValueEventCallback(const WBMQTT::TControlValueEvent& event)
22✔
369
    {
370
        if (event.RawValue.empty()) {
22✔
371
            return;
6✔
372
        }
373
        auto it = Config.ObjectNodes.find(event.Control->GetDevice()->GetId());
18✔
374
        if (it == Config.ObjectNodes.end()) {
18✔
375
            return;
×
376
        }
377
        std::string nodeName = it->first + "/" + event.Control->GetId();
18✔
378
        if (ControlExists(nodeName)) {
18✔
379
            return;
2✔
380
        }
381
        try {
382
            auto browseName = UA_QUALIFIEDNAME(1, (char*)it->first.c_str());
16✔
383
            TBrowsePathResult parentBpr(
384
                UA_Server_browseSimplifiedBrowsePath(Server,
385
                                                     UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
386
                                                     1,
387
                                                     &browseName));
16✔
388
            auto parentNodeId = parentBpr.Result.statusCode == UA_STATUSCODE_GOOD
16✔
389
                                    ? parentBpr.Result.targets[0].targetId.nodeId
16✔
390
                                    : CreateObjectNode(it->first);
16✔
391
            for (auto& valueNode: it->second) {
26✔
392
                if (valueNode.DeviceControlPair != nodeName) {
16✔
393
                    continue;
10✔
394
                }
395
                browseName = UA_QUALIFIEDNAME(1, (char*)event.Control->GetId().c_str());
6✔
396
                TBrowsePathResult childBpr(UA_Server_browseSimplifiedBrowsePath(Server, parentNodeId, 1, &browseName));
6✔
397
                if (childBpr.Result.statusCode != UA_STATUSCODE_GOOD) {
6✔
398
                    AddControl(nodeName, event.Control);
6✔
399
                    try {
400
                        CreateVariableNode(parentNodeId, nodeName, event.Control);
8✔
401
                    } catch (...) {
2✔
402
                        RemoveControl(nodeName);
2✔
403
                        throw;
2✔
404
                    }
2✔
405
                    break;
4✔
406
                }
407
            }
6✔
408
        } catch (const std::exception& e) {
18✔
409
            LOG(Error) << "Failed to add control '" << nodeName << "': " << e.what();
2✔
410
        }
2✔
411
    }
18✔
412

413
    UA_NodeId TServerImpl::CreateObjectNode(const std::string& nodeName)
8✔
414
    {
415
        UA_NodeId nodeId = UA_NODEID_STRING(1, (char*)nodeName.c_str());
8✔
416
        UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
8✔
417
        oAttr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)nodeName.c_str());
8✔
418
        auto res = UA_Server_addObjectNode(Server,
16✔
419
                                           nodeId,
420
                                           UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
421
                                           UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
422
                                           UA_QUALIFIEDNAME(1, (char*)nodeName.c_str()),
8✔
423
                                           UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
424
                                           oAttr,
425
                                           nullptr,
426
                                           nullptr);
427
        if (res != UA_STATUSCODE_GOOD) {
8✔
428
            throw std::runtime_error("Object node '" + nodeName + "' creation failed: " + UA_StatusCode_name(res));
×
429
        }
430
        return nodeId;
16✔
431
    }
432

433
    void TServerImpl::CreateVariableNode(const UA_NodeId& parentNodeId,
4✔
434
                                         const std::string& nodeName,
435
                                         WBMQTT::PControl control)
436
    {
437
        UA_VariableAttributes oAttr = UA_VariableAttributes_default;
4✔
438
        SetVariableAttributes(oAttr, control);
4✔
439

440
        UA_DataSource dataSource;
441
        dataSource.read = ReadVariableCallback;
4✔
442
        dataSource.write = WriteVariableCallback;
4✔
443

444
        auto res = UA_Server_addDataSourceVariableNode(Server,
8✔
445
                                                       UA_NODEID_STRING(1, (char*)nodeName.c_str()),
4✔
446
                                                       parentNodeId,
447
                                                       UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
448
                                                       UA_QUALIFIEDNAME(1, (char*)control->GetId().c_str()),
4✔
449
                                                       UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
450
                                                       oAttr,
451
                                                       dataSource,
452
                                                       this,
453
                                                       nullptr);
454
        if (res != UA_STATUSCODE_GOOD) {
4✔
455
            throw std::runtime_error("Variable node '" + nodeName + "' creation failed: " + UA_StatusCode_name(res));
×
456
        }
457
    }
4✔
458

459
    std::unique_ptr<IServer> MakeServer(const TServerConfig& config, WBMQTT::PDeviceDriver driver)
×
460
    {
461
        return std::unique_ptr<IServer>(new TServerImpl(config, driver));
×
462
    }
463
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc