/* * client_report_control.c * * Implementation of the ClientReportControlBlock class * * Copyright 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" struct sClientGooseControlBlock { char* objectReference; MmsValue* goEna; MmsValue* goID; MmsValue* datSet; MmsValue* confRev; MmsValue* ndsCom; MmsValue* dstAddress; MmsValue* minTime; MmsValue* maxTime; MmsValue* fixedOffs; }; ClientGooseControlBlock ClientGooseControlBlock_create(const char* objectReference) { ClientGooseControlBlock self = (ClientGooseControlBlock) GLOBAL_CALLOC(1, sizeof(struct sClientGooseControlBlock)); self->objectReference = StringUtils_copyString(objectReference); return self; } void ClientGooseControlBlock_destroy(ClientGooseControlBlock self) { GLOBAL_FREEMEM(self->objectReference); MmsValue_delete(self->goEna); MmsValue_delete(self->goID); MmsValue_delete(self->datSet); MmsValue_delete(self->confRev); MmsValue_delete(self->ndsCom); MmsValue_delete(self->dstAddress); MmsValue_delete(self->minTime); MmsValue_delete(self->maxTime); MmsValue_delete(self->fixedOffs); GLOBAL_FREEMEM(self); } bool ClientGooseControlBlock_getGoEna(ClientGooseControlBlock self) { if (self->goEna != NULL) return MmsValue_getBoolean(self->goEna); else return false; } void ClientGooseControlBlock_setGoEna(ClientGooseControlBlock self, bool goEna) { if (self->goEna == NULL) self->goEna = MmsValue_newBoolean(goEna); else MmsValue_setBoolean(self->goEna, goEna); } const char* ClientGooseControlBlock_getGoID(ClientGooseControlBlock self) { if (self->goID != NULL) return MmsValue_toString(self->goID); else return NULL; } void ClientGooseControlBlock_setGoID(ClientGooseControlBlock self, const char* goID) { if (self->goID == NULL) self->goID = MmsValue_newVisibleString(goID); else MmsValue_setVisibleString(self->goID, goID); } const char* ClientGooseControlBlock_getDatSet(ClientGooseControlBlock self) { if (self->datSet != NULL) return MmsValue_toString(self->datSet); else return NULL; } void ClientGooseControlBlock_setDatSet(ClientGooseControlBlock self, const char* datSet) { if (self->datSet == NULL) self->datSet = MmsValue_newVisibleString(datSet); else MmsValue_setVisibleString(self->datSet, datSet); } uint32_t ClientGooseControlBlock_getConfRev(ClientGooseControlBlock self) { if (self->confRev != NULL) return MmsValue_toUint32(self->confRev); else return 0; } bool ClientGooseControlBlock_getNdsComm(ClientGooseControlBlock self) { if (self->ndsCom != NULL) return MmsValue_getBoolean(self->ndsCom); else return false; } uint32_t ClientGooseControlBlock_getMinTime(ClientGooseControlBlock self) { if (self->minTime != NULL) return MmsValue_toUint32(self->minTime); else return 0; } uint32_t ClientGooseControlBlock_getMaxTime(ClientGooseControlBlock self) { if (self->maxTime != NULL) return MmsValue_toUint32(self->maxTime); else return 0; } bool ClientGooseControlBlock_getFixedOffs(ClientGooseControlBlock self) { if (self->fixedOffs != NULL) return MmsValue_getBoolean(self->fixedOffs); else return false; } static MmsValue* newEmptyPhyCommAddress(void) { MmsValue* self = MmsValue_createEmptyStructure(4); MmsValue_setElement(self, 0, MmsValue_newOctetString(6, 6)); MmsValue_setElement(self, 1, MmsValue_newUnsigned(8)); MmsValue_setElement(self, 2, MmsValue_newUnsigned(16)); MmsValue_setElement(self, 3, MmsValue_newUnsigned(16)); return self; } PhyComAddress ClientGooseControlBlock_getDstAddress(ClientGooseControlBlock self) { PhyComAddress retVal; memset(&retVal, 0, sizeof(retVal)); if (self->dstAddress == NULL) goto exit_error; if (MmsValue_getType(self->dstAddress) != MMS_STRUCTURE) { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong type\n"); goto exit_error; } if (MmsValue_getArraySize(self->dstAddress) != 4) { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong type\n"); goto exit_error; } MmsValue* addr = MmsValue_getElement(self->dstAddress, 0); if (MmsValue_getType(addr) != MMS_OCTET_STRING) { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong type\n"); goto exit_error; } if (MmsValue_getOctetStringSize(addr) != 6) { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - addr has wrong size\n"); goto exit_error; } uint8_t* addrBuf = MmsValue_getOctetStringBuffer(addr); memcpy(&(retVal.dstAddress), addrBuf, 6); MmsValue* prio = MmsValue_getElement(self->dstAddress, 1); if (MmsValue_getType(prio) != MMS_UNSIGNED) { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - prio has wrong type\n"); goto exit_error; } retVal.vlanPriority = MmsValue_toUint32(prio); MmsValue* vid = MmsValue_getElement(self->dstAddress, 2); if (MmsValue_getType(vid) != MMS_UNSIGNED) { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - vid has wrong type\n"); goto exit_error; } retVal.vlanId = MmsValue_toUint32(vid); MmsValue* appID = MmsValue_getElement(self->dstAddress, 3); if (MmsValue_getType(appID) != MMS_UNSIGNED) { if (DEBUG_IED_CLIENT) printf("IED_CLIENT: GoCB - appID has wrong type\n"); goto exit_error; } retVal.appId = MmsValue_toUint32(appID); exit_error: return retVal; } void ClientGooseControlBlock_setDstAddress(ClientGooseControlBlock self, PhyComAddress value) { if (self->dstAddress == NULL) self->dstAddress = newEmptyPhyCommAddress(); if (self->dstAddress) { MmsValue* addr = MmsValue_getElement(self->dstAddress, 0); MmsValue_setOctetString(addr, value.dstAddress, 6); MmsValue* prio = MmsValue_getElement(self->dstAddress, 1); MmsValue_setUint8(prio, value.vlanPriority); MmsValue* vid = MmsValue_getElement(self->dstAddress, 2); MmsValue_setUint16(vid, value.vlanId); MmsValue* appID = MmsValue_getElement(self->dstAddress, 3); MmsValue_setUint16(appID, value.appId); } } MmsValue* ClientGooseControlBlock_getDstAddress_addr(ClientGooseControlBlock self) { if (self->dstAddress != NULL) return MmsValue_getElement(self->dstAddress, 0); else return NULL; } void ClientGooseControlBlock_setDstAddress_addr(ClientGooseControlBlock self, MmsValue* macAddr) { if (self->dstAddress == NULL) self->dstAddress = newEmptyPhyCommAddress(); MmsValue* addr = MmsValue_getElement(self->dstAddress, 0); MmsValue_update(addr, macAddr); } uint8_t ClientGooseControlBlock_getDstAddress_priority(ClientGooseControlBlock self) { if (self->dstAddress != NULL) return (uint8_t) MmsValue_toUint32(MmsValue_getElement(self->dstAddress, 1)); else return 0; } void ClientGooseControlBlock_setDstAddress_priority(ClientGooseControlBlock self, uint8_t priorityValue) { if (self->dstAddress == NULL) self->dstAddress = newEmptyPhyCommAddress(); MmsValue* priority = MmsValue_getElement(self->dstAddress, 1); MmsValue_setUint8(priority, priorityValue); } uint16_t ClientGooseControlBlock_getDstAddress_vid(ClientGooseControlBlock self) { if (self->dstAddress != NULL) return (uint16_t) MmsValue_toUint32(MmsValue_getElement(self->dstAddress, 2)); else return 0; } void ClientGooseControlBlock_setDstAddress_vid(ClientGooseControlBlock self, uint16_t vidValue) { if (self->dstAddress == NULL) self->dstAddress = newEmptyPhyCommAddress(); MmsValue* vid = MmsValue_getElement(self->dstAddress, 2); MmsValue_setUint16(vid, vidValue); } uint16_t ClientGooseControlBlock_getDstAddress_appid(ClientGooseControlBlock self) { if (self->dstAddress != NULL) return (uint16_t) MmsValue_toUint32(MmsValue_getElement(self->dstAddress, 3)); else return 0; } void ClientGooseControlBlock_setDstAddress_appid(ClientGooseControlBlock self, uint16_t appidValue) { if (self->dstAddress == NULL) self->dstAddress = newEmptyPhyCommAddress(); MmsValue* appid = MmsValue_getElement(self->dstAddress, 3); MmsValue_setUint16(appid, appidValue); } 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)); } static bool private_ClientGooseControlBlock_updateValues(ClientGooseControlBlock self, MmsValue* values) { int elementCount = MmsValue_getArraySize(values); if (elementCount > 5) { updateOrClone(&(self->goEna), values, 0); updateOrClone(&(self->goID), values, 1); updateOrClone(&(self->datSet), values, 2); updateOrClone(&(self->confRev), values, 3); updateOrClone(&(self->ndsCom), values, 4); updateOrClone(&(self->dstAddress), values, 5); } else return false; if (elementCount > 6) updateOrClone(&(self->minTime), values, 6); if (elementCount > 7) updateOrClone(&(self->maxTime), values, 7); if (elementCount > 8) updateOrClone(&(self->fixedOffs), values, 8); return true; } ClientGooseControlBlock IedConnection_getGoCBValues(IedConnection self, IedClientError* error, const char* goCBReference, ClientGooseControlBlock updateGoCB) { MmsError mmsError = MMS_ERROR_NONE; *error = IED_ERROR_OK; ClientGooseControlBlock returnGoCB = updateGoCB; char domainId[65]; char itemId[130]; if (MmsMapping_getMmsDomainFromObjectReference(goCBReference, domainId) == NULL) { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } int domainIdSize = strlen(domainId); const char* itemIdStart = goCBReference + domainIdSize + 1; const char* separator = strchr(itemIdStart, '.'); if (separator == NULL) { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return NULL; } int separatorOffset = separator - itemIdStart; memcpy(itemId, itemIdStart, separatorOffset); itemId[separatorOffset] = '$'; itemId[separatorOffset + 1] = 'G'; itemId[separatorOffset + 2] = 'O'; itemId[separatorOffset + 3] = '$'; itemId[separatorOffset + 4] = 0; StringUtils_appendString(itemId, 130, separator + 1); if (DEBUG_IED_CLIENT) printf("DEBUG_IED_CLIENT: getGoCBValues for %s\n", goCBReference); MmsValue* goCB = MmsConnection_readVariable(self->connection, &mmsError, domainId, itemId); if (mmsError != MMS_ERROR_NONE) { *error = iedConnection_mapMmsErrorToIedError(mmsError); return NULL; } if (goCB == NULL) { *error = IED_ERROR_OBJECT_DOES_NOT_EXIST; return NULL; } if (MmsValue_getType(goCB) != MMS_STRUCTURE) { if (DEBUG_IED_CLIENT) printf("DEBUG_IED_CLIENT: getRCBValues returned wrong type!\n"); MmsValue_delete(goCB); *error = IED_ERROR_UNKNOWN; return NULL; } if (returnGoCB == NULL) returnGoCB = ClientGooseControlBlock_create(goCBReference); if (private_ClientGooseControlBlock_updateValues(returnGoCB, goCB)) *error = IED_ERROR_OK; else *error = IED_ERROR_UNEXPECTED_VALUE_RECEIVED; MmsValue_delete(goCB); return returnGoCB; } void IedConnection_setGoCBValues(IedConnection self, IedClientError* error, ClientGooseControlBlock goCB, uint32_t parametersMask, bool singleRequest) { *error = IED_ERROR_OK; MmsError mmsError = MMS_ERROR_NONE; char domainId[65]; char itemId[130]; if (MmsMapping_getMmsDomainFromObjectReference(goCB->objectReference, domainId) == NULL) { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return; } char* itemIdStart = goCB->objectReference + strlen(domainId) + 1; char* separator = strchr(itemIdStart, '.'); if (separator == NULL) { *error = IED_ERROR_OBJECT_REFERENCE_INVALID; return; } int separatorOffset = separator - itemIdStart; memcpy(itemId, itemIdStart, separatorOffset); itemId[separatorOffset] = '$'; itemId[separatorOffset + 1] = 'G'; itemId[separatorOffset + 2] = 'O'; itemId[separatorOffset + 3] = '$'; itemId[separatorOffset + 4] = 0; StringUtils_appendString(itemId, 130, separator + 1); if (DEBUG_IED_CLIENT) printf("DEBUG_IED_CLIENT: setGoCBValues for %s\n", goCB->objectReference); int itemIdLen = strlen(itemId); /* create the list of requested itemIds references */ LinkedList itemIds = LinkedList_create(); LinkedList values = LinkedList_create(); /* add rGoEna as last element */ if (parametersMask & GOCB_ELEMENT_GO_ID) { StringUtils_appendString(itemId, 130, "$GoID"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); LinkedList_add(values, goCB->goID); itemId[itemIdLen] = 0; } if (parametersMask & GOCB_ELEMENT_DATSET) { StringUtils_appendString(itemId, 130, "$DatSet"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); LinkedList_add(values, goCB->datSet); itemId[itemIdLen] = 0; } if (parametersMask & GOCB_ELEMENT_CONF_REV) { StringUtils_appendString(itemId, 130, "$ConfRev"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); LinkedList_add(values, goCB->confRev); itemId[itemIdLen] = 0; } if (parametersMask & GOCB_ELEMENT_NDS_COMM) { StringUtils_appendString(itemId, 130, "$NdsCom"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); LinkedList_add(values, goCB->ndsCom); itemId[itemIdLen] = 0; } if (parametersMask & GOCB_ELEMENT_DST_ADDRESS) { StringUtils_appendString(itemId, 130, "$DstAddress"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); LinkedList_add(values, goCB->dstAddress); itemId[itemIdLen] = 0; } if (parametersMask & GOCB_ELEMENT_MIN_TIME) { StringUtils_appendString(itemId, 130, "$MinTime"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); LinkedList_add(values, goCB->minTime); itemId[itemIdLen] = 0; } if (parametersMask & GOCB_ELEMENT_MAX_TIME) { StringUtils_appendString(itemId, 130, "$MaxTime"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); LinkedList_add(values, goCB->maxTime); itemId[itemIdLen] = 0; } if (parametersMask & GOCB_ELEMENT_FIXED_OFFS) { StringUtils_appendString(itemId, 130, "$FixedOffs"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); LinkedList_add(values, goCB->fixedOffs); itemId[itemIdLen] = 0; } if (parametersMask & GOCB_ELEMENT_GO_ENA) { StringUtils_appendString(itemId, 130, "$GoEna"); LinkedList_add(itemIds, StringUtils_copyString(itemId)); LinkedList_add(values, goCB->goEna); itemId[itemIdLen] = 0; } if (singleRequest) { LinkedList accessResults = NULL; *error = IED_ERROR_OK; MmsConnection_writeMultipleVariables(self->connection, &mmsError, domainId, itemIds, values, &accessResults); if (accessResults != NULL) { LinkedList element = LinkedList_getNext(accessResults); while (element != NULL) { MmsValue* accessResult = (MmsValue*) element->data; MmsDataAccessError resErr = MmsValue_getDataAccessError(accessResult); if (MmsValue_getDataAccessError(accessResult) != DATA_ACCESS_ERROR_SUCCESS) { *error = iedConnection_mapDataAccessErrorToIedError(resErr); break; } element = LinkedList_getNext(element); } LinkedList_destroyDeep(accessResults, (LinkedListValueDeleteFunction) MmsValue_delete); } 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; } exit_function: LinkedList_destroy(itemIds); LinkedList_destroyStatic(values); }