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.
libiec61850/src/mms/iso_client/iso_client_connection.c

618 lines
18 KiB
C

/*
* iso_client_connection.c
*
* Client side representation of the ISO stack (COTP, session, presentation, ACSE)
*
* Copyright 2013 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 "hal_socket.h"
#include "hal_thread.h"
#include "cotp.h"
#include "iso_session.h"
#include "iso_presentation.h"
#include "iso_client_connection.h"
#include "acse.h"
#ifndef DEBUG_ISO_CLIENT
#ifdef DEBUG
#define DEBUG_ISO_CLIENT 1
#else
#define DEBUG_ISO_CLIENT 0
#endif /*DEBUG */
#endif /* DEBUG_ISO_SERVER */
#define STATE_IDLE 0
#define STATE_ASSOCIATED 1
#define STATE_ERROR 2
#define TPKT_RFC1006_HEADER_SIZE 4
#define ISO_CLIENT_BUFFER_SIZE CONFIG_MMS_MAXIMUM_PDU_SIZE + 100
struct sIsoClientConnection
{
IsoIndicationCallback callback;
void* callbackParameter;
volatile int state;
Socket socket;
CotpConnection* cotpConnection;
IsoPresentation* presentation;
IsoSession* session;
AcseConnection acseConnection;
uint8_t* sendBuffer; /* ISO/MMS send buffer */
uint8_t* receiveBuf; /* ISO/MMS receive buffer */
ByteBuffer* receiveBuffer;
ByteBuffer* transmitPayloadBuffer;
Semaphore transmitBufferMutex;
ByteBuffer* receivePayloadBuffer;
Semaphore receiveBufferMutex;
uint8_t* cotpReadBuf;
uint8_t* cotpWriteBuf;
ByteBuffer* cotpReadBuffer;
ByteBuffer* cotpWriteBuffer;
volatile bool handlingThreadRunning;
volatile bool stopHandlingThread;
volatile bool destroyHandlingThread;
volatile bool startHandlingThread;
Thread thread;
};
static void
connectionHandlingThread(IsoClientConnection self)
{
IsoSessionIndication sessionIndication;
self->handlingThreadRunning = true;
self->stopHandlingThread = false;
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT_CONNECTION: new connection %p\n", self);
/* Wait until lower layer association is finished */
Semaphore_wait(self->receiveBufferMutex);
CotpConnection_resetPayload(self->cotpConnection);
while (true) {
TpktState packetState;
printf("P1\n");
while ((packetState = CotpConnection_readToTpktBuffer(self->cotpConnection)) == TPKT_WAITING)
{
Thread_sleep(1);
if (self->stopHandlingThread) {
packetState = TPKT_ERROR;
break;
}
}
printf("P2\n");
if (packetState == TPKT_ERROR)
break;
CotpIndication cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection);
if (cotpIndication == COTP_MORE_FRAGMENTS_FOLLOW)
continue;
if (cotpIndication != COTP_DATA_INDICATION)
break;
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT_CONNECTION: parse message\n");
sessionIndication =
IsoSession_parseMessage(self->session,
CotpConnection_getPayload(self->cotpConnection));
if (sessionIndication != SESSION_DATA) {
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT_CONNECTION: Invalid session message\n");
break;
}
if (!IsoPresentation_parseUserData(self->presentation, IsoSession_getUserData(self->session))) {
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT_CONNECTION: Invalid presentation message\n");
break;
}
printf("P3\n");
self->callback(ISO_IND_DATA, self->callbackParameter,
&(self->presentation->nextPayload));
printf("P4\n");
/* wait for user to release the buffer */
Semaphore_wait(self->receiveBufferMutex);
printf("P5\n");
CotpConnection_resetPayload(self->cotpConnection);
}
printf("I1\n");
self->callback(ISO_IND_CLOSED, self->callbackParameter, NULL);
printf("I2\n");
self->state = STATE_IDLE;
Socket_destroy(self->socket);
printf("I3\n");
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT_CONNECTION: exit connection %p\n", self);
/* release buffer to enable reuse of client connection */
Semaphore_post(self->receiveBufferMutex);
printf("I4\n");
self->handlingThreadRunning = false;
}
static void*
connectionThreadFunction(void* parameter)
{
IsoClientConnection self = (IsoClientConnection) parameter;
while (self->destroyHandlingThread == false) {
if (self->startHandlingThread) {
self->startHandlingThread = false;
connectionHandlingThread(self);
}
Thread_sleep(1);
}
self->destroyHandlingThread = false;
return NULL;
}
IsoClientConnection
IsoClientConnection_create(IsoIndicationCallback callback, void* callbackParameter)
{
IsoClientConnection self = (IsoClientConnection) GLOBAL_CALLOC(1, sizeof(struct sIsoClientConnection));
if (self == NULL)
return NULL;
self->callback = callback;
self->callbackParameter = callbackParameter;
self->state = STATE_IDLE;
self->sendBuffer = (uint8_t*) GLOBAL_MALLOC(ISO_CLIENT_BUFFER_SIZE);
self->transmitPayloadBuffer = (ByteBuffer*) GLOBAL_CALLOC(1, sizeof(ByteBuffer));
self->transmitPayloadBuffer->buffer = self->sendBuffer;
self->transmitPayloadBuffer->maxSize = ISO_CLIENT_BUFFER_SIZE;
self->receivePayloadBuffer = (ByteBuffer*) GLOBAL_CALLOC(1, sizeof(ByteBuffer));
self->transmitBufferMutex = Semaphore_create(1);
self->receiveBufferMutex = Semaphore_create(1);
self->receiveBuf = (uint8_t*) GLOBAL_MALLOC(ISO_CLIENT_BUFFER_SIZE);
self->receiveBuffer = (ByteBuffer*) GLOBAL_CALLOC(1, sizeof(ByteBuffer));
ByteBuffer_wrap(self->receiveBuffer, self->receiveBuf, 0, ISO_CLIENT_BUFFER_SIZE);
self->presentation = (IsoPresentation*) GLOBAL_CALLOC(1, sizeof(IsoPresentation));
self->session = (IsoSession*) GLOBAL_CALLOC(1, sizeof(IsoSession));
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);
self->cotpReadBuffer = (ByteBuffer*) GLOBAL_CALLOC(1, sizeof(ByteBuffer));
ByteBuffer_wrap(self->cotpReadBuffer, self->cotpReadBuf, 0, CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE);
self->cotpWriteBuffer = (ByteBuffer*) GLOBAL_CALLOC(1, sizeof(ByteBuffer));
ByteBuffer_wrap(self->cotpWriteBuffer, self->cotpWriteBuf, 0, CONFIG_COTP_MAX_TPDU_SIZE + TPKT_RFC1006_HEADER_SIZE);
self->cotpConnection = (CotpConnection*) GLOBAL_CALLOC(1, sizeof(CotpConnection));
return self;
}
void
IsoClientConnection_associate(IsoClientConnection self, IsoConnectionParameters params,
ByteBuffer* payload, uint32_t connectTimeoutInMs)
{
self->socket = TcpSocket_create();
Socket_setConnectTimeout(self->socket, connectTimeoutInMs);
if (!Socket_connect(self->socket, params->hostname, params->tcpPort))
goto returnError;
/* COTP (ISO transport) handshake */
CotpConnection_init(self->cotpConnection, self->socket, self->receiveBuffer, self->cotpReadBuffer, self->cotpWriteBuffer);
CotpIndication cotpIndication =
CotpConnection_sendConnectionRequestMessage(self->cotpConnection, params);
TpktState packetState;
uint64_t timeout = Hal_getTimeInMs() + CONFIG_TCP_READ_TIMEOUT_MS;
while (((packetState = CotpConnection_readToTpktBuffer(self->cotpConnection)) == TPKT_WAITING)
&& (Hal_getTimeInMs() < timeout))
{
Thread_sleep(1);
}
if (packetState != TPKT_PACKET_COMPLETE)
goto returnError;
cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection);
if (cotpIndication != COTP_CONNECT_INDICATION)
goto returnError;
/* Upper layers handshake */
struct sBufferChain sAcsePayload;
BufferChain acsePayload = &sAcsePayload;
acsePayload->buffer = payload->buffer;
acsePayload->partLength = payload->size;
acsePayload->length = payload->size;
acsePayload->nextPart = NULL;
AcseConnection_init(&(self->acseConnection), NULL, NULL);
AcseAuthenticationParameter authParameter = params->acseAuthParameter;
struct sBufferChain sAcseBuffer;
BufferChain acseBuffer = &sAcseBuffer;
acseBuffer->buffer = self->sendBuffer + payload->size;
acseBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE - acsePayload->length;
AcseConnection_createAssociateRequestMessage(&(self->acseConnection), params, acseBuffer, acsePayload,
authParameter);
struct sBufferChain sPresentationBuffer;
BufferChain presentationBuffer = &sPresentationBuffer;
presentationBuffer->buffer = self->sendBuffer + acseBuffer->length;
presentationBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE - acseBuffer->length;
IsoPresentation_init(self->presentation);
IsoPresentation_createConnectPdu(self->presentation, params, presentationBuffer, acseBuffer);
struct sBufferChain sSessionBuffer;
BufferChain sessionBuffer = &sSessionBuffer;
sessionBuffer->buffer = self->sendBuffer + presentationBuffer->length;
IsoSession_init(self->session);
IsoSession_createConnectSpdu(self->session, params, sessionBuffer,
presentationBuffer);
CotpConnection_sendDataMessage(self->cotpConnection, sessionBuffer);
Semaphore_post(self->transmitBufferMutex);
while ((packetState = CotpConnection_readToTpktBuffer(self->cotpConnection)) == TPKT_WAITING)
{
Thread_sleep(1);
}
cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection);
if (cotpIndication != COTP_DATA_INDICATION)
goto returnError;
IsoSessionIndication sessionIndication;
sessionIndication =
IsoSession_parseMessage(self->session, CotpConnection_getPayload(self->cotpConnection));
if (sessionIndication != SESSION_CONNECT) {
if (DEBUG_ISO_CLIENT)
printf("IsoClientConnection_associate: no session connect indication\n");
goto returnError;
}
if (!IsoPresentation_parseAcceptMessage(self->presentation, IsoSession_getUserData(self->session))) {
if (DEBUG_ISO_CLIENT)
printf("IsoClientConnection_associate: no presentation ok indication\n");
goto returnError;
}
AcseIndication acseIndication;
acseIndication = AcseConnection_parseMessage(&(self->acseConnection), &self->presentation->nextPayload);
if (acseIndication != ACSE_ASSOCIATE) {
if (DEBUG_ISO_CLIENT)
printf("IsoClientConnection_associate: no ACSE_ASSOCIATE indication\n");
goto returnError;
}
ByteBuffer_wrap(self->receivePayloadBuffer, self->acseConnection.userDataBuffer,
self->acseConnection.userDataBufferSize, self->acseConnection.userDataBufferSize);
Semaphore_wait(self->receiveBufferMutex);
self->callback(ISO_IND_ASSOCIATION_SUCCESS, self->callbackParameter, self->receivePayloadBuffer);
/* wait for upper layer to release buffer */
Semaphore_wait(self->receiveBufferMutex);
self->state = STATE_ASSOCIATED;
if (self->thread == NULL) {
self->thread = Thread_create(connectionThreadFunction, self, false);
Thread_start(self->thread);
}
self->startHandlingThread = true;
while (self->handlingThreadRunning == false)
Thread_sleep(1);
return;
returnError:
self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL);
self->state = STATE_ERROR;
Socket_destroy(self->socket);
self->socket = NULL;
Semaphore_post(self->transmitBufferMutex); //TODO check
return;
}
void
IsoClientConnection_sendMessage(IsoClientConnection self, ByteBuffer* payloadBuffer)
{
struct sBufferChain payloadBCMemory;
BufferChain payload = &payloadBCMemory;
BufferChain_init(payload, payloadBuffer->size, payloadBuffer->size, NULL, payloadBuffer->buffer);
struct sBufferChain presentationBCMemory;
BufferChain presentationBuffer = &presentationBCMemory;
presentationBuffer->buffer = self->sendBuffer + payload->length;
presentationBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE;
IsoPresentation_createUserData(self->presentation, presentationBuffer, payload);
struct sBufferChain sessionBufferBCMemory;
BufferChain sessionBuffer = &sessionBufferBCMemory;
IsoSession_createDataSpdu(self->session, sessionBuffer, presentationBuffer);
CotpIndication indication = CotpConnection_sendDataMessage(self->cotpConnection, sessionBuffer);
/* release transmit buffer for use by API client */
Semaphore_post(self->transmitBufferMutex);
if (indication != COTP_OK)
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: IsoClientConnection_sendMessage: send message failed!\n");
}
void
IsoClientConnection_close(IsoClientConnection self)
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: IsoClientConnection_close\n");
printf("B1\n");
if (self->handlingThreadRunning) {
self->stopHandlingThread = true;
while (self->handlingThreadRunning)
Thread_sleep(1);
}
printf("B2\n");
self->state = STATE_IDLE;
}
void
IsoClientConnection_destroy(IsoClientConnection self)
{
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: IsoClientConnection_destroy\n");
if (self->state == STATE_ASSOCIATED) {
if (DEBUG_ISO_CLIENT)
printf("ISO_CLIENT: call IsoClientConnection_close\n");
IsoClientConnection_close(self);
}
/* stop handling thread */
self->destroyHandlingThread = true;
if (self->thread != NULL) {
while (self->destroyHandlingThread)
Thread_sleep(1);
Thread_destroy(self->thread);
}
if (self->receiveBuf != NULL)
GLOBAL_FREEMEM(self->receiveBuf);
if (self->receiveBuffer != NULL)
GLOBAL_FREEMEM(self->receiveBuffer);
if (self->cotpConnection != NULL)
GLOBAL_FREEMEM(self->cotpConnection);
if (self->cotpReadBuffer != NULL)
GLOBAL_FREEMEM(self->cotpReadBuffer);
if (self->cotpReadBuf != NULL)
GLOBAL_FREEMEM(self->cotpReadBuf);
if (self->cotpWriteBuffer != NULL)
GLOBAL_FREEMEM(self->cotpWriteBuffer);
if (self->cotpWriteBuf != NULL)
GLOBAL_FREEMEM(self->cotpWriteBuf);
if (self->session != NULL)
GLOBAL_FREEMEM(self->session);
if (self->presentation != NULL)
GLOBAL_FREEMEM(self->presentation);
GLOBAL_FREEMEM(self->transmitPayloadBuffer);
GLOBAL_FREEMEM(self->receivePayloadBuffer);
Semaphore_destroy(self->receiveBufferMutex);
Semaphore_destroy(self->transmitBufferMutex);
GLOBAL_FREEMEM(self->sendBuffer);
GLOBAL_FREEMEM(self);
}
bool
IsoClientConnection_abort(IsoClientConnection self)
{
//TODO block other messages from being sent
IsoClientConnection_allocateTransmitBuffer(self);
struct sBufferChain sAcseBuffer;
BufferChain acseBuffer = &sAcseBuffer;
acseBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE;
acseBuffer->buffer = self->sendBuffer;
acseBuffer->nextPart = NULL;
AcseConnection_createAbortMessage(NULL, acseBuffer, false);
struct sBufferChain sPresentationBuffer;
BufferChain presentationBuffer = &sPresentationBuffer;
presentationBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE - acseBuffer->length;
presentationBuffer->buffer = self->sendBuffer + acseBuffer->length;
presentationBuffer->nextPart = acseBuffer;
IsoPresentation_createAbortUserMessage(self->presentation, presentationBuffer, acseBuffer);
struct sBufferChain sSessionBuffer;
BufferChain sessionBuffer = &sSessionBuffer;
sessionBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE - presentationBuffer->length;
sessionBuffer->buffer = self->sendBuffer + presentationBuffer->length;
sessionBuffer->nextPart = presentationBuffer;
IsoSession_createAbortSpdu(self->session, sessionBuffer, presentationBuffer);
CotpConnection_sendDataMessage(self->cotpConnection, sessionBuffer);
Semaphore_post(self->transmitBufferMutex);
uint64_t timeout = Hal_getTimeInMs() + CONFIG_TCP_READ_TIMEOUT_MS;
while ((self->handlingThreadRunning == true) && (Hal_getTimeInMs() < timeout));
if (self->handlingThreadRunning)
return false;
else
return true;
}
void
IsoClientConnection_release(IsoClientConnection self)
{
//TODO block other messages from being sent
IsoClientConnection_allocateTransmitBuffer(self);
struct sBufferChain sAcseBuffer;
BufferChain acseBuffer = &sAcseBuffer;
acseBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE;
acseBuffer->buffer = self->sendBuffer;
acseBuffer->nextPart = NULL;
AcseConnection_createReleaseRequestMessage(NULL, acseBuffer);
struct sBufferChain sPresentationBuffer;
BufferChain presentationBuffer = &sPresentationBuffer;
presentationBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE - acseBuffer->length;
presentationBuffer->buffer = self->sendBuffer + acseBuffer->length;
presentationBuffer->nextPart = acseBuffer;
IsoPresentation_createUserDataACSE(self->presentation, presentationBuffer, acseBuffer);
struct sBufferChain sSessionBuffer;
BufferChain sessionBuffer = &sSessionBuffer;
sessionBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE - presentationBuffer->length;
sessionBuffer->buffer = self->sendBuffer + presentationBuffer->length;
sessionBuffer->nextPart = presentationBuffer;
IsoSession_createFinishSpdu(NULL, sessionBuffer, presentationBuffer);
CotpConnection_sendDataMessage(self->cotpConnection, sessionBuffer);
Semaphore_post(self->transmitBufferMutex);
}
ByteBuffer*
IsoClientConnection_allocateTransmitBuffer(IsoClientConnection self)
{
Semaphore_wait(self->transmitBufferMutex);
self->transmitPayloadBuffer->size = 0;
self->transmitPayloadBuffer->maxSize = ISO_CLIENT_BUFFER_SIZE;
return self->transmitPayloadBuffer;
}
void
IsoClientConnection_releaseReceiveBuffer(IsoClientConnection self)
{
Semaphore_post(self->receiveBufferMutex);
}