/* * iso_connection.c * * Copyright 2013-2023 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 "libiec61850_platform_includes.h" #include "stack_config.h" #include "buffer_chain.h" #include "cotp.h" #include "iso_session.h" #include "iso_presentation.h" #include "acse.h" #include "iso_server.h" #include "hal_socket.h" #include "hal_thread.h" #include "iso_server_private.h" #ifndef DEBUG_ISO_SERVER #ifdef DEBUG #define DEBUG_ISO_SERVER 1 #else #define DEBUG_ISO_SERVER 0 #endif /*DEBUG */ #endif /* DEBUG_ISO_SERVER */ #define RECEIVE_BUF_SIZE CONFIG_MMS_MAXIMUM_PDU_SIZE + 100 #define SEND_BUF_SIZE CONFIG_MMS_MAXIMUM_PDU_SIZE + 100 #define TPKT_RFC1006_HEADER_SIZE 4 struct sIsoConnection { uint8_t* receiveBuffer; ByteBuffer rcvBuffer; uint8_t* sendBuffer; uint8_t* cotpReadBuf; uint8_t* cotpWriteBuf; ByteBuffer cotpReadBuffer; ByteBuffer cotpWriteBuffer; MessageReceivedHandler msgRcvdHandler; UserLayerTickHandler tickHandler; void* handlerParameter; /* context parameter for msgRcvdHandler */ IsoServer isoServer; Socket socket; #if (CONFIG_MMS_SUPPORT_TLS == 1) TLSSocket tlsSocket; #endif int state; IsoSession* session; IsoPresentation* presentation; CotpConnection* cotpConnection; AcseConnection* acseConnection; char* clientAddress; char* localAddress; #if (CONFIG_MMS_THREADLESS_STACK != 1) Thread thread; Semaphore conMutex; #endif #if (CONFIG_MMS_SINGLE_THREADED != 1) || (CONFIG_MMS_THREADLESS_STACK == 1) HandleSet handleSet; #endif }; static void finalizeIsoConnection(IsoConnection self) { if (DEBUG_ISO_SERVER) printf("ISO_SERVER: finalizeIsoConnection (%p)--> close transport connection\n", self); #if (CONFIG_MMS_SUPPORT_TLS == 1) if (self->tlsSocket) TLSSocket_close(self->tlsSocket); #endif #if (CONFIG_MMS_THREADLESS_STACK != 1) #if (CONFIG_MMS_SINGLE_THREADED != 1) if (self->handleSet) { Handleset_destroy(self->handleSet); self->handleSet = NULL; } #endif #endif GLOBAL_FREEMEM(self->session); GLOBAL_FREEMEM(self->presentation); GLOBAL_FREEMEM(self->acseConnection); GLOBAL_FREEMEM(self->cotpReadBuf); GLOBAL_FREEMEM(self->cotpWriteBuf); if (self->cotpConnection) { if (self->cotpConnection->handleSet) Handleset_destroy(self->cotpConnection->handleSet); GLOBAL_FREEMEM(self->cotpConnection->socketExtensionBuffer); } GLOBAL_FREEMEM(self->cotpConnection); self->cotpConnection = NULL; #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_destroy(self->conMutex); #endif GLOBAL_FREEMEM(self->receiveBuffer); GLOBAL_FREEMEM(self->sendBuffer); GLOBAL_FREEMEM(self->clientAddress); GLOBAL_FREEMEM(self->localAddress); IsoServer isoServer = self->isoServer; if (DEBUG_ISO_SERVER) printf("ISO_SERVER: connection %p closed\n", self); private_IsoServer_decreaseConnectionCounter(isoServer); } void IsoConnection_addToHandleSet(const IsoConnection self, HandleSet handles) { Handleset_addSocket(handles, self->socket); } void IsoConnection_removeFromHandleSet(const IsoConnection self, HandleSet handles) { Handleset_removeSocket(handles, self->socket); } void IsoConnection_callTickHandler(IsoConnection self) { CotpConnection_flushBuffer(self->cotpConnection); if (self->tickHandler) { self->tickHandler(self->handlerParameter); } } void IsoConnection_handleTcpConnection(IsoConnection self, bool isSingleThread) { #if (CONFIG_MMS_SINGLE_THREADED != 1) if (isSingleThread == false) { IsoConnection_callTickHandler(self); if (Handleset_waitReady(self->handleSet, 10) < 1) goto exit_function; } #endif TpktState tpktState = CotpConnection_readToTpktBuffer(self->cotpConnection); if (tpktState == TPKT_ERROR) self->state = ISO_CON_STATE_STOPPED; if (tpktState != TPKT_PACKET_COMPLETE) goto exit_function; CotpIndication cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection); switch (cotpIndication) { case COTP_MORE_FRAGMENTS_FOLLOW: goto exit_function; case COTP_CONNECT_INDICATION: if (DEBUG_ISO_SERVER) printf("ISO_SERVER: COTP connection indication\n"); #if (CONFIG_MMS_THREADLESS_STACK != 1) IsoConnection_lock(self); #endif CotpConnection_sendConnectionResponseMessage(self->cotpConnection); #if (CONFIG_MMS_THREADLESS_STACK != 1) IsoConnection_unlock(self); #endif break; case COTP_DATA_INDICATION: { ByteBuffer* cotpPayload = CotpConnection_getPayload(self->cotpConnection); if (DEBUG_ISO_SERVER) printf("ISO_SERVER: COTP data indication (payload size = %i)\n", cotpPayload->size); IsoSessionIndication sIndication = IsoSession_parseMessage(self->session, cotpPayload); ByteBuffer* sessionUserData = IsoSession_getUserData(self->session); switch (sIndication) { case SESSION_CONNECT: if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: session connect indication\n"); if (IsoPresentation_parseConnect(self->presentation, sessionUserData)) { if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: presentation ok\n"); ByteBuffer* acseBuffer = &(self->presentation->nextPayload); AcseIndication aIndication = AcseConnection_parseMessage(self->acseConnection, acseBuffer); if (aIndication == ACSE_ASSOCIATE) { #if (CONFIG_MMS_THREADLESS_STACK != 1) IsoConnection_lock(self); #endif if (DEBUG_ISO_SERVER) printf("ISO_SERVER: cotp_server: acse associate\n"); ByteBuffer mmsRequest; ByteBuffer_wrap(&mmsRequest, self->acseConnection->userDataBuffer, self->acseConnection->userDataBufferSize, self->acseConnection->userDataBufferSize); ByteBuffer mmsResponseBuffer; /* new */ ByteBuffer_wrap(&mmsResponseBuffer, self->sendBuffer, 0, SEND_BUF_SIZE); if (self->msgRcvdHandler != NULL) { self->msgRcvdHandler(self->handlerParameter, &mmsRequest, &mmsResponseBuffer); } struct sBufferChain mmsBufferPartStruct; BufferChain mmsBufferPart = &mmsBufferPartStruct; BufferChain_init(mmsBufferPart, mmsResponseBuffer.size, mmsResponseBuffer.size, NULL, self->sendBuffer); if (mmsResponseBuffer.size > 0) { if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: application payload size: %i\n", mmsResponseBuffer.size); struct sBufferChain acseBufferPartStruct; BufferChain acseBufferPart = &acseBufferPartStruct; acseBufferPart->buffer = self->sendBuffer + mmsBufferPart->length; acseBufferPart->partMaxLength = SEND_BUF_SIZE - mmsBufferPart->length; AcseConnection_createAssociateResponseMessage(self->acseConnection, ACSE_RESULT_ACCEPT, acseBufferPart, mmsBufferPart); struct sBufferChain presentationBufferPartStruct; BufferChain presentationBufferPart = &presentationBufferPartStruct; presentationBufferPart->buffer = self->sendBuffer + acseBufferPart->length; presentationBufferPart->partMaxLength = SEND_BUF_SIZE - acseBufferPart->length; IsoPresentation_createCpaMessage(self->presentation, presentationBufferPart, acseBufferPart); struct sBufferChain sessionBufferPartStruct; BufferChain sessionBufferPart = &sessionBufferPartStruct; sessionBufferPart->buffer = self->sendBuffer + presentationBufferPart->length; sessionBufferPart->partMaxLength = SEND_BUF_SIZE - presentationBufferPart->length; IsoSession_createAcceptSpdu(self->session, sessionBufferPart, presentationBufferPart); CotpConnection_sendDataMessage(self->cotpConnection, sessionBufferPart); } else { if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: association error. No response from application!\n"); } #if (CONFIG_MMS_THREADLESS_STACK != 1) IsoConnection_unlock(self); #endif } else { if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: acse association failed\n"); self->state = ISO_CON_STATE_STOPPED; } } else { self->state = ISO_CON_STATE_STOPPED; } break; case SESSION_DATA: if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: session data indication\n"); if (!IsoPresentation_parseUserData(self->presentation, sessionUserData)) { if (DEBUG_ISO_SERVER) printf("ISO_SERVER: presentation layer error\n"); self->state = ISO_CON_STATE_STOPPED; break; } if (self->presentation->nextContextId == self->presentation->mmsContextId) { if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: mms message\n"); ByteBuffer* mmsRequest = &(self->presentation->nextPayload); ByteBuffer mmsResponseBuffer; #if (CONFIG_MMS_THREADLESS_STACK != 1) IsoConnection_lock(self); #endif ByteBuffer_wrap(&mmsResponseBuffer, self->sendBuffer, 0, SEND_BUF_SIZE); if (self->msgRcvdHandler != NULL) { self->msgRcvdHandler(self->handlerParameter, mmsRequest, &mmsResponseBuffer); } /* send a response if required */ if (mmsResponseBuffer.size > 0) { struct sBufferChain mmsBufferPartStruct; BufferChain mmsBufferPart = &mmsBufferPartStruct; BufferChain_init(mmsBufferPart, mmsResponseBuffer.size, mmsResponseBuffer.size, NULL, self->sendBuffer); struct sBufferChain presentationBufferPartStruct; BufferChain presentationBufferPart = &presentationBufferPartStruct; presentationBufferPart->buffer = self->sendBuffer + mmsBufferPart->length; presentationBufferPart->partMaxLength = SEND_BUF_SIZE - mmsBufferPart->length; IsoPresentation_createUserData(self->presentation, presentationBufferPart, mmsBufferPart); struct sBufferChain sessionBufferPartStruct; BufferChain sessionBufferPart = &sessionBufferPartStruct; sessionBufferPart->buffer = self->sendBuffer + presentationBufferPart->length; sessionBufferPart->partMaxLength = SEND_BUF_SIZE - presentationBufferPart->length; IsoSession_createDataSpdu(self->session, sessionBufferPart, presentationBufferPart); CotpConnection_sendDataMessage(self->cotpConnection, sessionBufferPart); } #if (CONFIG_MMS_THREADLESS_STACK != 1) IsoConnection_unlock(self); #endif } else { if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: unknown presentation layer context!"); } break; case SESSION_FINISH: if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: session finish indication\n"); if (IsoPresentation_parseUserData(self->presentation, sessionUserData)) { if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: presentation ok\n"); #if (CONFIG_MMS_THREADLESS_STACK != 1) IsoConnection_lock(self); #endif struct sBufferChain acseBufferPartStruct; BufferChain acseBufferPart = &acseBufferPartStruct; acseBufferPart->buffer = self->sendBuffer; acseBufferPart->partMaxLength = SEND_BUF_SIZE; AcseConnection_createReleaseResponseMessage(self->acseConnection, acseBufferPart); struct sBufferChain presentationBufferPartStruct; BufferChain presentationBufferPart = &presentationBufferPartStruct; presentationBufferPart->buffer = self->sendBuffer + acseBufferPart->length; presentationBufferPart->partMaxLength = SEND_BUF_SIZE - acseBufferPart->length; IsoPresentation_createUserDataACSE(self->presentation, presentationBufferPart, acseBufferPart); struct sBufferChain sessionBufferPartStruct; BufferChain sessionBufferPart = &sessionBufferPartStruct; sessionBufferPart->buffer = self->sendBuffer + presentationBufferPart->length; sessionBufferPart->partMaxLength = SEND_BUF_SIZE - presentationBufferPart->length; IsoSession_createDisconnectSpdu(self->session, sessionBufferPart, presentationBufferPart); CotpConnection_sendDataMessage(self->cotpConnection, sessionBufferPart); #if (CONFIG_MMS_THREADLESS_STACK != 1) IsoConnection_unlock(self); #endif } self->state = ISO_CON_STATE_STOPPED; break; case SESSION_ABORT: if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: session abort indication\n"); self->state = ISO_CON_STATE_STOPPED; break; case SESSION_ERROR: if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: session error indication\n"); self->state = ISO_CON_STATE_STOPPED; break; default: /* illegal state */ if (DEBUG_ISO_SERVER) printf("ISO_SERVER: iso_connection: session illegal state\n"); self->state = ISO_CON_STATE_STOPPED; break; } CotpConnection_resetPayload(self->cotpConnection); } break; case COTP_ERROR: if (DEBUG_ISO_SERVER) printf("ISO_SERVER: Connection closed\n"); self->state = ISO_CON_STATE_STOPPED; break; default: if (DEBUG_ISO_SERVER) printf("ISO_SERVER: COTP unknown indication: %i\n", cotpIndication); self->state = ISO_CON_STATE_STOPPED; break; } exit_function: return; } #if ((CONFIG_MMS_SINGLE_THREADED == 0) && (CONFIG_MMS_THREADLESS_STACK == 0)) /* only for multi-thread mode */ static void* handleTcpConnection(void* parameter) { IsoConnection self = (IsoConnection) parameter; while(self->state == ISO_CON_STATE_RUNNING) IsoConnection_handleTcpConnection(self, false); IsoServer_closeConnection(self->isoServer, self); finalizeIsoConnection(self); self->state = ISO_CON_STATE_TERMINATED; return NULL; } #endif /* (CONFIG_MMS_SINGLE_THREADED == 0) */ IsoConnection IsoConnection_create(Socket socket, IsoServer isoServer, bool isSingleThread) { IsoConnection self = (IsoConnection) GLOBAL_CALLOC(1, sizeof(struct sIsoConnection)); if (self) { self->socket = socket; #if (CONFIG_MMS_SUPPORT_TLS == 1) if (IsoServer_getTLSConfiguration(isoServer) != NULL) { self->tlsSocket = TLSSocket_create(socket, IsoServer_getTLSConfiguration(isoServer), true); if (self->tlsSocket == NULL) { if (DEBUG_ISO_SERVER) printf("ISO_SERVER: IsoConnection - TLS initialization failed\n"); GLOBAL_FREEMEM(self); return NULL; } } #endif /* (CONFIG_MMS_SUPPORT_TLS == 1) */ self->receiveBuffer = (uint8_t*) GLOBAL_MALLOC(RECEIVE_BUF_SIZE); self->sendBuffer = (uint8_t*) GLOBAL_MALLOC(SEND_BUF_SIZE); self->msgRcvdHandler = NULL; self->tickHandler = NULL; self->handlerParameter = NULL; self->isoServer = isoServer; self->state = ISO_CON_STATE_RUNNING; self->clientAddress = Socket_getPeerAddress(self->socket); self->localAddress = Socket_getLocalAddress(self->socket); #if (CONFIG_MMS_THREADLESS_STACK != 1) self->conMutex = Semaphore_create(1); #endif ByteBuffer_wrap(&(self->rcvBuffer), self->receiveBuffer, 0, RECEIVE_BUF_SIZE); self->cotpReadBuf = (uint8_t*) GLOBAL_MALLOC(CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); self->cotpWriteBuf = (uint8_t*) GLOBAL_MALLOC(CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); ByteBuffer_wrap(&(self->cotpReadBuffer), self->cotpReadBuf, 0, CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); ByteBuffer_wrap(&(self->cotpWriteBuffer), self->cotpWriteBuf, 0, CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE); self->cotpConnection = (CotpConnection*) GLOBAL_CALLOC(1, sizeof(CotpConnection)); int socketExtensionBufferSize = CONFIG_MMS_MAXIMUM_PDU_SIZE + 1000; uint8_t* socketExtensionBuffer = (uint8_t*)GLOBAL_MALLOC(socketExtensionBufferSize); CotpConnection_init(self->cotpConnection, self->socket, &(self->rcvBuffer), &(self->cotpReadBuffer), &(self->cotpWriteBuffer), socketExtensionBuffer, socketExtensionBufferSize); #if (CONFIG_MMS_SUPPORT_TLS == 1) if (self->tlsSocket) self->cotpConnection->tlsSocket = self->tlsSocket; #endif /* (CONFIG_MMS_SUPPORT_TLS == 1) */ self->session = (IsoSession*) GLOBAL_CALLOC(1, sizeof(IsoSession)); IsoSession_init(self->session); self->presentation = (IsoPresentation*) GLOBAL_CALLOC(1, sizeof(IsoPresentation)); IsoPresentation_init(self->presentation); self->acseConnection = (AcseConnection*) GLOBAL_CALLOC(1, sizeof(AcseConnection)); #if (CONFIG_MMS_SUPPORT_TLS == 1) AcseConnection_init(self->acseConnection, IsoServer_getAuthenticator(self->isoServer), IsoServer_getAuthenticatorParameter(self->isoServer), self->tlsSocket); #else AcseConnection_init(self->acseConnection, IsoServer_getAuthenticator(self->isoServer), IsoServer_getAuthenticatorParameter(self->isoServer), NULL); #endif if (DEBUG_ISO_SERVER) printf("ISO_SERVER: IsoConnection: Start to handle connection for client %s\n", self->clientAddress); #if (CONFIG_MMS_SINGLE_THREADED == 0) #if (CONFIG_MMS_THREADLESS_STACK == 0) if (isSingleThread == false) { self->handleSet = Handleset_new(); Handleset_addSocket(self->handleSet, self->socket); self->thread = Thread_create((ThreadExecutionFunction) handleTcpConnection, self, false); } #endif #endif } return self; } int IsoConnection_getState(IsoConnection self) { return self->state; } void IsoConnection_start(IsoConnection self) { #if (CONFIG_MMS_SINGLE_THREADED == 0) #if (CONFIG_MMS_THREADLESS_STACK == 0) Thread_start(self->thread); #endif #endif } void IsoConnection_destroy(IsoConnection self) { #if (CONFIG_MMS_THREADLESS_STACK == 0) && (CONFIG_MMS_SINGLE_THREADED == 0) if (self->thread) Thread_destroy(self->thread); #endif if (self->socket != NULL) Socket_destroy(self->socket); #if (CONFIG_MMS_SINGLE_THREADED != 1) || (CONFIG_MMS_THREADLESS_STACK == 1) if (self->handleSet) Handleset_destroy(self->handleSet); #endif if (self->cotpConnection) { if (self->cotpConnection->handleSet) Handleset_destroy(self->cotpConnection->handleSet); GLOBAL_FREEMEM(self->cotpConnection->socketExtensionBuffer); } GLOBAL_FREEMEM(self); } char* IsoConnection_getPeerAddress(IsoConnection self) { return self->clientAddress; } char* IsoConnection_getLocalAddress(IsoConnection self) { return self->localAddress; } void IsoConnection_lock(IsoConnection self) { #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_wait(self->conMutex); #endif } void IsoConnection_unlock(IsoConnection self) { #if (CONFIG_MMS_THREADLESS_STACK != 1) Semaphore_post(self->conMutex); #endif } bool IsoConnection_sendMessage(IsoConnection self, ByteBuffer* message) { bool success = false; if (self->state == ISO_CON_STATE_STOPPED) { if (DEBUG_ISO_SERVER) printf("DEBUG_ISO_SERVER: sendMessage: connection already stopped!\n"); goto exit_error; } struct sBufferChain payloadBufferStruct; BufferChain payloadBuffer = &payloadBufferStruct; payloadBuffer->length = message->size; payloadBuffer->partLength = message->size; payloadBuffer->partMaxLength = message->size; payloadBuffer->buffer = message->buffer; payloadBuffer->nextPart = NULL; struct sBufferChain presentationBufferStruct; BufferChain presentationBuffer = &presentationBufferStruct; presentationBuffer->buffer = self->sendBuffer; presentationBuffer->partMaxLength = SEND_BUF_SIZE; IsoPresentation_createUserData(self->presentation, presentationBuffer, payloadBuffer); struct sBufferChain sessionBufferStruct; BufferChain sessionBuffer = &sessionBufferStruct; sessionBuffer->buffer = self->sendBuffer + presentationBuffer->partLength; IsoSession_createDataSpdu(self->session, sessionBuffer, presentationBuffer); CotpIndication indication; indication = CotpConnection_sendDataMessage(self->cotpConnection, sessionBuffer); if (DEBUG_ISO_SERVER) { if (indication != COTP_OK) printf("ISO_SERVER: IsoConnection_sendMessage failed!\n"); else printf("ISO_SERVER: IsoConnection_sendMessage success!\n"); } if (indication == COTP_OK) success = true; exit_error: return success; } void IsoConnection_close(IsoConnection self) { if (self->state != ISO_CON_STATE_TERMINATED) { self->state = ISO_CON_STATE_STOPPED; #if (CONFIG_MMS_THREADLESS_STACK != 1) && (CONFIG_MMS_SINGLE_THREADED != 1) /* wait for connection thread to terminate */ if (self->thread) { Thread_destroy(self->thread); self->thread = NULL; } else { finalizeIsoConnection(self); self->state = ISO_CON_STATE_TERMINATED; } #else finalizeIsoConnection(self); self->state = ISO_CON_STATE_TERMINATED; #endif } } void IsoConnection_installListener(IsoConnection self, MessageReceivedHandler rcvdHandler, UserLayerTickHandler tickHandler, void* parameter) { self->msgRcvdHandler = rcvdHandler; self->tickHandler = tickHandler; self->handlerParameter = parameter; } void* IsoConnection_getSecurityToken(IsoConnection self) { return self->acseConnection->securityToken; } bool IsoConnection_isRunning(IsoConnection self) { if (self->state == ISO_CON_STATE_RUNNING) return true; else return false; }