--- a/iec60870/iec60870_runtime.c Mon May 04 07:42:48 2026 +0200
+++ b/iec60870/iec60870_runtime.c Wed May 06 13:37:40 2026 +0200
@@ -82,30 +82,30 @@
*(IEC_BOOL *)p = v ? (IEC_BOOL)1 : (IEC_BOOL)0;
-/* CP24/CP56 helpers for time-tagged ASDUs (wall-clock; PLC does not drive protocol timestamps). */
-static struct sCP56Time2a iec_wall_cp56_storage;
-static struct sCP24Time2a iec_zero_cp24_storage;
-static struct sCP16Time2a iec_zero_cp16_storage;
-static CP56Time2a iec_wall_cp56(void) {
- CP56Time2a t = (CP56Time2a)&iec_wall_cp56_storage;
+/* CP24/CP56 helpers for time-tagged ASDUs (wall-clock; PLC does not drive protocol timestamps). + * Buffers are caller-provided so concurrent iec_make_monitor_io() calls do not share static state. */ +static CP56Time2a iec_wall_cp56_buf(struct sCP56Time2a *buf) { + CP56Time2a t = (CP56Time2a)buf; CP56Time2a_createFromMsTimestamp(t, (uint64_t)time(NULL) * 1000ULL);
-static CP24Time2a iec_zero_cp24(void) {
- memset(&iec_zero_cp24_storage, 0, sizeof(iec_zero_cp24_storage));
- return (CP24Time2a)&iec_zero_cp24_storage;
+static CP24Time2a iec_zero_cp24_buf(struct sCP24Time2a *buf) { + memset(buf, 0, sizeof(*buf)); + return (CP24Time2a)buf; -static CP16Time2a iec_zero_cp16(void) {
- memset(&iec_zero_cp16_storage, 0, sizeof(iec_zero_cp16_storage));
- return (CP16Time2a)&iec_zero_cp16_storage;
+static CP16Time2a iec_zero_cp16_buf(struct sCP16Time2a *buf) { + memset(buf, 0, sizeof(*buf)); + return (CP16Time2a)buf; static InformationObject iec_make_monitor_io(int type_id, int ioa, struct iec60870_binding *b) {
void *mem = malloc((size_t)InformationObject_getMaxSizeInMemory());
QualityDescriptor q = IEC60870_QUALITY_GOOD;
+ struct sCP56Time2a cp56_wall; + struct sCP24Time2a cp24_z; + struct sCP16Time2a cp16_z; struct sBinaryCounterReading bcr_st;
BinaryCounterReading bcr;
@@ -128,11 +128,11 @@
return (InformationObject)SinglePointWithCP24Time2a_create(
(SinglePointWithCP24Time2a)mem, ioa, iec_get_bool_var(b->iec_var), q,
+ iec_zero_cp24_buf(&cp24_z)); return (InformationObject)SinglePointWithCP56Time2a_create(
(SinglePointWithCP56Time2a)mem, ioa, iec_get_bool_var(b->iec_var), q,
+ iec_wall_cp56_buf(&cp56_wall)); DoublePointValue dv = (DoublePointValue)(*(IEC_BYTE *)b->iec_var & (IEC_BYTE)3);
@@ -142,12 +142,12 @@
DoublePointValue dv = (DoublePointValue)(*(IEC_BYTE *)b->iec_var & (IEC_BYTE)3);
return (InformationObject)DoublePointWithCP24Time2a_create(
- (DoublePointWithCP24Time2a)mem, ioa, dv, q, iec_zero_cp24());
+ (DoublePointWithCP24Time2a)mem, ioa, dv, q, iec_zero_cp24_buf(&cp24_z)); DoublePointValue dv = (DoublePointValue)(*(IEC_BYTE *)b->iec_var & (IEC_BYTE)3);
return (InformationObject)DoublePointWithCP56Time2a_create(
- (DoublePointWithCP56Time2a)mem, ioa, dv, q, iec_wall_cp56());
+ (DoublePointWithCP56Time2a)mem, ioa, dv, q, iec_wall_cp56_buf(&cp56_wall)); @@ -156,11 +156,11 @@
return (InformationObject)StepPositionWithCP24Time2a_create(
(StepPositionWithCP24Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, false, q,
+ iec_zero_cp24_buf(&cp24_z)); return (InformationObject)StepPositionWithCP56Time2a_create(
(StepPositionWithCP56Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, false, q,
+ iec_wall_cp56_buf(&cp56_wall)); return (InformationObject)BitString32_createEx(
@@ -168,11 +168,11 @@
return (InformationObject)Bitstring32WithCP24Time2a_createEx(
(Bitstring32WithCP24Time2a)mem, ioa, (uint32_t)*(UDINT *)b->iec_var, q,
+ iec_zero_cp24_buf(&cp24_z)); return (InformationObject)Bitstring32WithCP56Time2a_createEx(
(Bitstring32WithCP56Time2a)mem, ioa, (uint32_t)*(UDINT *)b->iec_var, q,
+ iec_wall_cp56_buf(&cp56_wall)); float nv = NormalizedValue_fromScaled((int)(int16_t)*(IEC_UINT *)b->iec_var);
@@ -182,12 +182,12 @@
float nv = NormalizedValue_fromScaled((int)(int16_t)*(IEC_UINT *)b->iec_var);
return (InformationObject)MeasuredValueNormalizedWithCP24Time2a_create(
- (MeasuredValueNormalizedWithCP24Time2a)mem, ioa, nv, q, iec_zero_cp24());
+ (MeasuredValueNormalizedWithCP24Time2a)mem, ioa, nv, q, iec_zero_cp24_buf(&cp24_z)); float nv = NormalizedValue_fromScaled((int)(int16_t)*(IEC_UINT *)b->iec_var);
return (InformationObject)MeasuredValueNormalizedWithCP56Time2a_create(
- (MeasuredValueNormalizedWithCP56Time2a)mem, ioa, nv, q, iec_wall_cp56());
+ (MeasuredValueNormalizedWithCP56Time2a)mem, ioa, nv, q, iec_wall_cp56_buf(&cp56_wall)); float nv = NormalizedValue_fromScaled((int)(int16_t)*(IEC_UINT *)b->iec_var);
@@ -201,11 +201,11 @@
return (InformationObject)MeasuredValueScaledWithCP24Time2a_create(
(MeasuredValueScaledWithCP24Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, q,
+ iec_zero_cp24_buf(&cp24_z)); return (InformationObject)MeasuredValueScaledWithCP56Time2a_create(
(MeasuredValueScaledWithCP56Time2a)mem, ioa, (int)*(IEC_INT *)b->iec_var, q,
+ iec_wall_cp56_buf(&cp56_wall)); return (InformationObject)MeasuredValueShort_create(
@@ -213,11 +213,11 @@
return (InformationObject)MeasuredValueShortWithCP24Time2a_create(
(MeasuredValueShortWithCP24Time2a)mem, ioa, *(IEC_REAL *)b->iec_var, q,
+ iec_zero_cp24_buf(&cp24_z)); return (InformationObject)MeasuredValueShortWithCP56Time2a_create(
(MeasuredValueShortWithCP56Time2a)mem, ioa, *(IEC_REAL *)b->iec_var, q,
+ iec_wall_cp56_buf(&cp56_wall)); bcr = BinaryCounterReading_create((BinaryCounterReading)&bcr_st,
@@ -227,12 +227,12 @@
bcr = BinaryCounterReading_create((BinaryCounterReading)&bcr_st,
(int32_t)(*(UDINT *)b->iec_var), 0, false, false, false);
return (InformationObject)IntegratedTotalsWithCP24Time2a_create(
- (IntegratedTotalsWithCP24Time2a)mem, ioa, bcr, iec_zero_cp24());
+ (IntegratedTotalsWithCP24Time2a)mem, ioa, bcr, iec_zero_cp24_buf(&cp24_z)); bcr = BinaryCounterReading_create((BinaryCounterReading)&bcr_st,
(int32_t)(*(UDINT *)b->iec_var), 0, false, false, false);
return (InformationObject)IntegratedTotalsWithCP56Time2a_create(
- (IntegratedTotalsWithCP56Time2a)mem, ioa, bcr, iec_wall_cp56());
+ (IntegratedTotalsWithCP56Time2a)mem, ioa, bcr, iec_wall_cp56_buf(&cp56_wall)); pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
@@ -241,20 +241,20 @@
SingleEvent_setEventState(se, (EventState)(pack_u32 & 3u));
SingleEvent_setQDP(se, (QualityDescriptorP)((pack_u32 >> 2) & 0xffu));
return (InformationObject)EventOfProtectionEquipment_create(
- (EventOfProtectionEquipment)mem, ioa, se, iec_zero_cp16(), iec_zero_cp24());
+ (EventOfProtectionEquipment)mem, ioa, se, iec_zero_cp16_buf(&cp16_z), iec_zero_cp24_buf(&cp24_z)); pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
ste = (StartEvent)(pack_u32 & 0xffu);
qdp = (QualityDescriptorP)((pack_u32 >> 8) & 0xffu);
return (InformationObject)PackedStartEventsOfProtectionEquipment_create(
- (PackedStartEventsOfProtectionEquipment)mem, ioa, ste, qdp, iec_zero_cp16(),
+ (PackedStartEventsOfProtectionEquipment)mem, ioa, ste, qdp, iec_zero_cp16_buf(&cp16_z), + iec_zero_cp24_buf(&cp24_z)); pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
oci = (OutputCircuitInfo)(pack_u32 & 0xffu);
qdp = (QualityDescriptorP)((pack_u32 >> 8) & 0xffu);
return (InformationObject)PackedOutputCircuitInfo_create(
- (PackedOutputCircuitInfo)mem, ioa, oci, qdp, iec_zero_cp16(), iec_zero_cp24());
+ (PackedOutputCircuitInfo)mem, ioa, oci, qdp, iec_zero_cp16_buf(&cp16_z), iec_zero_cp24_buf(&cp24_z)); pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
@@ -263,22 +263,22 @@
SingleEvent_setEventState(se, (EventState)(pack_u32 & 3u));
SingleEvent_setQDP(se, (QualityDescriptorP)((pack_u32 >> 2) & 0xffu));
return (InformationObject)EventOfProtectionEquipmentWithCP56Time2a_create(
- (EventOfProtectionEquipmentWithCP56Time2a)mem, ioa, se, iec_zero_cp16(),
+ (EventOfProtectionEquipmentWithCP56Time2a)mem, ioa, se, iec_zero_cp16_buf(&cp16_z), + iec_wall_cp56_buf(&cp56_wall)); pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
ste = (StartEvent)(pack_u32 & 0xffu);
qdp = (QualityDescriptorP)((pack_u32 >> 8) & 0xffu);
return (InformationObject)PackedStartEventsOfProtectionEquipmentWithCP56Time2a_create(
(PackedStartEventsOfProtectionEquipmentWithCP56Time2a)mem, ioa, ste, qdp,
- iec_zero_cp16(), iec_wall_cp56());
+ iec_zero_cp16_buf(&cp16_z), iec_wall_cp56_buf(&cp56_wall)); pack_u32 = (uint32_t)*(UDINT *)b->iec_var;
oci = (OutputCircuitInfo)(pack_u32 & 0xffu);
qdp = (QualityDescriptorP)((pack_u32 >> 8) & 0xffu);
return (InformationObject)PackedOutputCircuitInfoWithCP56Time2a_create(
- (PackedOutputCircuitInfoWithCP56Time2a)mem, ioa, oci, qdp, iec_zero_cp16(),
+ (PackedOutputCircuitInfoWithCP56Time2a)mem, ioa, oci, qdp, iec_zero_cp16_buf(&cp16_z), + iec_wall_cp56_buf(&cp56_wall)); memset(&scd_st, 0, sizeof(scd_st));
@@ -316,7 +316,7 @@
(FileSegment)mem, ioa, 0, 0, &seg_dummy, 0);
return (InformationObject)FileDirectory_create(
- (FileDirectory)mem, ioa, 0, (uint32_t)*(UDINT *)b->iec_var, 0, iec_wall_cp56());
+ (FileDirectory)mem, ioa, 0, (uint32_t)*(UDINT *)b->iec_var, 0, iec_wall_cp56_buf(&cp56_wall)); @@ -584,8 +584,9 @@
if (event == CS104_CON_EVENT_ACTIVATED)
iec_set_bool_var(srv->conn_bool, true);
- else if (event == CS104_CON_EVENT_DEACTIVATED || event == CS104_CON_EVENT_CONNECTION_CLOSED)
- iec_set_bool_var(srv->conn_bool, CS104_Slave_getOpenConnections(srv->slave) > 0);
+ /* Do not call CS104_Slave_getOpenConnections here — it re-enters the slave from inside + * lib60870 connection callbacks and can deadlock with multiple clients. conn_bool is + * refreshed every PLC cycle in __retrieve_*(). */ @@ -605,6 +606,10 @@
CS104_Slave_setLocalAddress(s->slave, s->ip_str);
CS104_Slave_setLocalPort(s->slave, s->port);
CS104_Slave_setMaxOpenConnections(s->slave, s->max_open);
+ /* Each master TCP session is an independent redundancy group. The default SINGLE_REDUNDANCY_GROUP + * shares one app-layer context across all peers; closing one connection can break others + * (e.g. two GiMonitor clients). */ + CS104_Slave_setServerMode(s->slave, CS104_MODE_CONNECTION_IS_REDUNDANCY_GROUP); CS101_AppLayerParameters al = CS104_Slave_getAppLayerParameters(s->slave);