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

wirenboard / wb-mqtt-iec104 / 38

31 Jul 2026 10:43AM UTC coverage: 52.549% (-0.2%) from 52.705%
38

push

github

web-flow
Use exported vars instead of MAKEFLAGS for coverage options (#30)

236 of 389 branches covered (60.67%)

402 of 765 relevant lines covered (52.55%)

3.58 hits per line

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

0.0
/src/IEC104Server.cpp
1
#include "IEC104Server.h"
2

3
#include <functional>
4
#include <stdexcept>
5

6
#include "cs104_slave.h"
7
#include "iec60870_slave.h"
8

9
#include "hal_thread.h"
10
#include "hal_time.h"
11

12
#include "log.h"
13

14
using namespace std::chrono;
15

16
#define LOG(logger) ::logger.Log() << "[IEC] "
17

18
namespace
19
{
20
    class TServerImpl: public IEC104::IServer
21
    {
22
        CS104_Slave Slave;
23
        CS101_AppLayerParameters AppLayerParameters;
24
        uint32_t CommonAddress;
25
        IEC104::IHandler* Handler;
26

27
    public:
28
        TServerImpl(const IEC104::TServerConfig& config);
29
        ~TServerImpl();
30

31
        void Stop();
32
        void SendSpontaneous(const IEC104::TInformationObjects& objs);
33
        void SetHandler(IEC104::IHandler* handler);
34

35
        bool IsReadyToAcceptConnections() const;
36
        bool HandleAsdu(IMasterConnection connection, CS101_ASDU asdu);
37
        void HandleConnectionEvent(IMasterConnection connection, CS104_PeerConnectionEvent event);
38
        void HandleInterrogationRequest(IMasterConnection connection, CS101_ASDU asdu, int qoi);
39
    };
40

41
    extern "C" {
42
    bool RequestConnectionHandler(void* parameter, const char* ipAddress)
×
43
    {
44
        return ((TServerImpl*)parameter)->IsReadyToAcceptConnections();
×
45
    }
46

47
    void ConnectionEventHandler(void* parameter, IMasterConnection connection, CS104_PeerConnectionEvent event)
×
48
    {
49
        return ((TServerImpl*)parameter)->HandleConnectionEvent(connection, event);
×
50
    }
51

52
    bool AsduHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu)
×
53
    {
54
        return ((TServerImpl*)parameter)->HandleAsdu(connection, asdu);
×
55
    }
56

57
    bool ClockSyncHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, CP56Time2a newTime)
×
58
    {
59
        // Do nothing. Just for compatibility with OPC servers
60
        return true;
×
61
    }
62

63
    bool InterrogationHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, uint8_t qoi)
×
64
    {
65
        ((TServerImpl*)parameter)->HandleInterrogationRequest(connection, asdu, qoi);
×
66
        return true;
×
67
    }
68
    }
69

70
    CS101_ASDU Append(InformationObject io,
×
71
                      CS101_ASDU asdu,
72
                      CS101_AppLayerParameters appLayerParameters,
73
                      int commonAddress,
74
                      CS101_CauseOfTransmission cot,
75
                      std::function<void(CS101_ASDU)> sendFn)
76
    {
77
        if (!CS101_ASDU_addInformationObject(asdu, io)) {
×
78
            sendFn(asdu);
×
79
            CS101_ASDU_destroy(asdu);
×
80
            asdu = CS101_ASDU_create(appLayerParameters, false, cot, 0, commonAddress, false, false);
×
81
            if (!CS101_ASDU_addInformationObject(asdu, io)) {
×
82
                LOG(Warn) << "Can't add information object with address " << InformationObject_getObjectAddress(io)
×
83
                          << " to ASDU";
×
84
            }
85
        }
86
        InformationObject_destroy(io);
×
87
        return asdu;
×
88
    }
89

90
    InformationObject CreateInformationObject(const IEC104::TSinglePointInformationObject& obj)
×
91
    {
92
        return (InformationObject)SinglePointInformation_create(NULL, obj.Address, obj.Value, IEC60870_QUALITY_GOOD);
×
93
    }
94

95
    InformationObject CreateInformationObject(const IEC104::TSinglePointInformationObjectWithTimestamp& obj)
×
96
    {
97
        sCP56Time2a timestamp;
98
        CP56Time2a_createFromMsTimestamp(&timestamp,
×
99
                                         duration_cast<milliseconds>(obj.Timestamp.time_since_epoch()).count());
×
100
        return (InformationObject)
101
            SinglePointWithCP56Time2a_create(NULL, obj.Address, obj.Value, IEC60870_QUALITY_GOOD, &timestamp);
×
102
    }
103

104
    InformationObject CreateInformationObject(const IEC104::TMeasuredValueScaledInformationObject& obj)
×
105
    {
106
        return (InformationObject)MeasuredValueScaled_create(NULL, obj.Address, obj.Value, IEC60870_QUALITY_GOOD);
×
107
    }
108

109
    InformationObject CreateInformationObject(const IEC104::TMeasuredValueScaledInformationObjectWithTimestamp& obj)
×
110
    {
111
        sCP56Time2a timestamp;
112
        CP56Time2a_createFromMsTimestamp(&timestamp,
×
113
                                         duration_cast<milliseconds>(obj.Timestamp.time_since_epoch()).count());
×
114
        return (InformationObject)
115
            MeasuredValueScaledWithCP56Time2a_create(NULL, obj.Address, obj.Value, IEC60870_QUALITY_GOOD, &timestamp);
×
116
    }
117

118
    InformationObject CreateInformationObject(const IEC104::TMeasuredValueShortInformationObject& obj)
×
119
    {
120
        return (InformationObject)MeasuredValueShort_create(NULL, obj.Address, obj.Value, IEC60870_QUALITY_GOOD);
×
121
    }
122

123
    InformationObject CreateInformationObject(const IEC104::TMeasuredValueShortInformationObjectWithTimestamp& obj)
×
124
    {
125
        sCP56Time2a timestamp;
126
        CP56Time2a_createFromMsTimestamp(&timestamp,
×
127
                                         duration_cast<milliseconds>(obj.Timestamp.time_since_epoch()).count());
×
128
        return (InformationObject)
129
            MeasuredValueShortWithCP56Time2a_create(NULL, obj.Address, obj.Value, IEC60870_QUALITY_GOOD, &timestamp);
×
130
    }
131

132
    void Send(CS101_AppLayerParameters appLayerParameters,
×
133
              int commonAddress,
134
              CS101_CauseOfTransmission cot,
135
              const IEC104::TInformationObjects& objs,
136
              std::function<void(CS101_ASDU)> sendFn)
137
    {
138
        CS101_ASDU asdu = CS101_ASDU_create(appLayerParameters, false, cot, 0, commonAddress, false, false);
×
139

140
        for (const auto& val: objs.SinglePoint) {
×
141
            asdu = Append(CreateInformationObject(val), asdu, appLayerParameters, commonAddress, cot, sendFn);
×
142
        }
143
        for (const auto& val: objs.MeasuredValueShort) {
×
144
            asdu = Append(CreateInformationObject(val), asdu, appLayerParameters, commonAddress, cot, sendFn);
×
145
        }
146
        for (const auto& val: objs.MeasuredValueScaled) {
×
147
            asdu = Append(CreateInformationObject(val), asdu, appLayerParameters, commonAddress, cot, sendFn);
×
148
        }
149
        for (const auto& val: objs.SinglePointWithTimestamp) {
×
150
            asdu = Append(CreateInformationObject(val), asdu, appLayerParameters, commonAddress, cot, sendFn);
×
151
        }
152
        for (const auto& val: objs.MeasuredValueShortWithTimestamp) {
×
153
            asdu = Append(CreateInformationObject(val), asdu, appLayerParameters, commonAddress, cot, sendFn);
×
154
        }
155
        for (const auto& val: objs.MeasuredValueScaledWithTimestamp) {
×
156
            asdu = Append(CreateInformationObject(val), asdu, appLayerParameters, commonAddress, cot, sendFn);
×
157
        }
158

159
        if (CS101_ASDU_getPayloadSize(asdu)) {
×
160
            sendFn(asdu);
×
161
        }
162
        CS101_ASDU_destroy(asdu);
×
163
    }
×
164

165
    void HandleCommand(CS101_ASDU asdu,
×
166
                       IMasterConnection connection,
167
                       IEC104::IHandler* handler,
168
                       std::function<std::string(InformationObject io)> fn)
169
    {
170
        if (CS101_ASDU_getCOT(asdu) == CS101_COT_ACTIVATION) {
×
171
            InformationObject io = CS101_ASDU_getElement(asdu, 0);
×
172
            CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_CON);
×
173
            if (!handler->SetParameter(InformationObject_getObjectAddress(io), fn(io))) {
×
174
                CS101_ASDU_setNegative(asdu, true);
×
175
            }
176
            InformationObject_destroy(io);
×
177
        } else {
178
            CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_COT);
×
179
        }
180
        IMasterConnection_sendASDU(connection, asdu);
×
181
    }
×
182

183
    TServerImpl::TServerImpl(const IEC104::TServerConfig& config): CommonAddress(config.CommonAddress), Handler(nullptr)
×
184
    {
185
        Slave = CS104_Slave_create(100, 100);
×
186

187
        CS104_Slave_setLocalAddress(Slave, config.BindIp.empty() ? "0.0.0.0" : config.BindIp.c_str());
×
188

189
        AppLayerParameters = CS104_Slave_getAppLayerParameters(Slave);
×
190

191
        CS104_Slave_setConnectionRequestHandler(Slave, RequestConnectionHandler, this);
×
192
        CS104_Slave_setConnectionEventHandler(Slave, ConnectionEventHandler, this);
×
193
        CS104_Slave_setASDUHandler(Slave, AsduHandler, this);
×
194
        CS104_Slave_setClockSyncHandler(Slave, ClockSyncHandler, NULL);
×
195
        CS104_Slave_setInterrogationHandler(Slave, InterrogationHandler, this);
×
196

197
        // Set server mode to allow multiple clients using the application layer
198
        CS104_Slave_setServerMode(Slave, CS104_MODE_CONNECTION_IS_REDUNDANCY_GROUP);
×
199

200
        CS104_Slave_start(Slave);
×
201

202
        if (CS104_Slave_isRunning(Slave) == false) {
×
203
            CS104_Slave_destroy(Slave);
×
204
            throw std::runtime_error("starting IEC 60870-5-104 server failed");
×
205
        }
206
    }
×
207

208
    TServerImpl::~TServerImpl()
×
209
    {
210
        TServerImpl::Stop();
×
211
        CS104_Slave_destroy(Slave);
×
212
    }
×
213

214
    void TServerImpl::Stop()
×
215
    {
216
        if (CS104_Slave_isRunning(Slave) == true) {
×
217
            CS104_Slave_stop(Slave);
×
218
        }
219
    }
×
220

221
    void TServerImpl::SendSpontaneous(const IEC104::TInformationObjects& objs)
×
222
    {
223
        if (CS104_Slave_isRunning(Slave) == false) {
×
224
            throw std::runtime_error("IEC 60870-5-104 is not running");
×
225
        }
226

227
        Send(AppLayerParameters, CommonAddress, CS101_COT_SPONTANEOUS, objs, [&](CS101_ASDU asdu) {
×
228
            CS104_Slave_enqueueASDU(Slave, asdu);
×
229
        });
×
230
    }
×
231

232
    bool TServerImpl::IsReadyToAcceptConnections() const
×
233
    {
234
        return (Handler != nullptr);
×
235
    }
236

237
    void TServerImpl::SetHandler(IEC104::IHandler* handler)
×
238
    {
239
        if (Handler != nullptr) {
×
240
            throw std::runtime_error("IIEC104Handler can be set only once");
×
241
        }
242
        Handler = handler;
×
243
    }
×
244

245
    bool TServerImpl::HandleAsdu(IMasterConnection connection, CS101_ASDU asdu)
×
246
    {
247
        if (!Handler) {
×
248
            LOG(Debug) << "Got ASDU, no handler";
×
249
            return false;
×
250
        }
251
        auto asduType = CS101_ASDU_getTypeID(asdu);
×
252
        if (Debug.IsEnabled()) {
×
253
            LOG(Debug) << "Got ASDU: " << TypeID_toString(asduType)
×
254
                       << ", COT: " << CS101_CauseOfTransmission_toString(CS101_ASDU_getCOT(asdu));
×
255
        }
256
        switch (asduType) {
×
257
            case C_SC_NA_1: // Single command
×
258
            case C_SC_TA_1: // Single command with timestamp
259
                HandleCommand(asdu, connection, Handler, [](InformationObject io) {
×
260
                    return (SingleCommand_getState((SingleCommand)io) ? "1" : "0");
×
261
                });
262
                return true;
×
263
            case C_SE_NB_1: // Measured value scaled command
×
264
            case C_SE_TB_1: // Measured value scaled command with timestamp
265
                HandleCommand(asdu, connection, Handler, [](InformationObject io) {
×
266
                    return std::to_string(MeasuredValueScaled_getValue((MeasuredValueScaled)io));
×
267
                });
268
                return true;
×
269
            case C_SE_NC_1: // Measured value short command
×
270
            case C_SE_TC_1: // Measured value short command with timestamp
271
                HandleCommand(asdu, connection, Handler, [](InformationObject io) {
×
272
                    return std::to_string(MeasuredValueShort_getValue((MeasuredValueShort)io));
×
273
                });
274
                return true;
×
275
            default:
×
276
                break;
×
277
        }
278
        return false;
×
279
    }
280

281
    void TServerImpl::HandleConnectionEvent(IMasterConnection connection, CS104_PeerConnectionEvent event)
×
282
    {
283
        char addrBuf[24] = {0};
×
284
        IMasterConnection_getPeerAddress(connection, addrBuf, sizeof(addrBuf) - 1);
×
285
        switch (event) {
×
286
            case CS104_CON_EVENT_CONNECTION_OPENED:
×
287
                LOG(Info) << "Connection opened " << addrBuf;
×
288
                break;
×
289
            case CS104_CON_EVENT_CONNECTION_CLOSED:
×
290
                LOG(Info) << "Connection closed " << addrBuf;
×
291
                break;
×
292
            case CS104_CON_EVENT_DEACTIVATED:
×
293
                LOG(Info) << "Connection deactivated " << addrBuf;
×
294
                break;
×
295
            case CS104_CON_EVENT_ACTIVATED: {
×
296
                LOG(Info) << "Connection activated " << addrBuf;
×
297
                Send(AppLayerParameters,
×
298
                     CommonAddress,
×
299
                     CS101_COT_SPONTANEOUS,
300
                     Handler->GetInformationObjectsValues(),
×
301
                     [&](CS101_ASDU asdu) { CS104_Slave_enqueueASDU(Slave, asdu); });
×
302
                break;
×
303
            }
304
        }
305
    }
×
306

307
    void TServerImpl::HandleInterrogationRequest(IMasterConnection connection, CS101_ASDU incomimgAsdu, int qoi)
×
308
    {
309
        if (qoi == IEC60870_QOI_STATION) { /* only handle station interrogation */
×
310
            IMasterConnection_sendACT_CON(connection, incomimgAsdu, false);
×
311

312
            Send(AppLayerParameters,
×
313
                 CommonAddress,
×
314
                 CS101_COT_INTERROGATED_BY_STATION,
315
                 Handler->GetInformationObjectsValues(),
×
316
                 [&](CS101_ASDU asdu) { IMasterConnection_sendASDU(connection, asdu); });
×
317

318
            IMasterConnection_sendACT_TERM(connection, incomimgAsdu);
×
319
        } else {
320
            char addrBuf[24] = {0};
×
321
            IMasterConnection_getPeerAddress(connection, addrBuf, sizeof(addrBuf) - 1);
×
322
            LOG(Warn) << addrBuf << " unsupported interrogation qoi=" << qoi;
×
323
            IMasterConnection_sendACT_CON(connection, incomimgAsdu, true);
×
324
        }
325
    }
×
326
}
327

328
std::unique_ptr<IEC104::IServer> IEC104::MakeServer(const IEC104::TServerConfig& config)
×
329
{
330
    return std::unique_ptr<IEC104::IServer>(new TServerImpl(config));
×
331
}
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