You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
485 lines
18 KiB
C
485 lines
18 KiB
C
![]()
11 years ago
|
/*
|
||
|
* iso_connection.c
|
||
|
*
|
||
|
* 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 <http://www.gnu.org/licenses/>.
|
||
|
*
|
||
|
* See COPYING file for the complete license text.
|
||
|
*/
|
||
|
|
||
|
#include "libiec61850_platform_includes.h"
|
||
|
|
||
|
#include "stack_config.h"
|
||
|
#include "byte_stream.h"
|
||
|
#include "buffer_chain.h"
|
||
|
#include "cotp.h"
|
||
|
#include "iso_session.h"
|
||
|
#include "iso_presentation.h"
|
||
|
#include "acse.h"
|
||
|
#include "iso_server.h"
|
||
|
#include "socket.h"
|
||
|
#include "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 ISO_CON_STATE_RUNNING 1
|
||
|
#define ISO_CON_STATE_STOPPED 0
|
||
|
|
||
|
struct sIsoConnection
|
||
|
{
|
||
|
uint8_t* receiveBuffer;
|
||
|
uint8_t* sendBuffer;
|
||
|
MessageReceivedHandler msgRcvdHandler;
|
||
|
IsoServer isoServer;
|
||
|
void* msgRcvdHandlerParameter;
|
||
|
Socket socket;
|
||
|
int state;
|
||
|
IsoSession* session;
|
||
|
IsoPresentation* presentation;
|
||
|
CotpConnection* cotpConnection;
|
||
|
char* clientAddress;
|
||
|
Thread thread;
|
||
|
Semaphore conMutex;
|
||
|
|
||
|
|
||
|
void* securityToken;
|
||
|
};
|
||
|
|
||
|
static void
|
||
|
handleTcpConnection(IsoConnection self)
|
||
|
{
|
||
|
if (DEBUG_ISO_SERVER)
|
||
|
printf("ISO_SERVER: connection %p started\n", self);
|
||
|
|
||
|
CotpIndication cotpIndication;
|
||
|
|
||
|
IsoSessionIndication sIndication;
|
||
|
|
||
|
AcseIndication aIndication;
|
||
|
AcseConnection acseConnection;
|
||
|
|
||
|
ByteBuffer receiveBuffer;
|
||
|
|
||
|
self->cotpConnection = (CotpConnection*) calloc(1, sizeof(CotpConnection));
|
||
|
CotpConnection_init(self->cotpConnection, self->socket, &receiveBuffer);
|
||
|
|
||
|
self->session = (IsoSession*) calloc(1, sizeof(IsoSession));
|
||
|
IsoSession_init(self->session);
|
||
|
|
||
|
self->presentation = (IsoPresentation*) calloc(1, sizeof(IsoPresentation));
|
||
|
IsoPresentation_init(self->presentation);
|
||
|
|
||
|
AcseConnection_init(&acseConnection, IsoServer_getAuthenticator(self->isoServer),
|
||
|
IsoServer_getAuthenticatorParameter(self->isoServer));
|
||
|
|
||
|
while (self->msgRcvdHandlerParameter == NULL)
|
||
|
Thread_sleep(1);
|
||
|
|
||
|
if (DEBUG_ISO_SERVER)
|
||
|
printf("ISO_SERVER: IsoConnection: Start to handle connection for client %s\n", self->clientAddress);
|
||
|
|
||
|
while (self->state == ISO_CON_STATE_RUNNING) {
|
||
|
ByteBuffer_wrap(&receiveBuffer, self->receiveBuffer, 0, RECEIVE_BUF_SIZE);
|
||
|
|
||
|
cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection);
|
||
|
|
||
|
switch (cotpIndication) {
|
||
|
case CONNECT_INDICATION:
|
||
|
if (DEBUG_ISO_SERVER)
|
||
|
printf("ISO_SERVER: COTP connection indication\n");
|
||
|
|
||
|
Semaphore_wait(self->conMutex);
|
||
|
|
||
|
CotpConnection_sendConnectionResponseMessage(self->cotpConnection);
|
||
|
|
||
|
Semaphore_post(self->conMutex);
|
||
|
|
||
|
break;
|
||
|
case DATA_INDICATION:
|
||
|
{
|
||
|
if (DEBUG_ISO_SERVER)
|
||
|
printf("ISO_SERVER: COTP data indication\n");
|
||
|
|
||
|
ByteBuffer* cotpPayload = CotpConnection_getPayload(self->cotpConnection);
|
||
|
|
||
|
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);
|
||
|
|
||
|
aIndication = AcseConnection_parseMessage(&acseConnection, acseBuffer);
|
||
|
|
||
|
self->securityToken = acseConnection.securityToken;
|
||
|
|
||
|
if (aIndication == ACSE_ASSOCIATE) {
|
||
|
|
||
|
Semaphore_wait(self->conMutex);
|
||
|
|
||
|
if (DEBUG_ISO_SERVER)
|
||
|
printf("ISO_SERVER: cotp_server: acse associate\n");
|
||
|
|
||
|
ByteBuffer mmsRequest;
|
||
|
|
||
|
ByteBuffer_wrap(&mmsRequest, acseConnection.userDataBuffer,
|
||
|
acseConnection.userDataBufferSize, acseConnection.userDataBufferSize);
|
||
|
ByteBuffer mmsResponseBuffer; /* new */
|
||
|
|
||
|
ByteBuffer_wrap(&mmsResponseBuffer, self->sendBuffer, 0, SEND_BUF_SIZE);
|
||
|
|
||
|
self->msgRcvdHandler(self->msgRcvdHandlerParameter,
|
||
|
&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_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(&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");
|
||
|
}
|
||
|
|
||
|
Semaphore_post(self->conMutex);
|
||
|
}
|
||
|
else {
|
||
|
if (DEBUG_ISO_SERVER)
|
||
|
printf("ISO_SERVER: iso_connection: acse association failed\n");
|
||
|
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: cotp_server: presentation 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;
|
||
|
|
||
|
IsoServer_userLock(self->isoServer);
|
||
|
Semaphore_wait(self->conMutex);
|
||
|
|
||
|
ByteBuffer_wrap(&mmsResponseBuffer, self->sendBuffer, 0, SEND_BUF_SIZE);
|
||
|
|
||
|
self->msgRcvdHandler(self->msgRcvdHandlerParameter,
|
||
|
mmsRequest, &mmsResponseBuffer);
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
Semaphore_post(self->conMutex);
|
||
|
IsoServer_userUnlock(self->isoServer);
|
||
|
}
|
||
|
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");
|
||
|
|
||
|
struct sBufferChain acseBufferPartStruct;
|
||
|
BufferChain acseBufferPart = &acseBufferPartStruct;
|
||
|
acseBufferPart->buffer = self->sendBuffer;
|
||
|
acseBufferPart->partMaxLength = SEND_BUF_SIZE;
|
||
|
|
||
|
AcseConnection_createReleaseResponseMessage(&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);
|
||
|
}
|
||
|
|
||
|
self->state = ISO_CON_STATE_STOPPED;
|
||
|
|
||
|
break;
|
||
|
|
||
|
case SESSION_ABORT:
|
||
|
self->state = ISO_CON_STATE_STOPPED;
|
||
|
break;
|
||
|
|
||
|
case SESSION_ERROR:
|
||
|
self->state = ISO_CON_STATE_STOPPED;
|
||
|
break;
|
||
|
|
||
|
default: /* illegal state */
|
||
|
self->state = ISO_CON_STATE_STOPPED;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case 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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (DEBUG_ISO_SERVER)
|
||
|
printf("ISO_SERVER: Connection handling loop finished --> close transport connection\n");
|
||
|
|
||
|
IsoServer_closeConnection(self->isoServer, self);
|
||
|
|
||
|
if (self->socket != NULL)
|
||
|
Socket_destroy(self->socket);
|
||
|
|
||
|
free(self->session);
|
||
|
free(self->presentation);
|
||
|
|
||
|
AcseConnection_destroy(&acseConnection);
|
||
|
|
||
|
CotpConnection_destroy(self->cotpConnection);
|
||
|
free(self->cotpConnection);
|
||
|
|
||
|
Semaphore_destroy(self->conMutex);
|
||
|
|
||
|
free(self->receiveBuffer);
|
||
|
free(self->sendBuffer);
|
||
|
free(self->clientAddress);
|
||
|
|
||
|
IsoServer isoServer = self->isoServer;
|
||
|
|
||
|
free(self);
|
||
|
|
||
|
if (DEBUG_ISO_SERVER)
|
||
|
printf("ISO_SERVER: connection %p closed\n", self);
|
||
|
|
||
|
private_IsoServer_decreaseConnectionCounter(isoServer);
|
||
|
}
|
||
|
|
||
|
IsoConnection
|
||
|
IsoConnection_create(Socket socket, IsoServer isoServer)
|
||
|
{
|
||
|
IsoConnection self = (IsoConnection) calloc(1, sizeof(struct sIsoConnection));
|
||
|
self->socket = socket;
|
||
|
self->receiveBuffer = (uint8_t*) malloc(RECEIVE_BUF_SIZE);
|
||
|
self->sendBuffer = (uint8_t*) malloc(SEND_BUF_SIZE);
|
||
|
self->msgRcvdHandler = NULL;
|
||
|
self->msgRcvdHandlerParameter = NULL;
|
||
|
self->isoServer = isoServer;
|
||
|
self->state = ISO_CON_STATE_RUNNING;
|
||
|
self->clientAddress = Socket_getPeerAddress(self->socket);
|
||
|
|
||
|
self->thread = Thread_create((ThreadExecutionFunction) handleTcpConnection, self, true);
|
||
|
self->conMutex = Semaphore_create(1);
|
||
|
|
||
|
Thread_start(self->thread);
|
||
|
|
||
|
if (DEBUG_ISO_SERVER)
|
||
|
printf("ISO_SERVER: new iso connection thread started\n");
|
||
|
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
char*
|
||
|
IsoConnection_getPeerAddress(IsoConnection self)
|
||
|
{
|
||
|
return self->clientAddress;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
IsoConnection_sendMessage(IsoConnection self, ByteBuffer* message, bool handlerMode)
|
||
|
{
|
||
|
if (self->state == ISO_CON_STATE_STOPPED) {
|
||
|
if (DEBUG_ISO_SERVER)
|
||
|
printf("DEBUG_ISO_SERVER: sendMessage: connection already stopped!\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!handlerMode)
|
||
|
Semaphore_wait(self->conMutex);
|
||
|
|
||
|
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 != OK)
|
||
|
printf("ISO_SERVER: IsoConnection_sendMessage failed!\n");
|
||
|
else
|
||
|
printf("ISO_SERVER: IsoConnection_sendMessage success!\n");
|
||
|
}
|
||
|
|
||
|
if (!handlerMode)
|
||
|
Semaphore_post(self->conMutex);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
IsoConnection_close(IsoConnection self)
|
||
|
{
|
||
|
if (self->state != ISO_CON_STATE_STOPPED) {
|
||
|
Socket socket = self->socket;
|
||
|
self->state = ISO_CON_STATE_STOPPED;
|
||
|
self->socket = NULL;
|
||
|
|
||
|
Socket_destroy(socket);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
IsoConnection_installListener(IsoConnection self, MessageReceivedHandler handler,
|
||
|
void* parameter)
|
||
|
{
|
||
|
self->msgRcvdHandler = handler;
|
||
|
self->msgRcvdHandlerParameter = parameter;
|
||
|
}
|
||
|
|
||
|
void*
|
||
|
IsoConnection_getSecurityToken(IsoConnection self)
|
||
|
{
|
||
|
return self->securityToken;
|
||
|
}
|
||
|
|