/******************************************************************************* * Copyright (c) 2009, Rockwell Automation, Inc. * All rights reserved. * ******************************************************************************/ #include #include #include "cipconnectionmanager.h" #include "opener_user_conf.h" #include "cipcommon.h" #include "cipmessagerouter.h" #include "ciperror.h" #include "endianconv.h" #include "opener_api.h" #include "encap.h" #include "cipidentity.h" #include "trace.h" #include "cipconnectionobject.h" #include "cipclass3connection.h" #include "cipioconnection.h" #include "cipassembly.h" #include "cpf.h" #include "appcontype.h" #include "generic_networkhandler.h" #include "cipepath.h" #include "cipelectronickey.h" #include "cipqos.h" #include "xorshiftrandom.h" const size_t g_kForwardOpenHeaderLength = 36; /**< the length in bytes of the forward open command specific data till the start of the connection path (including con path size)*/ const size_t g_kLargeForwardOpenHeaderLength = 40; /**< the length in bytes of the large forward open command specific data till the start of the connection path (including con path size)*/ static const unsigned int g_kNumberOfConnectableObjects = 2 + OPENER_CIP_NUM_APPLICATION_SPECIFIC_CONNECTABLE_OBJECTS; typedef struct { EipUint32 class_id; OpenConnectionFunction open_connection_function; } ConnectionManagementHandling; /* global variables private */ /** List holding information on the object classes and open/close function * pointers to which connections may be established. */ ConnectionManagementHandling g_connection_management_list[2 + OPENER_CIP_NUM_APPLICATION_SPECIFIC_CONNECTABLE_OBJECTS ] = {{0}}; /** buffer connection object needed for forward open */ CipConnectionObject g_dummy_connection_object; /** @brief Holds the connection ID's "incarnation ID" in the upper 16 bits */ EipUint32 g_incarnation_id; /* private functions */ EipStatus ForwardOpen(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipSessionHandle encapsulation_session); EipStatus LargeForwardOpen(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipSessionHandle encapsulation_session); EipStatus ForwardClose(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipSessionHandle encapsulation_session); EipStatus GetConnectionOwner(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipSessionHandle encapsulation_session); EipStatus GetConnectionData(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipUdint encapsulation_session); EipStatus SearchConnectionData(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipUdint encapsulation_session); void AssembleConnectionDataResponseMessage( CipMessageRouterResponse *message_router_response, CipConnectionObject *connection_object); EipStatus AssembleForwardOpenResponse(CipConnectionObject *connection_object, CipMessageRouterResponse *message_router_response, EipUint8 general_status, EipUint16 extended_status); EipStatus AssembleForwardCloseResponse(EipUint16 connection_serial_number, EipUint16 originatior_vendor_id, EipUint32 originator_serial_number, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, EipUint16 extended_error_code); /** @brief check if the data given in the connection object match with an already established connection * * The comparison is done according to the definitions in the CIP specification Section 3-5.5.2: * The following elements have to be equal: Vendor ID, Connection Serial Number, Originator Serial Number * @param connection_object connection object containing the comparison elements from the forward open request * @return * - NULL if no equal established connection exists * - pointer to the equal connection object */ CipConnectionObject *CheckForExistingConnection( const CipConnectionObject *const connection_object); /** @brief Compare the electronic key received with a forward open request with the device's data. * * @param key_format format identifier given in the forward open request * @param key_data pointer to the electronic key data received in the forward open request * @param extended_status the extended error code in case an error happened * @return general status on the establishment * - EIP_OK ... on success * - On an error the general status code to be put into the response */ EipStatus CheckElectronicKeyData(EipUint8 key_format, void *key_data, EipUint16 *extended_status); /** @brief Parse the connection path of a forward open request * * This function will take the connection object and the received data stream and parse the connection path. * @param connection_object pointer to the connection object structure for which the connection should * be established * @param message_router_request pointer to the received request structure. The position of the data stream pointer has to be at the connection length entry * @param extended_error the extended error code in case an error happened * @return general status on the establishment * - kEipStatusOk ... on success * - On an error the general status code to be put into the response */ EipUint8 ParseConnectionPath(CipConnectionObject *connection_object, CipMessageRouterRequest *message_router_request, EipUint16 *extended_error); ConnectionManagementHandling *GetConnectionManagementEntry( const EipUint32 class_id); void InitializeConnectionManagerData(void); void AddNullAddressItem( CipCommonPacketFormatData *common_data_packet_format_data); /** @brief gets the padded logical path TODO: enhance documentation * @param logical_path_segment TheLogical Path Segment * * @return The padded logical path */ unsigned int GetPaddedLogicalPath(const EipUint8 **logical_path_segment) { unsigned int padded_logical_path = *(*logical_path_segment)++; if( (padded_logical_path & 3) == 0 ) { padded_logical_path = *(*logical_path_segment)++; } else if( (padded_logical_path & 3) == 1 ) { (*logical_path_segment)++; /* skip pad */ padded_logical_path = *(*logical_path_segment)++; padded_logical_path |= *(*logical_path_segment)++ << 8; } else { OPENER_TRACE_ERR("illegal logical path segment\n"); } return padded_logical_path; } /** @brief Generate a new connection Id utilizing the Incarnation Id as * described in the EIP specs. * * A unique connectionID is formed from the boot-time-specified "incarnation ID" * and the per-new-connection connection number. The legacy default is to use * the lower 16-bit as a connection counter, incrementing for each connection. * * Some conformance tests may however fail an adapter due to the connection ID * not being random enough. To meet such requirements there is an option to * enable fully random connection IDs -- although the upper 16-bits are always * derived from the incarnation ID -- i.e., each time OpENer is started the * upper 16-bits will remain be the same. * * @return new 32-bit connection id */ CipUdint GetConnectionId(void) { #ifndef OPENER_RANDOMIZE_CONNECTION_ID static CipUint connection_id = 18; connection_id++; #else CipUint connection_id = NextXorShiftUint32(); #endif return (g_incarnation_id | (connection_id & 0x0000FFFF) ); } void InitializeConnectionManager(CipClass *class) { CipClass *meta_class = class->class_instance.cip_class; InsertAttribute( (CipInstance *) class, 1, kCipUint, EncodeCipUint, NULL, (void *) &class->revision, kGetableSingleAndAll ); /* revision */ InsertAttribute( (CipInstance *) class, 2, kCipUint, EncodeCipUint, NULL, (void *) &class->number_of_instances, kGetableSingleAndAll ); /* largest instance number */ InsertAttribute( (CipInstance *) class, 3, kCipUint, EncodeCipUint, NULL, (void *) &class->number_of_instances, kGetableSingle ); /* number of instances currently existing*/ InsertAttribute( (CipInstance *) class, 4, kCipUint, EncodeCipUint, NULL, (void *) &kCipUintZero, kNotSetOrGetable ); /* optional attribute list - default = 0 */ InsertAttribute( (CipInstance *) class, 5, kCipUint, EncodeCipUint, NULL, (void *) &kCipUintZero, kNotSetOrGetable ); /* optional service list - default = 0 */ InsertAttribute( (CipInstance *) class, 6, kCipUint, EncodeCipUint, NULL, (void *) &meta_class->highest_attribute_number, kGetableSingleAndAll ); /* max class attribute number*/ InsertAttribute( (CipInstance *) class, 7, kCipUint, EncodeCipUint, NULL, (void *) &class->highest_attribute_number, kGetableSingleAndAll ); /* max instance attribute number*/ InsertService(meta_class, kGetAttributeAll, &GetAttributeAll, "GetAttributeAll"); /* bind instance services to the metaclass*/ InsertService(meta_class, kGetAttributeSingle, &GetAttributeSingle, "GetAttributeSingle"); } EipStatus ConnectionManagerInit(EipUint16 unique_connection_id) { InitializeConnectionManagerData(); CipClass *connection_manager = CreateCipClass(kCipConnectionManagerClassCode, /* class code */ 0, /* # of class attributes */ 7, /* # highest class attribute number*/ 2, /* # of class services */ 0, /* # of instance attributes */ 14, /* # highest instance attribute number*/ 8, /* # of instance services */ 1, /* # of instances */ "connection manager", /* class name */ 1, /* revision */ &InitializeConnectionManager); /* # function pointer for initialization*/ if(connection_manager == NULL) { return kEipStatusError; } InsertService(connection_manager, kGetAttributeSingle, &GetAttributeSingle, "GetAttributeSingle"); InsertService(connection_manager, kGetAttributeAll, &GetAttributeAll, "GetAttributeAll"); InsertService(connection_manager, kForwardOpen, &ForwardOpen, "ForwardOpen"); InsertService(connection_manager, kLargeForwardOpen, &LargeForwardOpen, "LargeForwardOpen"); InsertService(connection_manager, kForwardClose, &ForwardClose, "ForwardClose"); InsertService(connection_manager, kGetConnectionOwner, &GetConnectionOwner, "GetConnectionOwner"); InsertService(connection_manager, kGetConnectionData, &GetConnectionData, "GetConnectionData"); InsertService(connection_manager, kSearchConnectionData, &SearchConnectionData, "SearchConnectionData"); g_incarnation_id = ( (EipUint32) unique_connection_id ) << 16; AddConnectableObject(kCipMessageRouterClassCode, EstablishClass3Connection); AddConnectableObject(kCipAssemblyClassCode, EstablishIoConnection); return kEipStatusOk; } EipStatus HandleReceivedConnectedData(const EipUint8 *const data, int data_length, struct sockaddr_in *from_address) { if( (CreateCommonPacketFormatStructure(data, data_length, &g_common_packet_format_data_item) ) == kEipStatusError ) { return kEipStatusError; } else { /* check if connected address item or sequenced address item received, otherwise it is no connected message and should not be here */ if( (g_common_packet_format_data_item.address_item.type_id == kCipItemIdConnectionAddress) || (g_common_packet_format_data_item.address_item.type_id == kCipItemIdSequencedAddressItem) ) { /* found connected address item or found sequenced address item -> for now the sequence number will be ignored */ if(g_common_packet_format_data_item.data_item.type_id == kCipItemIdConnectedDataItem) { /* connected data item received */ CipConnectionObject *connection_object = GetConnectedObject( g_common_packet_format_data_item.address_item.data.connection_identifier); if(connection_object == NULL) { return kEipStatusError; } /* only handle the data if it is coming from the originator */ if(connection_object->originator_address.sin_addr.s_addr == from_address->sin_addr.s_addr) { ConnectionObjectResetLastPackageInactivityTimerValue(connection_object); if(SEQ_GT32(g_common_packet_format_data_item.address_item.data. sequence_number, connection_object->eip_level_sequence_count_consuming) || !connection_object->eip_first_level_sequence_count_received) { /* reset the watchdog timer */ ConnectionObjectResetInactivityWatchdogTimerValue(connection_object); /* only inform assembly object if the sequence counter is greater or equal */ connection_object->eip_level_sequence_count_consuming = g_common_packet_format_data_item.address_item.data.sequence_number; connection_object->eip_first_level_sequence_count_received = true; if(NULL != connection_object->connection_receive_data_function) { return connection_object->connection_receive_data_function( connection_object, g_common_packet_format_data_item.data_item.data, g_common_packet_format_data_item.data_item.length); } } } else { OPENER_TRACE_WARN( "Connected Message Data Received with wrong address information\n"); } } } } return kEipStatusOk; } /** @brief Function prototype for all Forward Open handle functions * */ typedef EipStatus (*HandleForwardOpenRequestFunction)(CipConnectionObject * connection_object, CipInstance *instance, CipMessageRouterRequest * message_router_request, CipMessageRouterResponse * message_router_response); /** @brief Handles a Null Non Matching Forward Open Request * * Null, Non-Matching - Either ping device, or configure a device’s application, * or return General Status kCipErrorConnectionFailure and * Extended Status kConnectionManagerExtendedStatusCodeNullForwardOpenNotSupported */ EipStatus HandleNullNonMatchingForwardOpenRequest( CipConnectionObject *connection_object, CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response); EipStatus HandleNullNonMatchingForwardOpenRequest( CipConnectionObject *connection_object, CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response) { /* Suppress unused parameter compiler warning. */ (void) instance; (void) message_router_request; (void) message_router_response; OPENER_TRACE_INFO("Right now we cannot handle Null requests\n"); return AssembleForwardOpenResponse(connection_object, message_router_response, kCipErrorConnectionFailure, kConnectionManagerExtendedStatusCodeNullForwardOpenNotSupported); } /** @brief Handles a Null Matching Forward Open request * * Either reconfigure a target device’s application, or * return General Status kCipErrorConnectionFailure and * Extended Status kConnectionManagerExtendedStatusCodeNullForwardOpenNotSupported */ EipStatus HandleNullMatchingForwardOpenRequest( CipConnectionObject *connection_object, CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response); EipStatus HandleNullMatchingForwardOpenRequest( CipConnectionObject *connection_object, CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response) { /* Suppress unused parameter compiler warning. */ (void) instance; (void) message_router_request; OPENER_TRACE_INFO("Right now we cannot handle Null requests\n"); return AssembleForwardOpenResponse(connection_object, message_router_response, kCipErrorConnectionFailure, kConnectionManagerExtendedStatusCodeNullForwardOpenNotSupported); } /** @brief Handles a Non Null Matching Forward Open Request * * Non-Null, Matching request - Return General Status = kCipErrorConnectionFailure, * Extended Status = kConnectionManagerExtendedStatusCodeErrorConnectionInUseOrDuplicateForwardOpen */ EipStatus HandleNonNullMatchingForwardOpenRequest( CipConnectionObject *connection_object, CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response); EipStatus HandleNonNullMatchingForwardOpenRequest( CipConnectionObject *connection_object, CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response) { /* Suppress unused parameter compiler warning. */ (void) instance; (void) message_router_request; OPENER_TRACE_INFO("Right now we cannot handle reconfiguration requests\n"); return AssembleForwardOpenResponse(connection_object, message_router_response, kCipErrorConnectionFailure, kConnectionManagerExtendedStatusCodeErrorConnectionInUseOrDuplicateForwardOpen); } /** @brief Handles a Non Null Non Matching Forward Open Request * * Non-Null, Non-Matching request - Establish a new connection */ EipStatus HandleNonNullNonMatchingForwardOpenRequest( CipConnectionObject *connection_object, CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response); EipStatus HandleNonNullNonMatchingForwardOpenRequest( CipConnectionObject *connection_object, CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response) { /* Suppress unused parameter compiler warning. */ (void) connection_object; (void) instance; EipUint16 connection_status = kConnectionManagerExtendedStatusCodeSuccess; /*check if the trigger type value is invalid or ok */ if(kConnectionObjectTransportClassTriggerProductionTriggerInvalid == ConnectionObjectGetTransportClassTriggerProductionTrigger(& g_dummy_connection_object) ) { return AssembleForwardOpenResponse(&g_dummy_connection_object, message_router_response, kCipErrorConnectionFailure, kConnectionManagerExtendedStatusCodeErrorTransportClassAndTriggerCombinationNotSupported); } EipUint32 temp = ParseConnectionPath(&g_dummy_connection_object, message_router_request, &connection_status); if(kEipStatusOk != temp) { return AssembleForwardOpenResponse(&g_dummy_connection_object, message_router_response, temp, connection_status); } /*parsing is now finished all data is available and check now establish the connection */ ConnectionManagementHandling *connection_management_entry = GetConnectionManagementEntry( /* Gets correct open connection function for the targeted object */ g_dummy_connection_object.configuration_path.class_id); if(NULL != connection_management_entry) { if (NULL != connection_management_entry->open_connection_function) { temp = connection_management_entry->open_connection_function( &g_dummy_connection_object, &connection_status); } else { connection_status = kConnectionManagerExtendedStatusCodeMiscellaneous; } } else { temp = kEipStatusError; connection_status = kConnectionManagerExtendedStatusCodeInconsistentApplicationPathCombo; } if(kEipStatusOk != temp) { OPENER_TRACE_INFO("connection manager: connect failed\n"); /* in case of error the dummy objects holds all necessary information */ return AssembleForwardOpenResponse(&g_dummy_connection_object, message_router_response, temp, connection_status); } else { OPENER_TRACE_INFO("connection manager: connect succeeded\n"); /* in case of success the new connection is added at the head of the connection list */ return AssembleForwardOpenResponse(connection_list.first->data, message_router_response, kCipErrorSuccess, 0); } } /** @brief Array of Forward Open handle function pointers * * File scope variable * The first dimension handles if the request was a non-null request (0) or a null request (1), * the second dimension handles if the request was a non-matchin (0) or matching request (1) */ static const HandleForwardOpenRequestFunction handle_forward_open_request_functions[2][2] = { { HandleNonNullNonMatchingForwardOpenRequest, HandleNonNullMatchingForwardOpenRequest }, { HandleNullNonMatchingForwardOpenRequest, HandleNullMatchingForwardOpenRequest } }; EipStatus ForwardOpenRoutine(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipSessionHandle encapsulation_session); /** @brief Check if resources for new connection available, generate ForwardOpen Reply message. * * Large Forward Open service calls Forward Open service */ EipStatus LargeForwardOpen(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipSessionHandle encapsulation_session) { g_dummy_connection_object.is_large_forward_open = true; return ForwardOpenRoutine(instance, message_router_request, message_router_response, originator_address, encapsulation_session); } /** @brief Check if resources for new connection available, generate ForwardOpen Reply message. * * Forward Open four cases * Non-Null/Not matching - open a connection * Non-Null/Matching - error * Null/Not matching - ping a device/configure * Null/Matching - reconfigure * * Null connection - both O->T and T->O connection parameter field are null * Non-Null connection - one or both O->T and T->O connection parameter field are not null * Matching - Connection Triad matches an existing connection * (Connection Serial Number, Originator Vendor ID and Originator Serial Number) * * @param instance pointer to CIP object instance * @param message_router_request pointer to Message Router Request. * @param message_router_response pointer to Message Router Response. * @param originator_address address struct of the originator as received * @param encapsulation_session associated encapsulation session of the explicit message * @return >0 .. success, 0 .. no reply to send back * -1 .. error */ EipStatus ForwardOpen(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipSessionHandle encapsulation_session) { g_dummy_connection_object.is_large_forward_open = false; return ForwardOpenRoutine(instance, message_router_request, message_router_response, originator_address, encapsulation_session); } EipStatus ForwardOpenRoutine(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipSessionHandle encapsulation_session) { (void) instance; /*suppress compiler warning */ bool is_null_request = false; /* 1 = Null Request, 0 = Non-Null Request */ bool is_matching_request = false; /* 1 = Matching Request, 0 = Non-Matching Request */ /*first check if we have already a connection with the given params */ ConnectionObjectInitializeFromMessage(&(message_router_request->data), &g_dummy_connection_object); g_dummy_connection_object.associated_encapsulation_session = encapsulation_session; memcpy(&(g_dummy_connection_object.originator_address), originator_address, sizeof(g_dummy_connection_object.originator_address) ); ConnectionObjectConnectionType o_to_t_connection_type = ConnectionObjectGetOToTConnectionType(&g_dummy_connection_object); ConnectionObjectConnectionType t_to_o_connection_type = ConnectionObjectGetTToOConnectionType(&g_dummy_connection_object); /* Check if both connection types are valid, otherwise send error response */ if(kConnectionObjectConnectionTypeInvalid == o_to_t_connection_type) { return AssembleForwardOpenResponse(&g_dummy_connection_object, message_router_response, kCipErrorConnectionFailure, kConnectionManagerExtendedStatusCodeErrorInvalidOToTConnectionType); } if(kConnectionObjectConnectionTypeInvalid == t_to_o_connection_type) { return AssembleForwardOpenResponse(&g_dummy_connection_object, message_router_response, kCipErrorConnectionFailure, kConnectionManagerExtendedStatusCodeErrorInvalidTToOConnectionType); } if(kConnectionObjectConnectionTypeMulticast == t_to_o_connection_type) { /* for multicast, check if IP is within configured net because we send TTL 1 */ CipUdint originator_ip = ( (struct sockaddr_in *) originator_address )->sin_addr.s_addr; CipUdint interface_ip = g_network_status.ip_address; CipUdint interface_mask = g_network_status.network_mask; if( (originator_ip & interface_mask) != (interface_ip & interface_mask) ) { return AssembleForwardOpenResponse(&g_dummy_connection_object, message_router_response, kCipErrorConnectionFailure, kConnectionManagerExtendedStatusCodeNotConfiguredForOffSubnetMulticast); } } /* Check if request is a Null request or a Non-Null request */ if(kConnectionObjectConnectionTypeNull == o_to_t_connection_type && kConnectionObjectConnectionTypeNull == t_to_o_connection_type) { is_null_request = true; OPENER_TRACE_INFO("We have a Null request\n"); } else { is_null_request = false; OPENER_TRACE_INFO("We have a Non-Null request\n"); } /* Check if we have a matching or non matching request */ if(NULL != CheckForExistingConnection(&g_dummy_connection_object) ) { OPENER_TRACE_INFO("We have a Matching request\n"); is_matching_request = true; } else { OPENER_TRACE_INFO("We have a Non-Matching request\n"); is_matching_request = false; } HandleForwardOpenRequestFunction choosen_function = handle_forward_open_request_functions[is_null_request][is_matching_request]; return choosen_function(&g_dummy_connection_object, instance, message_router_request, message_router_response); } EipStatus ForwardClose(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipSessionHandle encapsulation_session) { /*Suppress compiler warning*/ (void) instance; (void) encapsulation_session; /* check connection_serial_number && originator_vendor_id && originator_serial_number if connection is established */ ConnectionManagerExtendedStatusCode connection_status = kConnectionManagerExtendedStatusCodeErrorConnectionTargetConnectionNotFound; /* set AddressInfo Items to invalid TypeID to prevent assembleLinearMsg to read them */ g_common_packet_format_data_item.address_info_item[0].type_id = 0; g_common_packet_format_data_item.address_info_item[1].type_id = 0; message_router_request->data += 2; /* ignore Priority/Time_tick and Time-out_ticks */ EipUint16 connection_serial_number = GetUintFromMessage( &message_router_request->data); EipUint16 originator_vendor_id = GetUintFromMessage( &message_router_request->data); EipUint32 originator_serial_number = GetUdintFromMessage( &message_router_request->data); OPENER_TRACE_INFO("ForwardClose: ConnSerNo %d\n", connection_serial_number); DoublyLinkedListNode *node = connection_list.first; while(NULL != node) { /* this check should not be necessary as only established connections should be in the active connection list */ CipConnectionObject *connection_object = node->data; if( (kConnectionObjectStateEstablished == ConnectionObjectGetState(connection_object) ) || (kConnectionObjectStateTimedOut == ConnectionObjectGetState(connection_object) ) ) { if( (connection_object->connection_serial_number == connection_serial_number) && (connection_object->originator_vendor_id == originator_vendor_id) && (connection_object->originator_serial_number == originator_serial_number) ) { /* found the corresponding connection object -> close it */ OPENER_ASSERT(NULL != connection_object->connection_close_function); if( ( (struct sockaddr_in *) originator_address )->sin_addr.s_addr == connection_object->originator_address.sin_addr.s_addr ) { connection_object->connection_close_function(connection_object); connection_status = kConnectionManagerExtendedStatusCodeSuccess; } else { connection_status = kConnectionManagerExtendedStatusWrongCloser; } break; } } node = node->next; } if(kConnectionManagerExtendedStatusCodeErrorConnectionTargetConnectionNotFound == connection_status) { OPENER_TRACE_INFO( "Connection not found! Requested connection tried: %u, %u, %i\n", connection_serial_number, originator_vendor_id, originator_serial_number); } return AssembleForwardCloseResponse(connection_serial_number, originator_vendor_id, originator_serial_number, message_router_request, message_router_response, connection_status); } /* TODO: Not implemented */ EipStatus GetConnectionOwner(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipSessionHandle encapsulation_session) { /* suppress compiler warnings */ (void) instance; (void) message_router_request; (void) message_router_response; (void) originator_address; (void) encapsulation_session; return kEipStatusOk; } EipStatus GetConnectionData(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipUdint encapsulation_session) { /* Suppress unused parameter compiler warning. */ (void)instance; (void)originator_address; (void)encapsulation_session; CIPServiceCode service_code = kGetConnectionData; message_router_response->reply_service = (0x80 | service_code); //get Connection Number from request EipUint16 Connection_number = GetUintFromMessage(&message_router_request->data); OPENER_TRACE_INFO("GetConnectionData for Connection_number: %d\n", Connection_number); //search connection DoublyLinkedListNode *iterator = connection_list.first; CipConnectionObject *search_connection_object = NULL; CipConnectionObject *connection_object = NULL; while(NULL != iterator) { search_connection_object = iterator->data; if( (search_connection_object->connection_number == Connection_number) ) { connection_object = search_connection_object; break; } iterator = iterator->next; } if(NULL != connection_object) { /* assemble response message */ AssembleConnectionDataResponseMessage(message_router_response, connection_object); message_router_response->general_status = kEipStatusOk; OPENER_TRACE_INFO("Connection found!\n"); } else { message_router_response->general_status = kCipErrorPathDestinationUnknown; OPENER_TRACE_INFO("Connection not found!\n"); } return kEipStatusOk; } EipStatus SearchConnectionData(CipInstance *instance, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, const struct sockaddr *originator_address, const CipUdint encapsulation_session) { /* Suppress unused parameter compiler warning. */ (void)instance; (void)originator_address; (void)encapsulation_session; CIPServiceCode service_code = kSearchConnectionData; message_router_response->reply_service = (0x80 | service_code); //connection data (connection triad) from request EipUint16 Connection_serial_number = GetUintFromMessage( &message_router_request->data); EipUint16 Originator_vendor_id = GetUintFromMessage( &message_router_request->data); EipUint32 Originator_serial_number = GetUdintFromMessage( &message_router_request->data); OPENER_TRACE_INFO( "SearchConnectionData for ConnSerNo: %d, OrigVendId: %d, OrigSerNo: %i,\n", Connection_serial_number, Originator_vendor_id, Originator_serial_number); //search connection DoublyLinkedListNode *iterator = connection_list.first; CipConnectionObject *search_connection_object = NULL; CipConnectionObject *connection_object = NULL; while(NULL != iterator) { search_connection_object = iterator->data; if( (search_connection_object->connection_serial_number == Connection_serial_number) && (search_connection_object->originator_vendor_id == Originator_vendor_id) && (search_connection_object->originator_serial_number == Originator_serial_number) ) { connection_object = search_connection_object; break; } iterator = iterator->next; } if(NULL != connection_object) { /* assemble response message */ AssembleConnectionDataResponseMessage(message_router_response, connection_object); message_router_response->general_status = kEipStatusOk; OPENER_TRACE_INFO("Connection found!\n"); } else { message_router_response->general_status = kCipErrorPathDestinationUnknown; OPENER_TRACE_INFO("Connection not found!\n"); } return kEipStatusOk; } void AssembleConnectionDataResponseMessage( CipMessageRouterResponse *message_router_response, CipConnectionObject *connection_object) { // Connection number UINT AddIntToMessage(connection_object->connection_number, &message_router_response->message); // Connection state UINT AddIntToMessage(connection_object->state, &message_router_response->message); // Originator Port UINT AddIntToMessage(connection_object->originator_address.sin_port, &message_router_response->message); // Target Port UINT AddIntToMessage(connection_object->remote_address.sin_port, &message_router_response->message); // Connection Serial Number UINT AddIntToMessage(connection_object->connection_serial_number, &message_router_response->message); // Originator Vendor ID UINT AddIntToMessage(connection_object->originator_vendor_id, &message_router_response->message); // Originator Serial number UDINT AddDintToMessage(connection_object->originator_serial_number, &message_router_response->message); // Originator O->T CID UDINT AddDintToMessage(connection_object->cip_consumed_connection_id, &message_router_response->message); // Target O->T CID UDINT AddDintToMessage(connection_object->cip_consumed_connection_id, &message_router_response->message); // Connection Timeout Multiplier USINT AddSintToMessage(connection_object->connection_timeout_multiplier, &message_router_response->message); // Reserved USINT AddSintToMessage(0, &message_router_response->message); // Reserved USINT AddSintToMessage(0, &message_router_response->message); // Reserved USINT AddSintToMessage(0, &message_router_response->message); // Originator RPI O->T UDINT AddDintToMessage(connection_object->o_to_t_requested_packet_interval, &message_router_response->message); // Originator API O->T UDINT AddDintToMessage(connection_object->transmission_trigger_timer, &message_router_response->message); // Originator T->O CID UDINT AddDintToMessage(connection_object->cip_produced_connection_id, &message_router_response->message); // Target T->O CID UDINT AddDintToMessage(connection_object->cip_produced_connection_id, &message_router_response->message); // Connection Timeout Multiplier USINT AddSintToMessage(connection_object->connection_timeout_multiplier, &message_router_response->message); // Reserved USINT AddSintToMessage(0, &message_router_response->message); // Reserved USINT AddSintToMessage(0, &message_router_response->message); // Reserved USINT AddSintToMessage(0, &message_router_response->message); // Originator RPI T->O UDINT AddDintToMessage(connection_object->t_to_o_requested_packet_interval, &message_router_response->message); // Originator API T->O UDINT AddDintToMessage(connection_object->transmission_trigger_timer, &message_router_response->message); } EipStatus ManageConnections(MilliSeconds elapsed_time) { //OPENER_TRACE_INFO("Entering ManageConnections\n"); /*Inform application that it can execute */ HandleApplication(); ManageEncapsulationMessages(elapsed_time); DoublyLinkedListNode *node = connection_list.first; while(NULL != node) { //OPENER_TRACE_INFO("Entering Connection Object loop\n"); CipConnectionObject *connection_object = node->data; if(kConnectionObjectStateEstablished == ConnectionObjectGetState(connection_object) ) { if( (NULL != connection_object->consuming_instance) || /* we have a consuming connection check inactivity watchdog timer */ (kConnectionObjectTransportClassTriggerDirectionServer == ConnectionObjectGetTransportClassTriggerDirection(connection_object) ) ) /* all server connections have to maintain an inactivity watchdog timer */ { if(elapsed_time >= connection_object->inactivity_watchdog_timer) { /* we have a timed out connection perform watchdog time out action*/ OPENER_TRACE_INFO(">>>>>>>>>>Connection ConnNr: %u timed out\n", connection_object->connection_serial_number); OPENER_ASSERT(NULL != connection_object->connection_timeout_function); connection_object->connection_timeout_function(connection_object); } else { connection_object->inactivity_watchdog_timer -= elapsed_time; connection_object->last_package_watchdog_timer -= elapsed_time; } } /* only if the connection has not timed out check if data is to be send */ if(kConnectionObjectStateEstablished == ConnectionObjectGetState(connection_object) ) { /* client connection */ if( (0 != ConnectionObjectGetExpectedPacketRate(connection_object) ) && (kEipInvalidSocket != connection_object->socket[kUdpCommuncationDirectionProducing]) ) /* only produce for the master connection */ { if(kConnectionObjectTransportClassTriggerProductionTriggerCyclic != ConnectionObjectGetTransportClassTriggerProductionTrigger( connection_object) ) { /* non cyclic connections have to decrement production inhibit timer */ if(elapsed_time <= connection_object->production_inhibit_timer) { //The connection is allowed to send again } else { connection_object->production_inhibit_timer -= elapsed_time; } } if(connection_object->transmission_trigger_timer <= elapsed_time) { /* need to send package */ OPENER_ASSERT( NULL != connection_object->connection_send_data_function); EipStatus eip_status = connection_object->connection_send_data_function(connection_object); if(eip_status == kEipStatusError) { OPENER_TRACE_ERR( "sending of UDP data in manage Connection failed\n"); } /* add the RPI to the timer value */ connection_object->transmission_trigger_timer += ConnectionObjectGetRequestedPacketInterval(connection_object); /* decrecment the elapsed time from timer value, if less than timer value */ if (connection_object->transmission_trigger_timer > elapsed_time) { connection_object->transmission_trigger_timer -= elapsed_time; } else { /* elapsed time was longer than RPI */ connection_object->transmission_trigger_timer = 0; OPENER_TRACE_INFO("elapsed time: %lu ms was longer than RPI: %u ms\n", elapsed_time, ConnectionObjectGetRequestedPacketInterval(connection_object)); } if(kConnectionObjectTransportClassTriggerProductionTriggerCyclic != ConnectionObjectGetTransportClassTriggerProductionTrigger( connection_object) ) { /* non cyclic connections have to reload the production inhibit timer */ ConnectionObjectResetProductionInhibitTimer(connection_object); } } else { connection_object->transmission_trigger_timer -= elapsed_time; } } } } node = node->next; } return kEipStatusOk; } /** @brief Assembles the Forward Open Response * * @param connection_object pointer to connection Object * @param message_router_response pointer to message router response * @param general_status the general status of the response * @param extended_status extended status in the case of an error otherwise 0 * @return status * kEipStatusOk .. no reply need to be sent back * kEipStatusOkSend .. need to send reply * kEipStatusError .. error */ EipStatus AssembleForwardOpenResponse(CipConnectionObject *connection_object, CipMessageRouterResponse *message_router_response, EipUint8 general_status, EipUint16 extended_status) { /* write reply information in CPF struct dependent of pa_status */ CipCommonPacketFormatData *cip_common_packet_format_data = &g_common_packet_format_data_item; cip_common_packet_format_data->item_count = 2; cip_common_packet_format_data->data_item.type_id = kCipItemIdUnconnectedDataItem; AddNullAddressItem(cip_common_packet_format_data); CIPServiceCode service_code = kForwardOpen; if(connection_object->is_large_forward_open) { service_code = kLargeForwardOpen; } message_router_response->reply_service = (0x80 | service_code); message_router_response->general_status = general_status; if(kCipErrorSuccess == general_status) { OPENER_TRACE_INFO("assembleFWDOpenResponse: sending success response\n"); /* if there is no application specific data, total length should be 26 */ message_router_response->size_of_additional_status = 0; if(cip_common_packet_format_data->address_info_item[0].type_id != 0) { cip_common_packet_format_data->item_count = 3; if(cip_common_packet_format_data->address_info_item[1].type_id != 0) { cip_common_packet_format_data->item_count = 4; /* there are two sockaddrinfo items to add */ } } AddDintToMessage(connection_object->cip_consumed_connection_id, &message_router_response->message); AddDintToMessage(connection_object->cip_produced_connection_id, &message_router_response->message); } else { /* we have an connection creation error */ OPENER_TRACE_WARN("AssembleForwardOpenResponse: sending error response, general/extended status=%d/%d\n", general_status, extended_status); ConnectionObjectSetState(connection_object, kConnectionObjectStateNonExistent); /* Expected data length is 10 octets */ switch(general_status) { case kCipErrorNotEnoughData: case kCipErrorTooMuchData: { message_router_response->size_of_additional_status = 0; break; } default: { switch(extended_status) { case kConnectionManagerExtendedStatusCodeErrorInvalidOToTConnectionSize: { message_router_response->size_of_additional_status = 2; message_router_response->additional_status[0] = extended_status; message_router_response->additional_status[1] = connection_object->correct_originator_to_target_size; break; } case kConnectionManagerExtendedStatusCodeErrorInvalidTToOConnectionSize: { message_router_response->size_of_additional_status = 2; message_router_response->additional_status[0] = extended_status; message_router_response->additional_status[1] = connection_object->correct_target_to_originator_size; break; } default: { message_router_response->size_of_additional_status = 1; message_router_response->additional_status[0] = extended_status; break; } } break; } } } AddIntToMessage(connection_object->connection_serial_number, &message_router_response->message); AddIntToMessage(connection_object->originator_vendor_id, &message_router_response->message); AddDintToMessage(connection_object->originator_serial_number, &message_router_response->message); if(kCipErrorSuccess == general_status) { /* set the actual packet rate to requested packet rate */ AddDintToMessage(connection_object->o_to_t_requested_packet_interval, &message_router_response->message); AddDintToMessage(connection_object->t_to_o_requested_packet_interval, &message_router_response->message); } AddSintToMessage(0, &message_router_response->message); /* remaining path size - for routing devices relevant */ AddSintToMessage(0, &message_router_response->message); /* reserved */ return kEipStatusOkSend; /* send reply */ } /** * @brief Adds a Null Address Item to the common data packet format data * @param common_data_packet_format_data The CPF data packet where the Null Address Item shall be added */ void AddNullAddressItem( CipCommonPacketFormatData *common_data_packet_format_data) { /* Precondition: Null Address Item only valid in unconnected messages */ assert( common_data_packet_format_data->data_item.type_id == kCipItemIdUnconnectedDataItem); common_data_packet_format_data->address_item.type_id = kCipItemIdNullAddress; common_data_packet_format_data->address_item.length = 0; } /* INT8 assembleFWDCloseResponse(UINT16 pa_ConnectionSerialNr, UINT16 pa_OriginatorVendorID, UINT32 pa_OriginatorSerialNr, S_CIP_MR_Request *pa_MRRequest, S_CIP_MR_Response *pa_MRResponse, S_CIP_CPF_Data *pa_CPF_data, INT8 pa_status, INT8 *pa_msg) * create FWDClose response dependent on status. * pa_ConnectionSerialNr requested ConnectionSerialNr * pa_OriginatorVendorID requested OriginatorVendorID * pa_OriginatorSerialNr requested OriginalSerialNr * pa_MRRequest pointer to message router request * pa_MRResponse pointer to message router response * pa_CPF_data pointer to CPF Data Item * pa_status status of FWDClose * pa_msg pointer to memory where reply has to be stored * return status * 0 .. no reply need to ne sent back * 1 .. need to send reply * -1 .. error */ EipStatus AssembleForwardCloseResponse(EipUint16 connection_serial_number, EipUint16 originatior_vendor_id, EipUint32 originator_serial_number, CipMessageRouterRequest *message_router_request, CipMessageRouterResponse *message_router_response, EipUint16 extended_error_code) { /* write reply information in CPF struct dependent of pa_status */ CipCommonPacketFormatData *common_data_packet_format_data = &g_common_packet_format_data_item; common_data_packet_format_data->item_count = 2; common_data_packet_format_data->data_item.type_id = kCipItemIdUnconnectedDataItem; AddNullAddressItem(common_data_packet_format_data); AddIntToMessage(connection_serial_number, &message_router_response->message); AddIntToMessage(originatior_vendor_id, &message_router_response->message); AddDintToMessage(originator_serial_number, &message_router_response->message); message_router_response->reply_service = (0x80 | message_router_request->service); /* Excepted length is 10 if there is no application specific data */ if(kConnectionManagerExtendedStatusCodeSuccess == extended_error_code) { AddSintToMessage(0, &message_router_response->message); /* no application data */ message_router_response->general_status = kCipErrorSuccess; message_router_response->size_of_additional_status = 0; } else { AddSintToMessage(*message_router_request->data, &message_router_response->message); /* remaining path size */ if(kConnectionManagerExtendedStatusWrongCloser == extended_error_code) { message_router_response->general_status = kCipErrorPrivilegeViolation; } else { message_router_response->general_status = kCipErrorConnectionFailure; message_router_response->additional_status[0] = extended_error_code; message_router_response->size_of_additional_status = 1; } } AddSintToMessage(0, &message_router_response->message); /* reserved */ return kEipStatusOkSend; } CipConnectionObject *GetConnectedObject(const EipUint32 connection_id) { DoublyLinkedListNode *iterator = connection_list.first; while(NULL != iterator) { if(kConnectionObjectStateEstablished == ConnectionObjectGetState(iterator->data) && connection_id == ConnectionObjectGetCipConsumedConnectionID(iterator->data) ) { return iterator->data; } iterator = iterator->next; } return NULL; } CipConnectionObject *GetConnectedOutputAssembly( const EipUint32 output_assembly_id) { DoublyLinkedListNode *iterator = connection_list.first; while(NULL != iterator) { if(kConnectionObjectInstanceTypeIOExclusiveOwner == ConnectionObjectGetInstanceType(iterator->data) && (kConnectionObjectStateEstablished == ConnectionObjectGetState(iterator->data) || kConnectionObjectStateTimedOut == ConnectionObjectGetState(iterator->data) ) && output_assembly_id == ( (CipConnectionObject *) iterator->data )->produced_path.instance_id) { return iterator->data; } iterator = iterator->next; } return NULL; } CipConnectionObject *CheckForExistingConnection( const CipConnectionObject *const connection_object) { DoublyLinkedListNode *iterator = connection_list.first; while(NULL != iterator) { if(kConnectionObjectStateEstablished == ConnectionObjectGetState(iterator->data) ) { if(EqualConnectionTriad(connection_object, iterator->data) ) { return iterator->data; } } iterator = iterator->next; } return NULL; } EipStatus CheckElectronicKeyData(EipUint8 key_format, void *key_data, EipUint16 *extended_status) { /* Default return value */ *extended_status = kConnectionManagerExtendedStatusCodeSuccess; /* Check key format */ if(4 != key_format) { *extended_status = kConnectionManagerExtendedStatusCodeErrorInvalidSegmentTypeInPath; return kEipStatusError; } bool compatiblity_mode = ElectronicKeyFormat4GetMajorRevisionCompatibility( key_data); /* Check VendorID and ProductCode, must match, or 0 */ if( ( (ElectronicKeyFormat4GetVendorId(key_data) != g_identity.vendor_id) && (ElectronicKeyFormat4GetVendorId(key_data) != 0) ) || ( (ElectronicKeyFormat4GetProductCode(key_data) != g_identity.product_code) && (ElectronicKeyFormat4GetProductCode(key_data) != 0) ) ) { *extended_status = kConnectionManagerExtendedStatusCodeErrorVendorIdOrProductcodeError; return kEipStatusError; } else { /* VendorID and ProductCode are correct */ /* Check DeviceType, must match or 0 */ if( (ElectronicKeyFormat4GetDeviceType(key_data) != g_identity.device_type) && (ElectronicKeyFormat4GetDeviceType(key_data) != 0) ) { *extended_status = kConnectionManagerExtendedStatusCodeErrorDeviceTypeError; return kEipStatusError; } else { /* VendorID, ProductCode and DeviceType are correct */ if(false == compatiblity_mode) { /* Major = 0 is valid */ if(0 == ElectronicKeyFormat4GetMajorRevision(key_data) ) { return kEipStatusOk; } /* Check Major / Minor Revision, Major must match, Minor match or 0 */ if( (ElectronicKeyFormat4GetMajorRevision(key_data) != g_identity.revision.major_revision) || ( (ElectronicKeyFormat4GetMinorRevision(key_data) != g_identity.revision.minor_revision) && (ElectronicKeyFormat4GetMinorRevision(key_data) != 0) ) ) { *extended_status = kConnectionManagerExtendedStatusCodeErrorRevisionMismatch; return kEipStatusError; } } else { /* Compatibility mode is set */ /* Major must match, Minor != 0 and <= MinorRevision */ if( (ElectronicKeyFormat4GetMajorRevision(key_data) == g_identity.revision.major_revision) && (ElectronicKeyFormat4GetMinorRevision(key_data) > 0) && (ElectronicKeyFormat4GetMinorRevision(key_data) <= g_identity.revision.minor_revision) ) { return kEipStatusOk; } else { *extended_status = kConnectionManagerExtendedStatusCodeErrorRevisionMismatch; return kEipStatusError; } } /* end if CompatiblityMode handling */ } } return (*extended_status == kConnectionManagerExtendedStatusCodeSuccess) ? kEipStatusOk : kEipStatusError; } EipUint8 ParseConnectionPath(CipConnectionObject *connection_object, CipMessageRouterRequest *message_router_request, EipUint16 *extended_error) { const EipUint8 *message = message_router_request->data; const size_t connection_path_size = GetUsintFromMessage(&message); /* length in words */ if(0 == connection_path_size) { // A (large) forward open request needs to have a connection path size larger than 0 return kEipStatusError; } size_t remaining_path = connection_path_size; OPENER_TRACE_INFO("Received connection path size: %zu \n", connection_path_size); CipClass *class = NULL; CipDword class_id = 0x0; CipInstanceNum instance_id = 0x0; /* with 256 we mark that we haven't got a PIT segment */ ConnectionObjectSetProductionInhibitTime(connection_object, 256); size_t header_length = g_kForwardOpenHeaderLength; if(connection_object->is_large_forward_open) { header_length = g_kLargeForwardOpenHeaderLength; } if( ( header_length + remaining_path * sizeof(CipWord) ) < message_router_request->request_data_size ) { /* the received packet is larger than the data in the path */ *extended_error = 0; return kCipErrorTooMuchData; } if( ( header_length + remaining_path * sizeof(CipWord) ) > message_router_request->request_data_size ) { /*there is not enough data in received packet */ *extended_error = 0; OPENER_TRACE_INFO("Message not long enough for path\n"); return kCipErrorNotEnoughData; } if(remaining_path > 0) { /* first look if there is an electronic key */ if(kSegmentTypeLogicalSegment == GetPathSegmentType(message) ) { if(kLogicalSegmentLogicalTypeSpecial == GetPathLogicalSegmentLogicalType(message) ) { if(kLogicalSegmentSpecialTypeLogicalFormatElectronicKey == GetPathLogicalSegmentSpecialTypeLogicalType(message) ) { if(kElectronicKeySegmentFormatKeyFormat4 == GetPathLogicalSegmentElectronicKeyFormat(message) ) { /* Check if there is enough data for holding the electronic key segment */ if(remaining_path < 5) { *extended_error = 0; OPENER_TRACE_INFO("Message not long enough for electronic key\n"); return kCipErrorNotEnoughData; } /* Electronic key format 4 found */ connection_object->electronic_key.key_format = 4; ElectronicKeyFormat4 *electronic_key = ElectronicKeyFormat4New(); GetElectronicKeyFormat4FromMessage(&message, electronic_key); /* logical electronic key found */ connection_object->electronic_key.key_data = electronic_key; remaining_path -= 5; /*length of the electronic key*/ OPENER_TRACE_INFO( "key: ven ID %d, dev type %d, prod code %d, major %d, minor %d\n", ElectronicKeyFormat4GetVendorId(connection_object->electronic_key. key_data), ElectronicKeyFormat4GetDeviceType(connection_object-> electronic_key.key_data), ElectronicKeyFormat4GetProductCode(connection_object-> electronic_key.key_data), ElectronicKeyFormat4GetMajorRevision(connection_object-> electronic_key.key_data), ElectronicKeyFormat4GetMinorRevision(connection_object-> electronic_key.key_data) ); if(kEipStatusOk != CheckElectronicKeyData(connection_object->electronic_key. key_format, connection_object->electronic_key. key_data, extended_error) ) { ElectronicKeyFormat4Delete(&electronic_key); return kCipErrorConnectionFailure; } ElectronicKeyFormat4Delete(&electronic_key); } } else { OPENER_TRACE_INFO("no key\n"); } } } //TODO: Refactor this afterwards if(kConnectionObjectTransportClassTriggerProductionTriggerCyclic != ConnectionObjectGetTransportClassTriggerProductionTrigger( connection_object) ) { /*non cyclic connections may have a production inhibit */ if(kSegmentTypeNetworkSegment == GetPathSegmentType(message) ) { NetworkSegmentSubtype network_segment_subtype = GetPathNetworkSegmentSubtype(message); if(kNetworkSegmentSubtypeProductionInhibitTimeInMilliseconds == network_segment_subtype) { OPENER_TRACE_INFO("PIT segment available - value: %u\n",message[1]); connection_object->production_inhibit_time = message[1]; message += 2; remaining_path -= 1; } } } if(kSegmentTypeLogicalSegment == GetPathSegmentType(message) && kLogicalSegmentLogicalTypeClassId == GetPathLogicalSegmentLogicalType(message) ) { class_id = CipEpathGetLogicalValue(&message); class = GetCipClass(class_id); if(NULL == class) { OPENER_TRACE_ERR("classid %" PRIx32 " not found\n", class_id); if(class_id >= 0xC8) { /*reserved range of class ids */ *extended_error = kConnectionManagerExtendedStatusCodeErrorInvalidSegmentTypeInPath; } else { *extended_error = kConnectionManagerExtendedStatusCodeInconsistentApplicationPathCombo; } return kCipErrorConnectionFailure; } OPENER_TRACE_INFO("classid %" PRIx32 " (%s)\n", class_id, class->class_name); } else { *extended_error = kConnectionManagerExtendedStatusCodeErrorInvalidSegmentTypeInPath; return kCipErrorConnectionFailure; } remaining_path -= 1; /* 1 16Bit word for the class part of the path */ /* Get instance ID */ if(kSegmentTypeLogicalSegment == GetPathSegmentType(message) && kLogicalSegmentLogicalTypeInstanceId == GetPathLogicalSegmentLogicalType(message) ) { /* store the configuration ID for later checking in the application connection types */ const CipDword temp_id = CipEpathGetLogicalValue(&message); OPENER_TRACE_INFO("Configuration instance id %" PRId32 "\n", temp_id); if( (temp_id > kCipInstanceNumMax) || ( NULL == GetCipInstance(class, (CipInstanceNum)temp_id) ) ) { /*according to the test tool we should respond with this extended error code */ *extended_error = kConnectionManagerExtendedStatusCodeErrorInvalidSegmentTypeInPath; return kCipErrorConnectionFailure; } instance_id = (CipInstanceNum)temp_id; /* 1 or 2 16Bit words for the configuration instance part of the path */ remaining_path -= (instance_id > 0xFF) ? 2 : 1; //TODO: 32 bit case missing } else { OPENER_TRACE_INFO("no config data\n"); } if(kConnectionObjectTransportClassTriggerTransportClass3 == ConnectionObjectGetTransportClassTriggerTransportClass(connection_object) ) { /*we have Class 3 connection*/ if(remaining_path > 0) { OPENER_TRACE_WARN( "Too much data in connection path for class 3 connection\n"); *extended_error = kConnectionManagerExtendedStatusCodeErrorInvalidSegmentTypeInPath; return kCipErrorConnectionFailure; } /* connection end point has to be the message router instance 1 */ if( (class_id != kCipMessageRouterClassCode) || (1 != instance_id) ) { *extended_error = kConnectionManagerExtendedStatusCodeInconsistentApplicationPathCombo; return kCipErrorConnectionFailure; } /* Configuration connection point is producing connection point */ CipConnectionPathEpath connection_epath = { .class_id = class_id, .instance_id = instance_id, .attribute_id_or_connection_point = 0 }; memcpy(&(connection_object->configuration_path), &connection_epath, sizeof(connection_object->configuration_path) ); memcpy(&(connection_object->produced_path), &connection_epath, sizeof(connection_object->produced_path) ); /* End class 3 connection handling */ } else { /* we have an IO connection */ CipConnectionPathEpath connection_epath = { .class_id = class_id, .instance_id = instance_id, .attribute_id_or_connection_point = 0 }; memcpy(&(connection_object->configuration_path), &connection_epath, sizeof(connection_object->configuration_path) ); ConnectionObjectConnectionType originator_to_target_connection_type = ConnectionObjectGetOToTConnectionType(connection_object); ConnectionObjectConnectionType target_to_originator_connection_type = ConnectionObjectGetTToOConnectionType(connection_object); connection_object->consumed_connection_path_length = 0; connection_object->consumed_connection_path = NULL; //connection_object->connection_path.connection_point[1] = 0; /* set not available path to Invalid */ size_t number_of_encoded_paths = 0; CipConnectionPathEpath *paths_to_encode[2] = { 0 }; if(kConnectionObjectConnectionTypeNull == originator_to_target_connection_type) { if(kConnectionObjectConnectionTypeNull == target_to_originator_connection_type) { /* configuration only connection */ number_of_encoded_paths = 0; OPENER_TRACE_WARN("assembly: type invalid\n"); } else { /* 1 path -> path is for production */ OPENER_TRACE_INFO("assembly: type produce\n"); number_of_encoded_paths = 1; paths_to_encode[0] = &(connection_object->produced_path); } } else { if(kConnectionObjectConnectionTypeNull == target_to_originator_connection_type) { /* 1 path -> path is for consumption */ OPENER_TRACE_INFO("assembly: type consume\n"); number_of_encoded_paths = 1; paths_to_encode[0] = &(connection_object->consumed_path); } else { /* 2 paths -> 1st for production 2nd for consumption */ OPENER_TRACE_INFO("assembly: type bidirectional\n"); paths_to_encode[0] = &(connection_object->consumed_path); paths_to_encode[1] = &(connection_object->produced_path); number_of_encoded_paths = 2; } } for(size_t i = 0; i < number_of_encoded_paths; i++) /* process up to 2 encoded paths */ { if(kSegmentTypeLogicalSegment == GetPathSegmentType(message) && (kLogicalSegmentLogicalTypeInstanceId == GetPathLogicalSegmentLogicalType(message) || kLogicalSegmentLogicalTypeConnectionPoint == GetPathLogicalSegmentLogicalType(message) ) ) /* Connection Point interpreted as InstanceNr -> only in Assembly Objects */ { /* Attribute Id or Connection Point */ /* Validate encoded instance number. */ const CipDword temp_instance_id = CipEpathGetLogicalValue(&message); if (temp_instance_id > kCipInstanceNumMax) { *extended_error = kConnectionManagerExtendedStatusCodeErrorInvalidSegmentTypeInPath; return kCipErrorConnectionFailure; } instance_id = (CipInstanceNum)temp_instance_id; CipConnectionPathEpath path; path.class_id = class_id; path.instance_id = instance_id; path.attribute_id_or_connection_point = 0; memcpy(paths_to_encode[i], &path, sizeof(connection_object->produced_path) ); OPENER_TRACE_INFO( "connection point %" PRIu32 "\n", instance_id); if( NULL == GetCipInstance(class, instance_id) ) { *extended_error = kConnectionManagerExtendedStatusCodeInconsistentApplicationPathCombo; return kCipErrorConnectionFailure; } /* 1 or 2 16Bit word for the connection point part of the path */ remaining_path -= (instance_id > 0xFF) ? 2 : 1; } else { *extended_error = kConnectionManagerExtendedStatusCodeErrorInvalidSegmentTypeInPath; return kCipErrorConnectionFailure; } } g_config_data_length = 0; g_config_data_buffer = NULL; while(remaining_path > 0) { /* remaining_path_size something left in the path should be configuration data */ SegmentType segment_type = GetPathSegmentType(message); switch(segment_type) { case kSegmentTypeDataSegment: { DataSegmentSubtype data_segment_type = GetPathDataSegmentSubtype( message); switch(data_segment_type) { case kDataSegmentSubtypeSimpleData: g_config_data_length = message[1] * 2; /*data segments store length 16-bit word wise */ g_config_data_buffer = (EipUint8 *) message + 2; remaining_path -= (g_config_data_length + 2) / 2; message += (g_config_data_length + 2); break; default: OPENER_TRACE_ERR("Not allowed in connection manager"); return kCipErrorPathSegmentError; } } break; case kSegmentTypeNetworkSegment: { NetworkSegmentSubtype subtype = GetPathNetworkSegmentSubtype(message); switch(subtype) { case kNetworkSegmentSubtypeProductionInhibitTimeInMilliseconds: if(kConnectionObjectTransportClassTriggerProductionTriggerCyclic != ConnectionObjectGetTransportClassTriggerProductionTrigger( connection_object) ) { /* only non cyclic connections may have a production inhibit */ connection_object->production_inhibit_time = message[1]; message += 2; remaining_path -= 2; } else { *extended_error = connection_path_size - remaining_path; /*offset in 16Bit words where within the connection path the error happened*/ return kCipErrorPathSegmentError; /*status code for invalid segment type*/ } break; default: OPENER_TRACE_ERR("Not allowed in connection manager"); return kCipErrorPathSegmentError; } } break; default: OPENER_TRACE_WARN( "No data segment identifier found for the configuration data\n"); *extended_error = connection_path_size - remaining_path; /*offset in 16Bit words where within the connection path the error happened*/ return kConnectionManagerGeneralStatusPathSegmentErrorInUnconnectedSend; } } } } OPENER_TRACE_INFO("Resulting PIT value: %u\n", connection_object->production_inhibit_time); /*save back the current position in the stream allowing followers to parse anything thats still there*/ message_router_request->data = message; return kEipStatusOk; } void CloseConnection(CipConnectionObject *RESTRICT connection_object) { OPENER_TRACE_INFO("cipconnectionmanager: CloseConnection, trigger: %d \n", ConnectionObjectGetTransportClassTriggerTransportClass(connection_object)); if(kConnectionObjectTransportClassTriggerTransportClass3 != ConnectionObjectGetTransportClassTriggerTransportClass(connection_object) ) { /* only close the UDP connection for not class 3 connections */ CloseUdpSocket(connection_object->socket[kUdpCommuncationDirectionConsuming]); connection_object->socket[kUdpCommuncationDirectionConsuming] = kEipInvalidSocket; CloseUdpSocket(connection_object->socket[kUdpCommuncationDirectionProducing]); connection_object->socket[kUdpCommuncationDirectionProducing] = kEipInvalidSocket; } RemoveFromActiveConnections(connection_object); ConnectionObjectInitializeEmpty(connection_object); } void AddNewActiveConnection(CipConnectionObject *const connection_object) { DoublyLinkedListInsertAtHead(&connection_list, connection_object); ConnectionObjectSetState(connection_object, kConnectionObjectStateEstablished); } void RemoveFromActiveConnections(CipConnectionObject *const connection_object) { for(DoublyLinkedListNode *iterator = connection_list.first; iterator != NULL; iterator = iterator->next) { if(iterator->data == connection_object) { DoublyLinkedListRemoveNode(&connection_list, &iterator); return; } } OPENER_TRACE_ERR("Connection not found in active connection list\n"); } EipBool8 IsConnectedOutputAssembly(const CipInstanceNum instance_number) { EipBool8 is_connected = false; DoublyLinkedListNode *node = connection_list.first; while(NULL != node) { CipConnectionObject *connection_object = (CipConnectionObject *) node->data; CipDword consumed_connection_point = connection_object->consumed_path.instance_id; if(instance_number == consumed_connection_point && true == ConnectionObjectIsTypeIOConnection(connection_object) ) { is_connected = true; break; } node = node->next; } return is_connected; } EipStatus AddConnectableObject(const CipUdint class_code, OpenConnectionFunction open_connection_function) { EipStatus status = kEipStatusError; /*parsing is now finished all data is available and check now establish the connection */ for(unsigned int i = 0; i < g_kNumberOfConnectableObjects; ++i) { if( (0 == g_connection_management_list[i].class_id) || (class_code == g_connection_management_list[i].class_id) ) { g_connection_management_list[i].class_id = class_code; g_connection_management_list[i].open_connection_function = open_connection_function; status = kEipStatusOk; break; } } return status; } ConnectionManagementHandling * GetConnectionManagementEntry(const EipUint32 class_id) { ConnectionManagementHandling *connection_management_entry = NULL; for(unsigned int i = 0; i < g_kNumberOfConnectableObjects; ++i) { if(class_id == g_connection_management_list[i].class_id) { connection_management_entry = &(g_connection_management_list[i]); break; } } return connection_management_entry; } EipStatus TriggerConnections(unsigned int output_assembly, unsigned int input_assembly) { EipStatus status = kEipStatusError; DoublyLinkedListNode *node = connection_list.first; while(NULL != node) { CipConnectionObject *connection_object = node->data; if( (output_assembly == connection_object->consumed_path.instance_id) && (input_assembly == connection_object->produced_path.instance_id) ) { if( kConnectionObjectTransportClassTriggerProductionTriggerApplicationObject == ConnectionObjectGetTransportClassTriggerProductionTrigger( connection_object) ) { /* produce at the next allowed occurrence */ connection_object->transmission_trigger_timer = connection_object->production_inhibit_time; status = kEipStatusOk; } break; } node = node->next; } return status; } void CheckForTimedOutConnectionsAndCloseTCPConnections( const CipConnectionObject *const connection_object, CloseSessionFunction CloseSessions) { DoublyLinkedListNode *search_node = connection_list.first; bool non_timed_out_connection_found = false; while(NULL != search_node) { CipConnectionObject *search_connection = search_node->data; if(ConnectionObjectEqualOriginator(connection_object, search_connection) && connection_object != search_connection && kConnectionObjectStateTimedOut != ConnectionObjectGetState(search_connection) ) { non_timed_out_connection_found = true; break; } search_node = search_node->next; } if(false == non_timed_out_connection_found) { CloseSessions(connection_object); } } void InitializeConnectionManagerData() { memset(g_connection_management_list, 0, g_kNumberOfConnectableObjects * sizeof(ConnectionManagementHandling) ); InitializeClass3ConnectionData(); InitializeIoConnectionData(); }