/* * client_report_control.c * * Implementation of the ClientReportControlBlock class * * Copyright 2013, 2014 Michael Zillgith * * This file is part of libIEC61850. * * libIEC61850 is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * libIEC61850 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with libIEC61850. If not, see . * * See COPYING file for the complete license text. */ #include "iec61850_client.h" #include "stack_config.h" #include "ied_connection_private.h" #include "libiec61850_platform_includes.h" static bool isBufferedRcb(const char* objectReference) { const char* separator = strchr(objectReference, '.'); if (separator == NULL) return false; //TODO report an error if (*(separator + 1) == 'B') return true; else return false; } ClientReportControlBlock ClientReportControlBlock_create(const char* objectReference) { ClientReportControlBlock self = (ClientReportControlBlock) GLOBAL_CALLOC(1, sizeof(struct sClientReportControlBlock)); self->objectReference = copyString(objectReference); self->isBuffered = isBufferedRcb(objectReference); return self; } void ClientReportControlBlock_destroy(ClientReportControlBlock self) { GLOBAL_FREEMEM(self->objectReference); MmsValue_deleteIfNotNull(self->rptId); MmsValue_deleteIfNotNull(self->rptEna); MmsValue_deleteIfNotNull(self->resv); MmsValue_deleteIfNotNull(self->datSet); MmsValue_deleteIfNotNull(self->confRev); MmsValue_deleteIfNotNull(self->optFlds); MmsValue_deleteIfNotNull(self->bufTm); MmsValue_deleteIfNotNull(self->sqNum); MmsValue_deleteIfNotNull(self->trgOps); MmsValue_deleteIfNotNull(self->intgPd); MmsValue_deleteIfNotNull(self->gi); MmsValue_deleteIfNotNull(self->purgeBuf); MmsValue_deleteIfNotNull(self->entryId); MmsValue_deleteIfNotNull(self->timeOfEntry); MmsValue_deleteIfNotNull(self->resvTms); MmsValue_deleteIfNotNull(self->owner); GLOBAL_FREEMEM(self); } char* ClientReportControlBlock_getObjectReference(ClientReportControlBlock self) { return self->objectReference; } bool ClientReportControlBlock_isBuffered(ClientReportControlBlock self) { return self->isBuffered; } char* ClientReportControlBlock_getRptId(ClientReportControlBlock self) { if (self->rptId != NULL) return MmsValue_toString(self->rptId); else return NULL; } void ClientReportControlBlock_setRptId(ClientReportControlBlock self, const char* rptId) { if (self->rptId == NULL) self->rptId = MmsValue_newVisibleString(rptId); else MmsValue_setVisibleString(self->rptId, rptId); } bool ClientReportControlBlock_getRptEna(ClientReportControlBlock self) { if (self->rptEna != NULL) { return MmsValue_getBoolean(self->rptEna); } else return false; } void ClientReportControlBlock_setRptEna(ClientReportControlBlock self, bool rptEna) { if (self->rptEna == NULL) self->rptEna = MmsValue_newBoolean(rptEna); else MmsValue_setBoolean(self->rptEna, rptEna); } bool ClientReportControlBlock_getResv(ClientReportControlBlock self) { if (self->resv != NULL) { return MmsValue_getBoolean(self->resv); } else return false; } void ClientReportControlBlock_setResv(ClientReportControlBlock self, bool resv) { if (self->resv == NULL) self->resv = MmsValue_newBoolean(resv); else MmsValue_setBoolean(self->resv, resv); } char* ClientReportControlBlock_getDataSetReference(ClientReportControlBlock self) { if (self->datSet != NULL) return MmsValue_toString(self->datSet); else return NULL; } void ClientReportControlBlock_setDataSetReference(ClientReportControlBlock self, const char* dataSetReference) { if (self->datSet == NULL) self->datSet = MmsValue_newVisibleString(dataSetReference); else MmsValue_setVisibleString(self->datSet, dataSetReference); } uint32_t ClientReportControlBlock_getConfRev(ClientReportControlBlock self) { if (self->confRev != NULL) return MmsValue_toUint32(self->confRev); else return 0; } int ClientReportControlBlock_getOptFlds(ClientReportControlBlock self) { return (MmsValue_getBitStringAsInteger(self->optFlds) / 2); } void ClientReportControlBlock_setOptFlds(ClientReportControlBlock self, int optFlds) { if (self->optFlds == NULL) self->optFlds = MmsValue_newBitString(10); MmsValue_setBitStringFromInteger(self->optFlds, optFlds * 2); /* bit 0 is reserved in MMS mapping */ } uint32_t ClientReportControlBlock_getBufTm(ClientReportControlBlock self) { if (self->bufTm != NULL) return MmsValue_toUint32(self->bufTm); else return 0; } void ClientReportControlBlock_setBufTm(ClientReportControlBlock self, uint32_t bufTm) { if (self->bufTm == NULL) self->bufTm = MmsValue_newUnsignedFromUint32(bufTm); else MmsValue_setUint32(self->bufTm, bufTm); } uint16_t ClientReportControlBlock_getSqNum(ClientReportControlBlock self) { if (self->sqNum != NULL) return (uint16_t) MmsValue_toUint32(self->sqNum); else return 0; } int ClientReportControlBlock_getTrgOps(ClientReportControlBlock self) { int triggerOps = 0; if (self->trgOps != NULL) { if (MmsValue_getBitStringBit(self->trgOps, 1)) triggerOps += TRG_OPT_DATA_CHANGED; if (MmsValue_getBitStringBit(self->trgOps, 2)) triggerOps += TRG_OPT_QUALITY_CHANGED; if (MmsValue_getBitStringBit(self->trgOps, 3)) triggerOps += TRG_OPT_DATA_UPDATE; if (MmsValue_getBitStringBit(self->trgOps, 4)) triggerOps += TRG_OPT_INTEGRITY; if (MmsValue_getBitStringBit(self->trgOps, 5)) triggerOps += TRG_OPT_GI; } return triggerOps; } void ClientReportControlBlock_setTrgOps(ClientReportControlBlock self, int trgOps) { if (self->trgOps == NULL) self->trgOps = MmsValue_newBitString(6); MmsValue_setBitStringFromInteger(self->trgOps, trgOps << 1); } uint32_t ClientReportControlBlock_getIntgPd(ClientReportControlBlock self) { if (self->intgPd != NULL) return MmsValue_toUint32(self->intgPd); else return 0; } void ClientReportControlBlock_setIntgPd(ClientReportControlBlock self, uint32_t intgPd) { if (self->intgPd == NULL) self->intgPd = MmsValue_newUnsignedFromUint32(intgPd); else MmsValue_setUint32(self->intgPd, intgPd); } bool ClientReportControlBlock_getGI(ClientReportControlBlock self) { if (self->gi != NULL) return MmsValue_getBoolean(self->gi); else return false; } void ClientReportControlBlock_setGI(ClientReportControlBlock self, bool gi) { if (self->gi == NULL) self->gi = MmsValue_newBoolean(gi); else MmsValue_setBoolean(self->gi, gi); } bool ClientReportControlBlock_getPurgeBuf(ClientReportControlBlock self) { if (self->purgeBuf != NULL) return MmsValue_getBoolean(self->purgeBuf); else return false; } void ClientReportControlBlock_setPurgeBuf(ClientReportControlBlock self, bool purgeBuf) { if (self->purgeBuf == NULL) self->purgeBuf = MmsValue_newBoolean(purgeBuf); else MmsValue_setBoolean(self->purgeBuf, purgeBuf); } int16_t ClientReportControlBlock_getResvTms(ClientReportControlBlock self) { if (self->resvTms != NULL) return (int16_t) MmsValue_toInt32(self->resvTms); else return 0; } void ClientReportControlBlock_setResvTms(ClientReportControlBlock self, int16_t resvTms) { if (self->resvTms == NULL) self->resvTms = MmsValue_newIntegerFromInt16(resvTms); else MmsValue_setInt32(self->resvTms, (int32_t) resvTms); } MmsValue* ClientReportControlBlock_getEntryId(ClientReportControlBlock self) { return self->entryId; } void ClientReportControlBlock_setEntryId(ClientReportControlBlock self, MmsValue* entryId) { if (self->entryId != NULL) { MmsValue_update(self->entryId, entryId); } else { if (MmsValue_getType(entryId) != MMS_OCTET_STRING) { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: ClientReportControlBlock_setEntryId invalid argument type\n"); } else self->entryId = MmsValue_clone(entryId); } } uint64_t ClientReportControlBlock_getEntryTime(ClientReportControlBlock self) { if (self->timeOfEntry != NULL) return MmsValue_getBinaryTimeAsUtcMs(self->timeOfEntry); else return 0; } MmsValue* ClientReportControlBlock_getOwner(ClientReportControlBlock self) { return self->owner; } static void updateOrClone(MmsValue** valuePtr, MmsValue* values, int index) { if (*valuePtr != NULL) MmsValue_update(*valuePtr, MmsValue_getElement(values, index)); else *valuePtr = MmsValue_clone(MmsValue_getElement(values, index)); } bool private_ClientReportControlBlock_updateValues(ClientReportControlBlock self, MmsValue* values) { int rcbElementCount = MmsValue_getArraySize(values); updateOrClone(&(self->rptId), values, 0); updateOrClone(&(self->rptEna), values, 1); if (self->isBuffered) { updateOrClone(&(self->datSet), values, 2); updateOrClone(&(self->confRev), values, 3); updateOrClone(&(self->optFlds), values, 4); updateOrClone(&(self->bufTm), values, 5); updateOrClone(&(self->sqNum), values, 6); updateOrClone(&(self->trgOps), values,7); updateOrClone(&(self->intgPd), values, 8); updateOrClone(&(self->gi), values, 9); updateOrClone(&(self->purgeBuf), values, 10); updateOrClone(&(self->entryId), values, 11); updateOrClone(&(self->timeOfEntry), values, 12); if (rcbElementCount > 13) { MmsValue* element13 = MmsValue_getElement(values, 13); if (MmsValue_getType(element13) == MMS_OCTET_STRING) updateOrClone(&(self->owner), values, 13); else { updateOrClone(&(self->resvTms), values, 13); if (rcbElementCount > 14) updateOrClone(&(self->owner), values, 14); } } } else { updateOrClone(&(self->resv), values, 2); updateOrClone(&(self->datSet), values, 3); updateOrClone(&(self->confRev), values, 4); updateOrClone(&(self->optFlds), values, 5); updateOrClone(&(self->bufTm), values, 6); updateOrClone(&(self->sqNum), values, 7); updateOrClone(&(self->trgOps), values, 8); updateOrClone(&(self->intgPd), values, 9); updateOrClone(&(self->gi), values, 10); if (rcbElementCount == 12) /* owner is optional */ updateOrClone(&(self->owner), values, 11); } return true; } ClientReportControlBlock IedConnection_getRCBValues(IedConnection self, IedClientError* error, const char* rcbReference, ClientReportControlBlock updateRcb) { MmsError mmsError = MMS_ERROR_NONE; ClientReportControlBlock returnRcb = updateRcb; char domainId[65]; char itemId[65]; char* domainName = MmsMapping_getMmsDomainFromObjectReference(rcbReference, domainId); if (domainName == NULL) return NULL; strcpy(itemId, rcbReference + strlen(domainId) + 1); StringUtils_replace(itemId, '.', '$'); if (DEBUG_IED_CLIENT) printf("DEBUG_IED_CLIENT: readRCBValues for %s\n", rcbReference); MmsValue* rcb = MmsConnection_readVariable(self->connection, &mmsError, domainId, itemId); if (mmsError != MMS_ERROR_NONE) { *error = iedConnection_mapMmsErrorToIedError(mmsError); return NULL; } if (rcb == NULL) { *error = IED_ERROR_OBJECT_DOES_NOT_EXIST; return NULL; } if (MmsValue_getType(rcb) != MMS_STRUCTURE) { if (DEBUG_IED_CLIENT) printf("DEBUG_IED_CLIENT: getRCBValues returned wrong type!\n"); MmsValue_delete(rcb); *error = IED_ERROR_UNKNOWN; return NULL; } if (returnRcb == NULL) returnRcb = ClientReportControlBlock_create(rcbReference); private_ClientReportControlBlock_updateValues(returnRcb, rcb); MmsValue_delete(rcb); *error = IED_ERROR_OK; return returnRcb; } void IedConnection_setRCBValues(IedConnection self, IedClientError* error, ClientReportControlBlock rcb, uint32_t parametersMask, bool singleRequest) { *error = IED_ERROR_OK; MmsError mmsError = MMS_ERROR_NONE; bool isBuffered = ClientReportControlBlock_isBuffered(rcb); char domainId[65]; char itemId[129]; char* rcbReference = ClientReportControlBlock_getObjectReference(rcb); MmsMapping_getMmsDomainFromObjectReference(rcbReference, domainId); strcpy(itemId, rcbReference + strlen(domainId) + 1); StringUtils_replace(itemId, '.', '$'); if (DEBUG_IED_CLIENT) printf("DEBUG_IED_CLIENT: setRCBValues for %s\n", rcbReference); int itemIdLen = strlen(itemId); /* create the list of requested itemIds references */ LinkedList itemIds = LinkedList_create(); LinkedList values = LinkedList_create(); /* add resv/resvTms as first element and rptEna as last element */ if (parametersMask & RCB_ELEMENT_RESV) { if (isBuffered) goto error_invalid_parameter; strcpy(itemId + itemIdLen, "$Resv"); LinkedList_add(itemIds, copyString(itemId)); LinkedList_add(values, rcb->resv); } if (parametersMask & RCB_ELEMENT_RESV_TMS) { if (!isBuffered) goto error_invalid_parameter; strcpy(itemId + itemIdLen, "$ResvTms"); LinkedList_add(itemIds, copyString(itemId)); LinkedList_add(values, rcb->resvTms); } if (parametersMask & RCB_ELEMENT_RPT_ID) { strcpy(itemId + itemIdLen, "$RptID"); LinkedList_add(itemIds, copyString(itemId)); LinkedList_add(values, rcb->rptId); } if (parametersMask & RCB_ELEMENT_DATSET) { strcpy(itemId + itemIdLen, "$DatSet"); LinkedList_add(itemIds, copyString(itemId)); LinkedList_add(values, rcb->datSet); } if (parametersMask & RCB_ELEMENT_ENTRY_ID) { strcpy(itemId + itemIdLen, "$EntryID"); LinkedList_add(itemIds, copyString(itemId)); LinkedList_add(values, rcb->entryId); } if (parametersMask & RCB_ELEMENT_OPT_FLDS) { strcpy(itemId + itemIdLen, "$OptFlds"); LinkedList_add(itemIds, copyString(itemId)); LinkedList_add(values, rcb->optFlds); } if (parametersMask & RCB_ELEMENT_BUF_TM) { strcpy(itemId + itemIdLen, "$BufTm"); LinkedList_add(itemIds, copyString(itemId)); LinkedList_add(values, rcb->bufTm); } if (parametersMask & RCB_ELEMENT_TRG_OPS) { strcpy(itemId + itemIdLen, "$TrgOps"); LinkedList_add(itemIds, copyString(itemId)); LinkedList_add(values, rcb->trgOps); } if (parametersMask & RCB_ELEMENT_INTG_PD) { strcpy(itemId + itemIdLen, "$IntgPd"); LinkedList_add(itemIds, copyString(itemId)); LinkedList_add(values, rcb->intgPd); } if (parametersMask & RCB_ELEMENT_GI) { strcpy(itemId + itemIdLen, "$GI"); LinkedList_add(itemIds, copyString(itemId)); LinkedList_add(values, rcb->gi); } if (parametersMask & RCB_ELEMENT_PURGE_BUF) { if (!isBuffered) goto error_invalid_parameter; strcpy(itemId + itemIdLen, "$PurgeBuf"); LinkedList_add(itemIds, copyString(itemId)); LinkedList_add(values, rcb->purgeBuf); } if (parametersMask & RCB_ELEMENT_TIME_OF_ENTRY) { if (!isBuffered) goto error_invalid_parameter; strcpy(itemId + itemIdLen, "$TimeOfEntry"); LinkedList_add(itemIds, copyString(itemId)); LinkedList_add(values, rcb->timeOfEntry); } if (parametersMask & RCB_ELEMENT_RPT_ENA) { strcpy(itemId + itemIdLen, "$RptEna"); LinkedList_add(itemIds, copyString(itemId)); LinkedList_add(values, rcb->rptEna); } if (singleRequest) { LinkedList accessResults = NULL; MmsConnection_writeMultipleVariables(self->connection, &mmsError, domainId, itemIds, values, &accessResults); if (accessResults != NULL) { LinkedList accessResult = LinkedList_getNext(accessResults); while (accessResult != NULL) { MmsValue* dataAccessError = (MmsValue*) accessResult->data; if (MmsValue_getDataAccessError(dataAccessError) != DATA_ACCESS_ERROR_SUCCESS) { *error = IED_ERROR_UNKNOWN; break; } accessResult = LinkedList_getNext(accessResult); } LinkedList_destroyDeep(accessResults, (LinkedListValueDeleteFunction) MmsValue_delete); } else *error = iedConnection_mapMmsErrorToIedError(mmsError); goto exit_function; } else { LinkedList itemIdElement = LinkedList_getNext(itemIds); LinkedList valueElement = LinkedList_getNext(values); while (itemIdElement != NULL) { char* rcbItemId = (char*) itemIdElement->data; MmsValue* value = (MmsValue*) valueElement->data; MmsConnection_writeVariable(self->connection, &mmsError, domainId, rcbItemId, value); if (mmsError != MMS_ERROR_NONE) break; itemIdElement = LinkedList_getNext(itemIdElement); valueElement = LinkedList_getNext(valueElement); } *error = iedConnection_mapMmsErrorToIedError(mmsError); goto exit_function; } error_invalid_parameter: *error = IED_ERROR_USER_PROVIDED_INVALID_ARGUMENT; exit_function: LinkedList_destroy(itemIds); LinkedList_destroyStatic(values); }