/* * sv_publisher.c * * Copyright 2016 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 "stack_config.h" #include "libiec61850_platform_includes.h" #include #include "sv_publisher.h" #include "hal_ethernet.h" #include "ber_encoder.h" #define DEBUG_SV_PUBLISHER 1 #define CONFIG_SV_DEFAULT_DST_ADDRESS CONFIG_GOOSE_DEFAULT_DST_ADDRESS #define CONFIG_SV_DEFAULT_PRIORITY 4 #define CONFIG_SV_DEFAULT_VLAN_ID 0 #define CONFIG_SV_DEFAULT_APPID 0x4000 #define SV_MAX_MESSAGE_SIZE 1518 typedef struct sCommParameters { uint8_t vlanPriority; uint16_t vlanId; uint16_t appId; uint8_t dstAddress[6]; } CommParameters; struct sSV_ASDU { char* svID; char* datset; int dataSize; bool hasRefrTm; bool hasSmpRate; bool hasSmpMod; uint8_t* _dataBuffer; uint8_t smpSynch; uint16_t smpCnt; uint32_t confRev; uint64_t refrTm; uint8_t smpMod; uint8_t* smpCntBuf; SV_ASDU _next; }; struct sSampledValuesPublisher { uint8_t* buffer; uint16_t appId; EthernetSocket ethernetSocket; int lengthField; /* can probably be removed since packets have fixed size! */ int payloadStart; int payloadLength; /* length of payload buffer */ int asduCount; /* number of ASDUs in the APDU */ SV_ASDU asduLIst; }; static void preparePacketBuffer(SampledValuesPublisher self, CommParameters* parameters, const char* interfaceID) { uint8_t srcAddr[6]; if (interfaceID != NULL) Ethernet_getInterfaceMACAddress(interfaceID, srcAddr); else Ethernet_getInterfaceMACAddress(CONFIG_ETHERNET_INTERFACE_ID, srcAddr); uint8_t defaultDstAddr[] = CONFIG_SV_DEFAULT_DST_ADDRESS; uint8_t* dstAddr; uint8_t priority; uint16_t vlanId; uint16_t appId; if (parameters == NULL) { dstAddr = defaultDstAddr; priority = CONFIG_SV_DEFAULT_PRIORITY; vlanId = CONFIG_SV_DEFAULT_VLAN_ID; appId = CONFIG_SV_DEFAULT_APPID; } else { dstAddr = parameters->dstAddress; priority = parameters->vlanPriority; vlanId = parameters->vlanId; appId = parameters->appId; } if (interfaceID != NULL) self->ethernetSocket = Ethernet_createSocket(interfaceID, dstAddr); else self->ethernetSocket = Ethernet_createSocket(CONFIG_ETHERNET_INTERFACE_ID, dstAddr); self->buffer = (uint8_t*) GLOBAL_MALLOC(SV_MAX_MESSAGE_SIZE); memcpy(self->buffer, dstAddr, 6); memcpy(self->buffer + 6, srcAddr, 6); int bufPos = 12; /* Priority tag - IEEE 802.1Q */ self->buffer[bufPos++] = 0x81; self->buffer[bufPos++] = 0x00; uint8_t tci1 = priority << 5; tci1 += vlanId / 256; uint8_t tci2 = vlanId % 256; self->buffer[bufPos++] = tci1; /* Priority + VLAN-ID */ self->buffer[bufPos++] = tci2; /* VLAN-ID */ /* EtherType Sampled Values */ self->buffer[bufPos++] = 0x88; self->buffer[bufPos++] = 0xBa; /* APPID */ self->buffer[bufPos++] = appId / 256; self->buffer[bufPos++] = appId % 256; self->lengthField = bufPos; /* Length */ self->buffer[bufPos++] = 0x00; self->buffer[bufPos++] = 0x08; /* Reserved1 */ self->buffer[bufPos++] = 0x00; self->buffer[bufPos++] = 0x00; /* Reserved2 */ self->buffer[bufPos++] = 0x00; self->buffer[bufPos++] = 0x00; self->payloadStart = bufPos; } static int encodeUInt16FixedSize(uint16_t value, uint8_t* buffer, int bufPos) { uint8_t* valueArray = (uint8_t*) &value; #if (ORDER_LITTLE_ENDIAN == 1) buffer[bufPos++] = valueArray[1]; buffer[bufPos++] = valueArray[0]; #else buffer[bufPos++] = valueArray[0]; buffer[bufPos++] = valueArray[1]; #endif return bufPos; } static int encodeUInt32FixedSize(uint32_t value, uint8_t* buffer, int bufPos) { uint8_t* valueArray = (uint8_t*) &value; #if (ORDER_LITTLE_ENDIAN == 1) buffer[bufPos++] = valueArray[3]; buffer[bufPos++] = valueArray[2]; buffer[bufPos++] = valueArray[1]; buffer[bufPos++] = valueArray[0]; #else buffer[bufPos++] = valueArray[0]; buffer[bufPos++] = valueArray[1]; buffer[bufPos++] = valueArray[2]; buffer[bufPos++] = valueArray[3]; #endif return bufPos; } static int encodeInt32FixedSize(int32_t value, uint8_t* buffer, int bufPos) { uint8_t* valueArray = (uint8_t*) &value; #if (ORDER_LITTLE_ENDIAN == 1) buffer[bufPos++] = valueArray[3]; buffer[bufPos++] = valueArray[2]; buffer[bufPos++] = valueArray[1]; buffer[bufPos++] = valueArray[0]; #else buffer[bufPos++] = valueArray[0]; buffer[bufPos++] = valueArray[1]; buffer[bufPos++] = valueArray[2]; buffer[bufPos++] = valueArray[3]; #endif return bufPos; } static int encodeUtcTime(uint64_t timeval, uint8_t* buffer, int bufPos) { uint32_t timeval32 = (timeval / 1000LL); uint8_t* timeArray = (uint8_t*) &timeval32; uint8_t* valueArray = buffer + bufPos; #if (ORDER_LITTLE_ENDIAN == 1) valueArray[0] = timeArray[3]; valueArray[1] = timeArray[2]; valueArray[2] = timeArray[1]; valueArray[3] = timeArray[0]; #else valueArray[0] = timeArray[0]; valueArray[1] = timeArray[1]; valueArray[2] = timeArray[2]; valueArray[3] = timeArray[3]; #endif uint32_t remainder = (timeval % 1000LL); uint32_t fractionOfSecond = (remainder) * 16777 + ((remainder * 216) / 1000); /* encode fraction of second */ valueArray[4] = ((fractionOfSecond >> 16) & 0xff); valueArray[5] = ((fractionOfSecond >> 8) & 0xff); valueArray[6] = (fractionOfSecond & 0xff); /* encode time quality */ valueArray[7] = 0x0a; /* 10 bit sub-second time accuracy */ return bufPos + 8; } SampledValuesPublisher SampledValuesPublisher_create(const char* interfaceId) { SampledValuesPublisher self = GLOBAL_CALLOC(1, sizeof(struct sSampledValuesPublisher)); self->asduLIst = NULL; preparePacketBuffer(self, NULL, interfaceId); return self; } SV_ASDU SampledValuesPublisher_addASDU(SampledValuesPublisher self, char* svID, char* datset, uint32_t confRev) { SV_ASDU newAsdu = GLOBAL_CALLOC(1, sizeof(struct sSV_ASDU)); newAsdu->svID = svID; newAsdu->datset = datset; newAsdu->confRev = confRev; newAsdu->_next = NULL; /* append new ASDU to list */ if (self->asduLIst == NULL) self->asduLIst = newAsdu; else { SV_ASDU lastAsdu = self->asduLIst; while (lastAsdu->_next != NULL) lastAsdu = lastAsdu->_next; lastAsdu->_next = newAsdu; } return newAsdu; } static int SV_ASDU_getEncodedSize(SV_ASDU self) { int encodedSize = 0; /* svID */ encodedSize += ( 2 + strlen(self->svID) ); /* datset */ if (self->datset != NULL) encodedSize += ( 2 + strlen(self->datset) ); /* smpCnt */ encodedSize += 4; /* confRef */ encodedSize += 6; /* refrTm */ if (self->hasRefrTm) encodedSize += 10; /* ??? */ /* smpSynch */ encodedSize += 3; /* smpRate */ if (self->hasSmpRate) encodedSize += 4; /* sample */ encodedSize += 2; encodedSize += self->dataSize; /* smpMod */ if (self->hasSmpMod) encodedSize += 4; return encodedSize; } static int SV_ASDU_encodeToBuffer(SV_ASDU self, uint8_t* buffer, int bufPos) { int encodedSize = SV_ASDU_getEncodedSize(self); /* tag and length field */ bufPos = BerEncoder_encodeTL(0x30, encodedSize, buffer, bufPos); /* svID */ bufPos = BerEncoder_encodeStringWithTag(0x80, self->svID, buffer, bufPos); /* DatSet */ if (self->datset != NULL) bufPos = BerEncoder_encodeStringWithTag(0x81, self->datset, buffer, bufPos); //uint8_t octetString[4]; /* SmpCnt */ bufPos = BerEncoder_encodeTL(0x82, 2, buffer, bufPos); self->smpCntBuf = buffer + bufPos; bufPos = encodeUInt16FixedSize(self->smpCnt, buffer, bufPos); /* ConfRev */ bufPos = BerEncoder_encodeTL(0x83, 4, buffer, bufPos); bufPos = encodeUInt32FixedSize(self->confRev, buffer, bufPos); /* RefrTm */ if (self->hasRefrTm) { bufPos = BerEncoder_encodeTL(0x84, 8, buffer, bufPos); bufPos = encodeUtcTime(self->refrTm, buffer, bufPos); } /* SmpSynch */ bufPos = BerEncoder_encodeTL(0x85, 1, buffer, bufPos); buffer[bufPos++] = self->smpSynch; /* SmpRate */ //TODO implement me /* Sample */ bufPos = BerEncoder_encodeTL(0x87, self->dataSize, buffer, bufPos); self->_dataBuffer = buffer + bufPos; bufPos += self->dataSize; /* data has to inserted by user before sending message */ /* SmpMod */ if (self->hasSmpMod) { bufPos = BerEncoder_encodeTL(0x84, 4, buffer, bufPos); buffer[bufPos++] = 0; buffer[bufPos++] = 0; buffer[bufPos++] = 0; buffer[bufPos++] = self->smpMod; } return bufPos; } void SampledValuesPublisher_setupComplete(SampledValuesPublisher self) { int numberOfAsdu = 0; /* determine number of ASDUs and length of all ASDUs */ SV_ASDU nextAsdu = self->asduLIst; int totalASDULength = 0; while (nextAsdu != NULL) { numberOfAsdu++; int asduLength = SV_ASDU_getEncodedSize(nextAsdu); /* tag and length field */ asduLength += BerEncoder_determineLengthSize(asduLength); asduLength++; totalASDULength += asduLength; nextAsdu = nextAsdu->_next; } /* encode frame to buffer */ int sequenceSize = 1 + BerEncoder_determineLengthSize(totalASDULength) + totalASDULength; int innerSize = 2 + BerEncoder_UInt32determineEncodedSize(numberOfAsdu) + sequenceSize; uint8_t* buffer = self->buffer + self->payloadStart; int bufPos = BerEncoder_encodeTL(0x60, innerSize, buffer, 0); /* noASDU */ bufPos = BerEncoder_encodeUInt32WithTL(0x80, numberOfAsdu, buffer, bufPos); /* seqASDU */ bufPos = BerEncoder_encodeTL(0xa2, totalASDULength, buffer, bufPos); nextAsdu = self->asduLIst; while (nextAsdu != NULL) { bufPos = SV_ASDU_encodeToBuffer(nextAsdu, buffer, bufPos); nextAsdu = nextAsdu->_next; } /* Update length field */ int payloadLength = bufPos; size_t msgLength = payloadLength + 8; int lengthIndex = self->lengthField; self->buffer[lengthIndex] = msgLength / 256; self->buffer[lengthIndex + 1] = msgLength & 0xff; self->payloadLength = payloadLength; } void SampledValuesPublisher_publish(SampledValuesPublisher self) { if (DEBUG_SV_PUBLISHER) printf("SV_PUBLISHER: send SV message\n"); Ethernet_sendPacket(self->ethernetSocket, self->buffer, self->payloadStart + self->payloadLength); } void SampledValuesPublisher_destroy(SampledValuesPublisher self) { GLOBAL_FREEMEM(self->buffer); } void SV_ASDU_resetBuffer(SV_ASDU self) { self->dataSize = 0; } int SV_ASDU_addINT8(SV_ASDU self) { int index = self->dataSize; self->dataSize += 1; return index; } void SV_ASDU_setINT8(SV_ASDU self, int index, int8_t value) { self->_dataBuffer[index] = value; } int SV_ASDU_addINT32(SV_ASDU self) { int index = self->dataSize; self->dataSize += 4; return index; } void SV_ASDU_setINT32(SV_ASDU self, int index, int32_t value) { encodeInt32FixedSize(value, self->_dataBuffer, index); } int SV_ASDU_addFLOAT(SV_ASDU self) { int index = self->dataSize; self->dataSize += 4; return index; } void SV_ASDU_setFLOAT(SV_ASDU self, int index, float value) { uint8_t* buf = (uint8_t*) &value; #if (ORDER_LITTLE_ENDIAN == 1) BerEncoder_revertByteOrder(buf, 4); #endif int i; uint8_t* buffer = self->_dataBuffer + index; for (i = 0; i < 4; i++) { buffer[i] = buf[i]; } } void SV_ASDU_setSmpCnt(SV_ASDU self, uint16_t value) { self->smpCnt = value; encodeUInt16FixedSize(self->smpCnt, self->smpCntBuf, 0); } void SV_ASDU_increaseSmpCnt(SV_ASDU self) { self->smpCnt++; encodeUInt16FixedSize(self->smpCnt, self->smpCntBuf, 0); } void SV_ASDU_setRefrTm(SV_ASDU self, uint64_t refrTm) { self->hasRefrTm = true; self->refrTm = refrTm; } void SV_ASDU_setSmpMod(SV_ASDU self, uint8_t smpMod) { self->hasSmpMod = true; self->smpMod = smpMod; }