diff --git a/CMakeLists.txt b/CMakeLists.txt index cab1dff1..e6b0160e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,7 @@ project(libiec61850) ENABLE_TESTING() set(LIB_VERSION_MAJOR "1") -set(LIB_VERSION_MINOR "1") +set(LIB_VERSION_MINOR "2") set(LIB_VERSION_PATCH "0") set(LIB_VERSION "${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}") @@ -46,16 +46,35 @@ set(CONFIG_REPORTING_DEFAULT_REPORT_BUFFER_SIZE "8000" CACHE STRING "Default buf # advanced options option(DEBUG "Enable debugging mode (include assertions)" OFF) -option(DEBUG_SOCKET "Enable printf debugging for socket layer" OFF) -option(DEBUG_COTP "Enable COTP printf debugging" OFF) -option(DEBUG_ISO_SERVER "Enable ISO SERVER printf debugging" OFF) -option(DEBUG_ISO_CLIENT "Enable ISO CLIENT printf debugging" OFF) -option(DEBUG_IED_SERVER "Enable IED SERVER printf debugging" OFF) -option(DEBUG_IED_CLIENT "Enable IED CLIENT printf debugging" OFF) -option(DEBUG_MMS_SERVER "Enable MMS SERVER printf debugging" OFF) -option(DEBUG_MMS_CLIENT "Enable MMS CLIENT printf debugging" OFF) -#mark_as_advanced(DEBUG DEBUG_COTP DEBUG_ISO_SERVER DEBUG_ISO_CLIENT DEBUG_IED_SERVER -# DEBUG_IED_CLIENT DEBUG_MMS_SERVER DEBUG_MMS_CLIENT) +option(DEBUG_SOCKET "Enable printf debugging for socket layer" ${DEBUG}) +option(DEBUG_COTP "Enable COTP printf debugging" ${DEBUG}) +option(DEBUG_ISO_SERVER "Enable ISO SERVER printf debugging" ${DEBUG}) +option(DEBUG_ISO_CLIENT "Enable ISO CLIENT printf debugging" ${DEBUG}) +option(DEBUG_IED_SERVER "Enable IED SERVER printf debugging" ${DEBUG}) +option(DEBUG_IED_CLIENT "Enable IED CLIENT printf debugging" ${DEBUG}) +option(DEBUG_MMS_SERVER "Enable MMS SERVER printf debugging" ${DEBUG}) +option(DEBUG_MMS_CLIENT "Enable MMS CLIENT printf debugging" ${DEBUG}) +option(DEBUG_GOOSE_SUBSCRIBER "Enable GOOSE subscriber printf debugging" ${DEBUG}) +option(DEBUG_GOOSE_PUBLISHER "Enable GOOSE publisher printf debugging" ${DEBUG}) +option(DEBUG_SV_SUBSCRIBER "Enable Sampled Values subscriber debugging" ${DEBUG}) +option(DEBUG_SV_PUBLISHER "Enable Sampled Values publisher debugging" ${DEBUG}) +option(DEBUG_HAL_ETHERNET "Enable Ethernet HAL printf debugging" ${DEBUG}) + +#mark_as_advanced( +# DEBUG_SOCKET +# DEBUG_COTP +# DEBUG_ISO_SERVER +# DEBUG_ISO_CLIENT +# DEBUG_IED_SERVER +# DEBUG_IED_CLIENT +# DEBUG_MMS_SERVER +# DEBUG_MMS_CLIENT +# DEBUG_GOOSE_SUBSCRIBER +# DEBUG_GOOSE_PUBLISHER +# DEBUG_SV_SUBSCRIBER +# DEBUG_SV_PUBLISHER +# DEBUG_HAL_ETHERNET +#) include_directories( ${CMAKE_CURRENT_BINARY_DIR}/config diff --git a/src/goose/goose_receiver.c b/src/goose/goose_receiver.c index c99e0426..d18dccd8 100644 --- a/src/goose/goose_receiver.c +++ b/src/goose/goose_receiver.c @@ -129,6 +129,10 @@ parseAllData(uint8_t* buffer, int allDataLength, MmsValue* dataSetValues) MmsValue* value = MmsValue_getElement(dataSetValues, elementIndex); bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength); + if (bufPos < 0) { + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); + return 0; + } if (bufPos + elementLength > allDataLength) { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: sub element is too large!\n"); @@ -277,6 +281,10 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength); + if (bufPos < 0) { + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); + return 0; + } if (bufPos + elementLength > allDataLength) { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: sub element is too large!\n"); @@ -331,6 +339,10 @@ parseAllDataUnknownValue(GooseSubscriber self, uint8_t* buffer, int allDataLengt uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, allDataLength); + if (bufPos < 0) { + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); + return 0; + } if (bufPos + elementLength > allDataLength) { if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: sub element is too large!\n"); @@ -461,6 +473,10 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) if (buffer[bufPos++] == 0x61) { int gooseLength; bufPos = BerDecoder_decodeLength(buffer, &gooseLength, bufPos, apduLength); + if (bufPos < 0) { + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); + return 0; + } int gooseEnd = bufPos + gooseLength; @@ -469,6 +485,10 @@ parseGoosePayload(GooseReceiver self, uint8_t* buffer, int apduLength) uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, apduLength); + if (bufPos < 0) { + if (DEBUG_GOOSE_SUBSCRIBER) printf("GOOSE_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); + return 0; + } if (bufPos + elementLength > apduLength) { if (DEBUG_GOOSE_SUBSCRIBER) @@ -774,7 +794,7 @@ GooseReceiver_destroy(GooseReceiver self) /*************************************** * Functions for non-threaded operation ***************************************/ -void +EthernetSocket GooseReceiver_startThreadless(GooseReceiver self) { if (self->interfaceId == NULL) @@ -788,6 +808,8 @@ GooseReceiver_startThreadless(GooseReceiver self) } else self->running = false; + + return self->ethSocket; } void @@ -811,10 +833,3 @@ GooseReceiver_tick(GooseReceiver self) else return false; } - -void -GooseReceiver_addHandleSet(GooseReceiver self, EthernetHandleSet handles) -{ - return EthernetHandleSet_addSocket(handles, self->ethSocket); -} - diff --git a/src/goose/goose_receiver.h b/src/goose/goose_receiver.h index 7d854ae0..4995daea 100644 --- a/src/goose/goose_receiver.h +++ b/src/goose/goose_receiver.h @@ -30,6 +30,7 @@ extern "C" { #include +typedef struct sEthernetSocket* EthernetSocket; /** * \addtogroup goose_api_group @@ -116,7 +117,7 @@ GooseReceiver_destroy(GooseReceiver self); /*************************************** * Functions for non-threaded operation ***************************************/ -void +EthernetSocket GooseReceiver_startThreadless(GooseReceiver self); void @@ -134,20 +135,6 @@ GooseReceiver_stopThreadless(GooseReceiver self); bool GooseReceiver_tick(GooseReceiver self); -/* Forward declaration */ -typedef struct sEthernetHandleSet* EthernetHandleSet; - -/** - * \brief Add the receiver to a handleset for multiplexed asynchronous IO. - * - * Note: This function must only be called after GooseReceiver_startThreadless(). - * - * \param[in] self The SVReceiver instance. - * \param[inout] handles The EthernetHandleSet to which the EthernetSocket of this receiver should be added. - */ -void -GooseReceiver_addHandleSet(GooseReceiver self, EthernetHandleSet handles); - /**@}*/ #ifdef __cplusplus diff --git a/src/sampled_values/sv_publisher.c b/src/sampled_values/sv_publisher.c index c6c178d5..578a1826 100644 --- a/src/sampled_values/sv_publisher.c +++ b/src/sampled_values/sv_publisher.c @@ -43,8 +43,8 @@ #define SV_MAX_MESSAGE_SIZE 1518 struct sSVPublisher_ASDU { - char* svID; - char* datset; + const char* svID; + const char* datset; int dataSize; bool hasRefrTm; @@ -296,7 +296,7 @@ SVPublisher_create(CommParameters* parameters, const char* interfaceId) } SVPublisher_ASDU -SVPublisher_addASDU(SVPublisher self, char* svID, char* datset, uint32_t confRev) +SVPublisher_addASDU(SVPublisher self, const char* svID, const char* datset, uint32_t confRev) { SVPublisher_ASDU newAsdu = (SVPublisher_ASDU) GLOBAL_CALLOC(1, sizeof(struct sSVPublisher_ASDU)); @@ -398,8 +398,10 @@ SVPublisher_ASDU_encodeToBuffer(SVPublisher_ASDU self, uint8_t* buffer, int bufP buffer[bufPos++] = self->smpSynch; /* SmpRate */ - bufPos = BerEncoder_encodeTL(0x86, 2, buffer, bufPos); - bufPos = encodeUInt16FixedSize(self->smpRate, buffer, bufPos); + if (self->hasSmpRate) { + bufPos = BerEncoder_encodeTL(0x86, 2, buffer, bufPos); + bufPos = encodeUInt16FixedSize(self->smpRate, buffer, bufPos); + } /* Sample */ bufPos = BerEncoder_encodeTL(0x87, self->dataSize, buffer, bufPos); @@ -410,7 +412,7 @@ SVPublisher_ASDU_encodeToBuffer(SVPublisher_ASDU self, uint8_t* buffer, int bufP /* SmpMod */ if (self->hasSmpMod) { - bufPos = BerEncoder_encodeTL(0x88, 4, buffer, bufPos); + bufPos = BerEncoder_encodeTL(0x88, 2, buffer, bufPos); bufPos = encodeUInt16FixedSize(self->smpMod, buffer, bufPos); } diff --git a/src/sampled_values/sv_publisher.h b/src/sampled_values/sv_publisher.h index 50a9ed6e..5ea8676b 100644 --- a/src/sampled_values/sv_publisher.h +++ b/src/sampled_values/sv_publisher.h @@ -85,7 +85,7 @@ SVPublisher_create(CommParameters* parameters, const char* interfaceId); * \return the new ASDU instance. */ SVPublisher_ASDU -SVPublisher_addASDU(SVPublisher self, char* svID, char* datset, uint32_t confRev); +SVPublisher_addASDU(SVPublisher self, const char* svID, const char* datset, uint32_t confRev); /** * \brief Prepare the publisher for publishing. diff --git a/src/sampled_values/sv_subscriber.c b/src/sampled_values/sv_subscriber.c index c17d05b8..573fc39e 100644 --- a/src/sampled_values/sv_subscriber.c +++ b/src/sampled_values/sv_subscriber.c @@ -70,20 +70,20 @@ struct sSVSubscriber { struct sSVSubscriber_ASDU { char* svId; + char* datSet; uint8_t* smpCnt; uint8_t* confRev; uint8_t* refrTm; uint8_t* smpSynch; - + uint8_t* smpMod; + uint8_t* smpRate; int dataBufferLength; uint8_t* dataBuffer; }; - - SVReceiver SVReceiver_create(void) { @@ -208,7 +208,7 @@ SVReceiver_destroy(SVReceiver self) GLOBAL_FREEMEM(self); } -void +EthernetSocket SVReceiver_startThreadless(SVReceiver self) { if (self->interfaceId == NULL) @@ -219,6 +219,8 @@ SVReceiver_startThreadless(SVReceiver self) Ethernet_setProtocolFilter(self->ethSocket, ETH_P_SV); self->running = true; + + return self->ethSocket; } void @@ -229,29 +231,26 @@ SVReceiver_stopThreadless(SVReceiver self) self->running = false; } -void -SVReceiver_addHandleSet(SVReceiver self, EthernetHandleSet handles) -{ - return EthernetHandleSet_addSocket(handles, self->ethSocket); -} - static void parseASDU(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, int length) { int bufPos = 0; + int svIdLength = 0; + int datSetLength = 0; struct sSVSubscriber_ASDU asdu; memset(&asdu, 0, sizeof(struct sSVSubscriber_ASDU)); - int svIdLength = 0; - - while (bufPos < length) { int elementLength; uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, length); + if (bufPos < 0) { + if (DEBUG_SV_SUBSCRIBER) printf("SV_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); + return; + } switch (tag) { @@ -260,6 +259,11 @@ parseASDU(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, int length) svIdLength = elementLength; break; + case 0x81: + asdu.datSet = (char*) (buffer + bufPos); + datSetLength = elementLength; + break; + case 0x82: asdu.smpCnt = buffer + bufPos; break; @@ -276,12 +280,21 @@ parseASDU(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, int length) asdu.smpSynch = buffer + bufPos; break; + case 0x86: + asdu.smpRate = buffer + bufPos; + break; + case 0x87: asdu.dataBuffer = buffer + bufPos; asdu.dataBufferLength = elementLength; break; + case 0x88: + asdu.smpMod = buffer + bufPos; + break; + default: /* ignore unknown tag */ + if (DEBUG_SV_SUBSCRIBER) printf("SV_SUBSCRIBER: found unknown tag %02x\n", tag); break; } @@ -290,6 +303,25 @@ parseASDU(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, int length) if (asdu.svId != NULL) asdu.svId[svIdLength] = 0; + if (asdu.datSet != NULL) + asdu.datSet[datSetLength] = 0; + + if (DEBUG_SV_SUBSCRIBER) { + printf("SV_SUBSCRIBER: SV ASDU: ----------------\n"); + printf("SV_SUBSCRIBER: DataLength: %d\n", asdu.dataBufferLength); + printf("SV_SUBSCRIBER: SvId: %s\n", asdu.svId); + printf("SV_SUBSCRIBER: SmpCnt: %d\n", SVSubscriber_ASDU_getSmpCnt(&asdu)); + printf("SV_SUBSCRIBER: ConfRev: %d\n", SVSubscriber_ASDU_getConfRev(&asdu)); + + if (SVSubscriber_ASDU_hasDatSet(&asdu)) + printf("SV_SUBSCRIBER: DatSet: %s\n", asdu.datSet); + if (SVSubscriber_ASDU_hasRefrTm(&asdu)) + printf("SV_SUBSCRIBER: RefrTm: %lu\n", SVSubscriber_ASDU_getRefrTmAsMs(&asdu)); + if (SVSubscriber_ASDU_hasSmpMod(&asdu)) + printf("SV_SUBSCRIBER: SmpMod: %d\n", SVSubscriber_ASDU_getSmpMod(&asdu)); + if (SVSubscriber_ASDU_hasSmpRate(&asdu)) + printf("SV_SUBSCRIBER: SmpRate: %d\n", SVSubscriber_ASDU_getSmpRate(&asdu)); + } /* Call callback handler */ if (subscriber->listener != NULL) @@ -307,6 +339,10 @@ parseSequenceOfASDU(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, i uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, length); + if (bufPos < 0) { + if (DEBUG_SV_SUBSCRIBER) printf("SV_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); + return; + } switch (tag) { case 0x30: @@ -314,6 +350,7 @@ parseSequenceOfASDU(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, i break; default: /* ignore unknown tag */ + if (DEBUG_SV_SUBSCRIBER) printf("SV_SUBSCRIBER: found unknown tag %02x\n", tag); break; } @@ -330,6 +367,10 @@ parseSVPayload(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, int ap int elementLength; bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, apduLength); + if (bufPos < 0) { + if (DEBUG_SV_SUBSCRIBER) printf("SV_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); + return; + } int svEnd = bufPos + elementLength; @@ -337,6 +378,10 @@ parseSVPayload(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, int ap uint8_t tag = buffer[bufPos++]; bufPos = BerDecoder_decodeLength(buffer, &elementLength, bufPos, svEnd); + if (bufPos < 0) { + if (DEBUG_SV_SUBSCRIBER) printf("SV_SUBSCRIBER: Malformed message: failed to decode BER length tag!\n"); + return; + } if (bufPos + elementLength > apduLength) { if (DEBUG_SV_SUBSCRIBER) @@ -358,6 +403,7 @@ parseSVPayload(SVReceiver self, SVSubscriber subscriber, uint8_t* buffer, int ap break; default: /* ignore unknown tag */ + if (DEBUG_SV_SUBSCRIBER) printf("SV_SUBSCRIBER: found unknown tag %02x\n", tag); break; } @@ -585,6 +631,23 @@ SVSubscriber_ASDU_hasRefrTm(SVSubscriber_ASDU self) return (self->refrTm != NULL); } +bool +SVSubscriber_ASDU_hasDatSet(SVSubscriber_ASDU self) +{ + return (self->datSet != NULL); +} + +bool +SVSubscriber_ASDU_hasSmpRate(SVSubscriber_ASDU self) +{ + return (self->smpRate != NULL); +} + +bool +SVSubscriber_ASDU_hasSmpMod(SVSubscriber_ASDU self) +{ + return (self->smpMod != NULL); +} const char* SVSubscriber_ASDU_getSvId(SVSubscriber_ASDU self) @@ -592,6 +655,12 @@ SVSubscriber_ASDU_getSvId(SVSubscriber_ASDU self) return self->svId; } +const char* +SVSubscriber_ASDU_getDatSet(SVSubscriber_ASDU self) +{ + return self->datSet; +} + uint32_t SVSubscriber_ASDU_getConfRev(SVSubscriber_ASDU self) { @@ -606,6 +675,28 @@ SVSubscriber_ASDU_getConfRev(SVSubscriber_ASDU self) return retVal; } +uint8_t +SVSubscriber_ASDU_getSmpMod(SVSubscriber_ASDU self) +{ + uint8_t retVal = *((uint8_t*) (self->smpMod)); + + return retVal; +} + +uint16_t +SVSubscriber_ASDU_getSmpRate(SVSubscriber_ASDU self) +{ + uint16_t retVal = *((uint16_t*) (self->smpRate)); + +#if (ORDER_LITTLE_ENDIAN == 1) + uint8_t* buf = (uint8_t*) (&retVal); + + BerEncoder_revertByteOrder(buf, 2); +#endif + + return retVal; +} + int8_t SVSubscriber_ASDU_getINT8(SVSubscriber_ASDU self, int index) { diff --git a/src/sampled_values/sv_subscriber.h b/src/sampled_values/sv_subscriber.h index bc62bf94..ef5ab875 100644 --- a/src/sampled_values/sv_subscriber.h +++ b/src/sampled_values/sv_subscriber.h @@ -1,5 +1,5 @@ /* - * sv_subscriber_api.h + * sv_subscriber.h * * Copyright 2015 Michael Zillgith * @@ -30,6 +30,8 @@ extern "C" { #endif +typedef struct sEthernetSocket* EthernetSocket; + /** * \defgroup sv_subscriber_api_group IEC 61850 Sampled Values (SV) subscriber API * @@ -204,7 +206,7 @@ SVReceiver_destroy(SVReceiver self); * Functions for non-threaded operation ***************************************/ -void +EthernetSocket SVReceiver_startThreadless(SVReceiver self); void @@ -222,20 +224,6 @@ SVReceiver_stopThreadless(SVReceiver self); bool SVReceiver_tick(SVReceiver self); -/* Forward declaration */ -typedef struct sEthernetHandleSet* EthernetHandleSet; - -/** - * \brief Add the receiver to a handleset for multiplexed asynchronous IO. - * - * Note: This function must only be called after SVReceiver_startThreadless(). - * - * \param[in] self The SVReceiver instance. - * \param[inout] handles The EthernetHandleSet to which the EthernetSocket of this receiver should be added. - */ -void -SVReceiver_addHandleSet(SVReceiver self, EthernetHandleSet handles); - /* * Subscriber */ @@ -289,6 +277,14 @@ SVSubscriber_ASDU_getSmpCnt(SVSubscriber_ASDU self); const char* SVSubscriber_ASDU_getSvId(SVSubscriber_ASDU self); +/** + * \brief return the DatSet value included in the SV ASDU + * + * \param self ASDU object instance + */ +const char* +SVSubscriber_ASDU_getDatSet(SVSubscriber_ASDU self); + /** * \brief return the ConfRev value included in the SV ASDU * @@ -297,6 +293,32 @@ SVSubscriber_ASDU_getSvId(SVSubscriber_ASDU self); uint32_t SVSubscriber_ASDU_getConfRev(SVSubscriber_ASDU self); +/** + * \brief return the SmpMod value included in the SV ASDU + * + * \param self ASDU object instance + */ +uint8_t +SVSubscriber_ASDU_getSmpMod(SVSubscriber_ASDU self); + +/** + * \brief return the SmpRate value included in the SV ASDU + * + * \param self ASDU object instance + */ +uint16_t +SVSubscriber_ASDU_getSmpRate(SVSubscriber_ASDU self); + +/** + * \brief Check if DatSet value is included in the SV ASDU + * + * \param self ASDU object instance + * + * \return true if DatSet value is present, false otherwise + */ +bool +SVSubscriber_ASDU_hasDatSet(SVSubscriber_ASDU self); + /** * \brief Check if RefrTm value is included in the SV ASDU * @@ -307,6 +329,26 @@ SVSubscriber_ASDU_getConfRev(SVSubscriber_ASDU self); bool SVSubscriber_ASDU_hasRefrTm(SVSubscriber_ASDU self); +/** + * \brief Check if SmpMod value is included in the SV ASDU + * + * \param self ASDU object instance + * + * \return true if SmpMod value is present, false otherwise + */ +bool +SVSubscriber_ASDU_hasSmpMod(SVSubscriber_ASDU self); + +/** + * \brief Check if SmpRate value is included in the SV ASDU + * + * \param self ASDU object instance + * + * \return true if SmpRate value is present, false otherwise + */ +bool +SVSubscriber_ASDU_hasSmpRate(SVSubscriber_ASDU self); + /** * \brief Get the RefrTim value included in SV ASDU as ms timestamp *