diff --git a/src/iec61850/inc/iec61850_server.h b/src/iec61850/inc/iec61850_server.h index e69f2c0e..b2953226 100644 --- a/src/iec61850/inc/iec61850_server.h +++ b/src/iec61850/inc/iec61850_server.h @@ -3,7 +3,7 @@ * * IEC 61850 server API for libiec61850. * - * Copyright 2013-2020 Michael Zillgith + * Copyright 2013-2022 Michael Zillgith * * This file is part of libIEC61850. * @@ -1534,6 +1534,42 @@ IedServer_updateCtlModel(IedServer self, DataObject* ctlObject, ControlModel val /**@}*/ +/** + * @defgroup IEC61850_SERVER_RCB Server side report control block (RCB) handling + * + * @{ + */ + +typedef enum { + RCB_EVENT_GET_PARAMETER, /* << parameter read by client (not implemented) */ + RCB_EVENT_SET_PARAMETER, /* << parameter set by client */ + RCB_EVENT_UNRESERVED, /* << RCB reservation canceled */ + RCB_EVENT_RESERVED, /* << RCB reserved */ + RCB_EVENT_ENABLE, /* << RCB enabled */ + RCB_EVENT_DISABLE, /* << RCB disabled */ + RCB_EVENT_GI, /* << GI report triggered */ + RCB_EVENT_PURGEBUF /* << Purge buffer procedure executed */ +} IedServer_RCBEventType; + +/** + * \brief Callback that is called in case of RCB event + * + * \param parameter user provided paramter + */ +typedef void (*IedServer_RCBEventHandler) (void* parameter, ReportControlBlock* rcb, ClientConnection connection, IedServer_RCBEventType event, const char* parameterName, MmsDataAccessError serviceError); + +/** + * \brief Set a handler for report control block (RCB) events + * + * \param self the instance of IedServer to operate on. + * \param handler the event handler to be used + * \param parameter a user provided parameter that is passed to the handler. + */ +LIB61850_API void +IedServer_setRCBEventHandler(IedServer self, IedServer_RCBEventHandler handler, void* parameter); + +/**@}*/ + /** * @defgroup IEC61850_SERVER_SVCB Server side sampled values control block (SVCB) handling * diff --git a/src/iec61850/inc_private/mms_mapping_internal.h b/src/iec61850/inc_private/mms_mapping_internal.h index b73618f6..c61c237e 100644 --- a/src/iec61850/inc_private/mms_mapping_internal.h +++ b/src/iec61850/inc_private/mms_mapping_internal.h @@ -1,7 +1,7 @@ /* * mms_mapping_internal.h * - * Copyright 2013-2020 Michael Zillgith + * Copyright 2013-2022 Michael Zillgith * * This file is part of libIEC61850. * @@ -328,6 +328,9 @@ struct sMmsMapping { IedConnectionIndicationHandler connectionIndicationHandler; void* connectionIndicationHandlerParameter; + + IedServer_RCBEventHandler rcbEventHandler; + void* rcbEventHandlerParameter; }; #endif /* MMS_MAPPING_INTERNAL_H_ */ diff --git a/src/iec61850/server/impl/ied_server.c b/src/iec61850/server/impl/ied_server.c index a158caf8..4c287e77 100644 --- a/src/iec61850/server/impl/ied_server.c +++ b/src/iec61850/server/impl/ied_server.c @@ -584,6 +584,13 @@ IedServer_createWithTlsSupport(IedModel* dataModel, TLSConfiguration tlsConfigur return IedServer_createWithConfig(dataModel, tlsConfiguration, NULL); } +void +IedServer_setRCBEventHandler(IedServer self, IedServer_RCBEventHandler handler, void* parameter) +{ + self->mmsMapping->rcbEventHandler = handler; + self->mmsMapping->rcbEventHandlerParameter = parameter; +} + void IedServer_destroy(IedServer self) { diff --git a/src/iec61850/server/mms_mapping/reporting.c b/src/iec61850/server/mms_mapping/reporting.c index 780ba258..919ae346 100644 --- a/src/iec61850/server/mms_mapping/reporting.c +++ b/src/iec61850/server/mms_mapping/reporting.c @@ -1,7 +1,7 @@ /* * reporting.c * - * Copyright 2013-2020 Michael Zillgith + * Copyright 2013-2022 Michael Zillgith * * This file is part of libIEC61850. * @@ -1675,6 +1675,8 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme bool resvTmsAccess = false; /* access is to RecvTms or Resv */ bool dontUpdate = false; + ClientConnection clientConnection = private_IedServer_getClientConnectionByHandle(self->iedServer, connection); + /* check reservation timeout for buffered RCBs */ if (rc->buffered) { @@ -1713,6 +1715,10 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme rc->reserved = true; rc->clientConnection = connection; rc->reservationTimeout = Hal_getTimeInMs() + (rc->resvTms * 1000); + + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_RESERVED, NULL, DATA_ACCESS_ERROR_SUCCESS); + } } else { if (DEBUG_IED_SERVER) @@ -1735,6 +1741,7 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (rc->reserved == false) { if ((strcmp(elementName, "Resv")) && (strcmp(elementName, "ResvTms"))) { retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; } } @@ -1742,6 +1749,7 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if ((rc->reserved) && (rc->clientConnection != connection)) { retVal = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + goto exit_function; } @@ -1764,6 +1772,7 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (rc->enabled == true) { retVal = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + goto exit_function; } @@ -1803,6 +1812,11 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme MmsValue_setUint32(sqNum, 0U); retVal = DATA_ACCESS_ERROR_SUCCESS; + + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_ENABLE, NULL, DATA_ACCESS_ERROR_SUCCESS); + } + goto exit_function; } else { @@ -1816,6 +1830,7 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (((rc->enabled) || (rc->reserved)) && (rc->clientConnection != connection)) { retVal = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + goto exit_function; } @@ -1833,6 +1848,14 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme /* clear report buffer */ purgeBuf(rc); + + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_GI, NULL, DATA_ACCESS_ERROR_SUCCESS); + } + } + + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_DISABLE, NULL, DATA_ACCESS_ERROR_SUCCESS); } rc->enabled = false; @@ -1849,10 +1872,16 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme } retVal = DATA_ACCESS_ERROR_SUCCESS; + + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_GI, NULL, DATA_ACCESS_ERROR_SUCCESS); + } + goto exit_function; } else { retVal = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + goto exit_function; } } @@ -1861,6 +1890,7 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if ((rc->reserved) && (rc->clientConnection != connection)) { retVal = DATA_ACCESS_ERROR_TEMPORARILY_UNAVAILABLE; + goto exit_function; } @@ -1870,6 +1900,11 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme rc->reserved = value->value.boolean; if (rc->reserved == true) { + + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_RESERVED, NULL, DATA_ACCESS_ERROR_SUCCESS); + } + updateOwner(rc, connection); rc->clientConnection = connection; } @@ -1888,6 +1923,11 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (MmsValue_getBoolean(value) == true) { purgeBuf(rc); retVal = DATA_ACCESS_ERROR_SUCCESS; + + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_PURGEBUF, NULL, DATA_ACCESS_ERROR_SUCCESS); + } + goto exit_function; } } @@ -1900,15 +1940,21 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (updateReportDataset(self, rc, value, connection)) { - if (rc->buffered) + if (rc->buffered) { purgeBuf(rc); + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_PURGEBUF, NULL, DATA_ACCESS_ERROR_SUCCESS); + } + } + MmsValue_update(datSet, value); increaseConfRev(rc); } else { retVal = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + goto exit_function; } } @@ -1927,6 +1973,10 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (rc->buffered) { rc->nextIntgReportTime = 0; purgeBuf(rc); + + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_PURGEBUF, NULL, DATA_ACCESS_ERROR_SUCCESS); + } } } @@ -1938,9 +1988,14 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (!MmsValue_equals(trgOps, value)) { MmsValue_update(trgOps, value); - if (rc->buffered) + if (rc->buffered) { purgeBuf(rc); + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_PURGEBUF, NULL, DATA_ACCESS_ERROR_SUCCESS); + } + } + refreshTriggerOptions(rc); } @@ -1949,7 +2004,9 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme else if (strcmp(elementName, "EntryID") == 0) { if (MmsValue_getOctetStringSize(value) != 8) { + retVal = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + goto exit_function; } @@ -1957,7 +2014,9 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (!checkReportBufferForEntryID(rc, value)) { rc->reportBuffer->isOverflow = true; + retVal = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + goto exit_function; } @@ -1981,9 +2040,14 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (!MmsValue_equals(bufTm, value)) { MmsValue_update(bufTm, value); - if (rc->buffered) + if (rc->buffered) { purgeBuf(rc); + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_PURGEBUF, NULL, DATA_ACCESS_ERROR_SUCCESS); + } + } + refreshBufferTime(rc); } @@ -1995,8 +2059,13 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (!MmsValue_equals(rptId, value)) { MmsValue_update(rptId, value); - if (rc->buffered) + if (rc->buffered) { purgeBuf(rc); + + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_PURGEBUF, NULL, DATA_ACCESS_ERROR_SUCCESS); + } + } } goto exit_function; @@ -2017,11 +2086,19 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme rc->reservationTimeout = 0; rc->reserved = false; updateOwner(rc, NULL); + + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_UNRESERVED, NULL, DATA_ACCESS_ERROR_SUCCESS); + } } else { rc->reservationTimeout = Hal_getTimeInMs() + (rc->resvTms * 1000); reserveRcb(rc, connection); + + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_RESERVED, NULL, DATA_ACCESS_ERROR_SUCCESS); + } } MmsValue* resvTmsVal = ReportControl_getRCBValue(rc, "ResvTms"); @@ -2029,18 +2106,22 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme if (resvTmsVal != NULL) MmsValue_update(resvTmsVal, value); } - else + else { retVal = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + } } else { if (self->iedServer->edition < IEC_61850_EDITION_2_1) { - retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; } else { rc->reservationTimeout = Hal_getTimeInMs() + (RESV_TMS_IMPLICIT_VALUE * 1000); reserveRcb(rc, connection); + + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_RESERVED, NULL, DATA_ACCESS_ERROR_SUCCESS); + } } } @@ -2049,18 +2130,22 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme } else if (strcmp(elementName, "ConfRev") == 0) { retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; } else if (strcmp(elementName, "SqNum") == 0) { retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; } else if (strcmp(elementName, "Owner") == 0) { retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; } else if (strcmp(elementName, "TimeofEntry") == 0) { retVal = DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED; + goto exit_function; } @@ -2073,6 +2158,7 @@ Reporting_RCBWriteAccessHandler(MmsMapping* self, ReportControl* rc, char* eleme } else { retVal = DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID; + goto exit_function; } @@ -2118,6 +2204,10 @@ exit_function: updateGenericTrackingObjectValues(self, rc, IEC61850_SERVICE_TYPE_SET_URCB_VALUES, retVal); #endif /* (CONFIG_IEC61850_SERVICE_TRACKING == 1) */ + if (self->rcbEventHandler) { + self->rcbEventHandler(self->rcbEventHandlerParameter, rc->rcb, clientConnection, RCB_EVENT_SET_PARAMETER, elementName, retVal); + } + return retVal; }