/* * iso_client_connection.c * * Client side representation of the ISO stack (COTP, session, presentation, ACSE) * * Copyright 2013-2018 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 "hal_socket.h" #include "hal_thread.h" #include "cotp.h" #include "iso_session.h" #include "iso_presentation.h" #include "iso_client_connection.h" #include "tls_config.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_CONNECTED 1 #define STATE_ERROR 2 #define STATE_CONNECTING 3 #define TPKT_RFC1006_HEADER_SIZE 4 #define ISO_CLIENT_BUFFER_SIZE CONFIG_MMS_MAXIMUM_PDU_SIZE + 100 typedef enum { INT_STATE_IDLE, INT_STATE_TCP_CONNECTING, INT_STATE_WAIT_FOR_COTP_CONNECT_RESP, INT_STATE_WAIT_FOR_ACSE_RESP, INT_STATE_WAIT_FOR_DATA_MSG, INT_STATE_CLOSING_CONNECTION, INT_STATE_CLOSE_ON_ERROR, INT_STATE_ERROR } eIsoClientInternalState; struct sIsoClientConnection { IsoConnectionParameters parameters; IsoIndicationCallback callback; void* callbackParameter; volatile eIsoClientInternalState intState; volatile int state; Semaphore stateMutex; uint64_t nextReadTimeout; Socket socket; #if (CONFIG_MMS_SUPPORT_TLS == 1) TLSSocket tlsSocket; #endif 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 tickMutex; uint8_t* cotpReadBuf; uint8_t* cotpWriteBuf; ByteBuffer* cotpReadBuffer; ByteBuffer* cotpWriteBuffer; }; static void setState(IsoClientConnection self, int newState) { Semaphore_wait(self->stateMutex); self->state = newState; Semaphore_post(self->stateMutex); } static int getState(IsoClientConnection self) { int stateVal; Semaphore_wait(self->stateMutex); stateVal = self->state; Semaphore_post(self->stateMutex); return stateVal; } static inline void setIntState(IsoClientConnection self, eIsoClientInternalState newState) { self->intState = newState; } static inline eIsoClientInternalState getIntState(IsoClientConnection self) { return self->intState; } IsoClientConnection IsoClientConnection_create(IsoConnectionParameters parameters, IsoIndicationCallback callback, void* callbackParameter) { IsoClientConnection self = (IsoClientConnection) GLOBAL_CALLOC(1, sizeof(struct sIsoClientConnection)); if (self) { self->parameters = parameters; self->callback = callback; self->callbackParameter = callbackParameter; self->intState = INT_STATE_IDLE; self->state = STATE_IDLE; self->stateMutex = Semaphore_create(1); 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->tickMutex = 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; } static bool sendConnectionRequestMessage(IsoClientConnection self) { /* COTP (ISO transport) handshake */ CotpConnection_init(self->cotpConnection, self->socket, self->receiveBuffer, self->cotpReadBuffer, self->cotpWriteBuffer); #if (CONFIG_MMS_SUPPORT_TLS == 1) if (self->parameters->tlsConfiguration) { /* create TLSSocket and start TLS authentication */ TLSSocket tlsSocket = TLSSocket_create(self->socket, self->parameters->tlsConfiguration, false); if (tlsSocket) self->cotpConnection->tlsSocket = tlsSocket; else { if (DEBUG_ISO_CLIENT) printf("TLS handshake failed!\n"); return false; } } #endif /* (CONFIG_MMS_SUPPORT_TLS == 1) */ /* COTP (ISO transport) handshake */ CotpIndication cotpIndication = CotpConnection_sendConnectionRequestMessage(self->cotpConnection, self->parameters); if (cotpIndication != COTP_OK) return false; else return true; } static void sendAcseInitiateRequest(IsoClientConnection self) { /* Upper layers handshake */ struct sBufferChain sAcsePayload; BufferChain acsePayload = &sAcsePayload; acsePayload->buffer = self->transmitPayloadBuffer->buffer; acsePayload->partLength = self->transmitPayloadBuffer->size; acsePayload->length = self->transmitPayloadBuffer->size; acsePayload->nextPart = NULL; AcseConnection_init(&(self->acseConnection), NULL, NULL, NULL); AcseAuthenticationParameter authParameter = self->parameters->acseAuthParameter; struct sBufferChain sAcseBuffer; BufferChain acseBuffer = &sAcseBuffer; acseBuffer->buffer = self->sendBuffer + self->transmitPayloadBuffer->size; acseBuffer->partMaxLength = ISO_CLIENT_BUFFER_SIZE - acsePayload->length; AcseConnection_createAssociateRequestMessage(&(self->acseConnection), self->parameters, 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, self->parameters, presentationBuffer, acseBuffer); struct sBufferChain sSessionBuffer; BufferChain sessionBuffer = &sSessionBuffer; sessionBuffer->buffer = self->sendBuffer + presentationBuffer->length; IsoSession_init(self->session); IsoSession_createConnectSpdu(self->session, self->parameters, sessionBuffer, presentationBuffer); CotpConnection_sendDataMessage(self->cotpConnection, sessionBuffer); Semaphore_post(self->transmitBufferMutex); } static void releaseSocket(IsoClientConnection self) { if (self->socket) { #if (CONFIG_MMS_SUPPORT_TLS == 1) if (self->cotpConnection->tlsSocket) TLSSocket_close(self->cotpConnection->tlsSocket); #endif Socket_destroy(self->socket); self->socket = NULL; } } /* * Connection state machine * * called by tick function * * \return value indicates that connection is currently waiting and calling thread can be suspended */ bool IsoClientConnection_handleConnection(IsoClientConnection self) { Semaphore_wait(self->tickMutex); bool waits = false; eIsoClientInternalState currentState = getIntState(self); eIsoClientInternalState nextState = currentState; switch (currentState) { case INT_STATE_IDLE: case INT_STATE_ERROR: waits = true; break; case INT_STATE_TCP_CONNECTING: { SocketState socketState = Socket_checkAsyncConnectState(self->socket); if (socketState == SOCKET_STATE_CONNECTED) { if (sendConnectionRequestMessage(self)) { self->nextReadTimeout = Hal_getTimeInMs() + CONFIG_TCP_READ_TIMEOUT_MS; nextState = INT_STATE_WAIT_FOR_COTP_CONNECT_RESP; } else { IsoClientConnection_releaseTransmitBuffer(self); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_CLOSE_ON_ERROR; } } else if (socketState == SOCKET_STATE_FAILED) { IsoClientConnection_releaseTransmitBuffer(self); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_CLOSE_ON_ERROR; } else { waits = true; } } break; case INT_STATE_WAIT_FOR_COTP_CONNECT_RESP: { uint64_t currentTime = Hal_getTimeInMs(); if (currentTime > self->nextReadTimeout) { if (DEBUG_ISO_CLIENT) printf("Timeout waiting for COTP CR\n"); IsoClientConnection_releaseTransmitBuffer(self); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_CLOSE_ON_ERROR; } else { TpktState packetState = CotpConnection_readToTpktBuffer(self->cotpConnection); if (packetState == TPKT_PACKET_COMPLETE) { CotpIndication cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection); if (cotpIndication != COTP_CONNECT_INDICATION) { if (DEBUG_ISO_CLIENT) printf("Unexpected COTP state (%i)\n", cotpIndication); IsoClientConnection_releaseTransmitBuffer(self); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_CLOSE_ON_ERROR; } else { sendAcseInitiateRequest(self); self->nextReadTimeout = Hal_getTimeInMs() + CONFIG_TCP_READ_TIMEOUT_MS; nextState = INT_STATE_WAIT_FOR_ACSE_RESP; } } else if (packetState == TPKT_ERROR) { if (DEBUG_ISO_CLIENT) printf("Error receiving COTP message\n"); IsoClientConnection_releaseTransmitBuffer(self); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_CLOSE_ON_ERROR; } else { waits = true; } } } break; case INT_STATE_WAIT_FOR_ACSE_RESP: { uint64_t currentTime = Hal_getTimeInMs(); if (currentTime > self->nextReadTimeout) { if (DEBUG_ISO_CLIENT) printf("Timeout waiting for ACSE initiate response\n"); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_ERROR; } else { TpktState packetState = CotpConnection_readToTpktBuffer(self->cotpConnection); if (packetState == TPKT_PACKET_COMPLETE) { CotpIndication cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection); if (cotpIndication != COTP_DATA_INDICATION) { if (DEBUG_ISO_CLIENT) printf("Unexpected COTP state (%i)\n", cotpIndication); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_CLOSE_ON_ERROR; } else { /* parse ACSE response */ 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"); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_CLOSE_ON_ERROR; } else { if (IsoPresentation_parseAcceptMessage(self->presentation, IsoSession_getUserData(self->session)) == false) { if (DEBUG_ISO_CLIENT) printf("IsoClientConnection_associate: no presentation ok indication\n"); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_CLOSE_ON_ERROR; } else { 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"); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_CLOSE_ON_ERROR; } else { } } ByteBuffer_wrap(self->receivePayloadBuffer, self->acseConnection.userDataBuffer, self->acseConnection.userDataBufferSize, self->acseConnection.userDataBufferSize); setState(self, STATE_CONNECTED); nextState = INT_STATE_WAIT_FOR_DATA_MSG; self->callback(ISO_IND_ASSOCIATION_SUCCESS, self->callbackParameter, self->receivePayloadBuffer); CotpConnection_resetPayload(self->cotpConnection); } } } else if (packetState == TPKT_ERROR) { if (DEBUG_ISO_CLIENT) printf("Error receiving COTP message\n"); self->callback(ISO_IND_ASSOCIATION_FAILED, self->callbackParameter, NULL); nextState = INT_STATE_CLOSE_ON_ERROR; } else { waits = true; } } } break; case INT_STATE_WAIT_FOR_DATA_MSG: { TpktState packetState = CotpConnection_readToTpktBuffer(self->cotpConnection); if (packetState == TPKT_ERROR) { nextState = INT_STATE_CLOSE_ON_ERROR; } else if (packetState == TPKT_PACKET_COMPLETE) { CotpIndication cotpIndication = CotpConnection_parseIncomingMessage(self->cotpConnection); switch (cotpIndication) { case COTP_MORE_FRAGMENTS_FOLLOW: break; case COTP_DISCONNECT_INDICATION: { nextState = INT_STATE_CLOSING_CONNECTION; } break; case COTP_DATA_INDICATION: { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT_CONNECTION: parse message\n"); IsoSessionIndication 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"); nextState = INT_STATE_CLOSE_ON_ERROR; } else { if (!IsoPresentation_parseUserData(self->presentation, IsoSession_getUserData(self->session))) { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT_CONNECTION: Invalid presentation message\n"); nextState = INT_STATE_CLOSE_ON_ERROR; } else { self->callback(ISO_IND_DATA, self->callbackParameter, &(self->presentation->nextPayload)); CotpConnection_resetPayload(self->cotpConnection); } } } break; default: { nextState = INT_STATE_CLOSE_ON_ERROR; } break; } } else { waits = true; } } break; case INT_STATE_CLOSE_ON_ERROR: { setState(self, STATE_ERROR); self->callback(ISO_IND_CLOSED, self->callbackParameter, NULL);; releaseSocket(self); nextState = INT_STATE_ERROR; } break; case INT_STATE_CLOSING_CONNECTION: { setState(self, STATE_IDLE); self->callback(ISO_IND_CLOSED, self->callbackParameter, NULL);; releaseSocket(self); nextState = INT_STATE_IDLE; } break; } self->callback(ISO_IND_TICK, self->callbackParameter, NULL); setIntState(self, nextState); Semaphore_post(self->tickMutex); return waits; } bool IsoClientConnection_associateAsync(IsoClientConnection self, uint32_t connectTimeoutInMs) { bool success = true; /* Create socket and start connect */ setState(self, STATE_CONNECTING); Semaphore_wait(self->tickMutex); self->socket = TcpSocket_create(); Socket_setConnectTimeout(self->socket, connectTimeoutInMs); #if (CONFIG_ACTIVATE_TCP_KEEPALIVE == 1) Socket_activateTcpKeepAlive(self->socket, CONFIG_TCP_KEEPALIVE_IDLE, CONFIG_TCP_KEEPALIVE_INTERVAL, CONFIG_TCP_KEEPALIVE_CNT); #endif setIntState(self, INT_STATE_TCP_CONNECTING); if (Socket_connectAsync(self->socket, self->parameters->hostname, self->parameters->tcpPort) == false) { Socket_destroy(self->socket); self->socket = NULL; setIntState(self, INT_STATE_ERROR); setState(self, STATE_ERROR); IsoClientConnection_releaseTransmitBuffer(self); success = false; } Semaphore_post(self->tickMutex); return success; } void IsoClientConnection_sendMessage(IsoClientConnection self, ByteBuffer* payloadBuffer) { if (getState(self) == STATE_CONNECTED) { 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); if (indication != COTP_OK) if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: IsoClientConnection_sendMessage: send message failed!\n"); } else { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: Not connected --> cannot send message\n"); } /* release transmit buffer for use by API client */ Semaphore_post(self->transmitBufferMutex); } void IsoClientConnection_close(IsoClientConnection self) { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: IsoClientConnection_close\n"); Semaphore_wait(self->tickMutex); eIsoClientInternalState intState = getIntState(self); if ((intState != INT_STATE_IDLE) && (intState != INT_STATE_ERROR) && (intState != INT_STATE_CLOSE_ON_ERROR)) { setIntState(self, INT_STATE_CLOSING_CONNECTION); Semaphore_post(self->tickMutex); IsoClientConnection_handleConnection(self); setState(self, STATE_IDLE); } else { Semaphore_post(self->tickMutex); } } void IsoClientConnection_destroy(IsoClientConnection self) { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: IsoClientConnection_destroy\n"); int state = getState(self); if (state == STATE_CONNECTED) { if (DEBUG_ISO_CLIENT) printf("ISO_CLIENT: call IsoClientConnection_close\n"); IsoClientConnection_close(self); } 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->transmitBufferMutex); Semaphore_destroy(self->stateMutex); Semaphore_destroy(self->tickMutex); GLOBAL_FREEMEM(self->sendBuffer); GLOBAL_FREEMEM(self); } static void sendAbortMessage(IsoClientConnection self) { 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); } void IsoClientConnection_abortAsync(IsoClientConnection self) { sendAbortMessage(self); } 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_releaseTransmitBuffer(IsoClientConnection self) { Semaphore_post(self->transmitBufferMutex); }