/* Copyright (C) 2015-2022 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free * Software Foundation. * * This program 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 * version 2 along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ /** * \file * * \author Kevin Wong * * App-layer parser for ENIP protocol common code * */ #include "suricata-common.h" #include "util-unittest.h" #include "util-unittest-helper.h" #include "detect-parse.h" #include "detect-engine.h" #include "util-byte.h" #include "pkt-var.h" #include "util-profiling.h" #include "app-layer-enip-common.h" /** * \brief Extract 8 bits and move up the offset * @param res * @param input * @param offset */ static int ENIPExtractUint8(uint8_t *res, const uint8_t *input, uint16_t *offset, uint32_t input_len) { if (input_len < sizeof(uint8_t) || *offset > (input_len - sizeof(uint8_t))) { SCLogDebug("ENIPExtractUint8: Parsing beyond payload length"); return 0; } *res = *(input + *offset); *offset += sizeof(uint8_t); return 1; } /** * \brief Extract 16 bits and move up the offset * @param res * @param input * @param offset */ static int ENIPExtractUint16(uint16_t *res, const uint8_t *input, uint16_t *offset, uint32_t input_len) { if (input_len < sizeof(uint16_t) || *offset > (input_len - sizeof(uint16_t))) { SCLogDebug("ENIPExtractUint16: Parsing beyond payload length"); return 0; } if (ByteExtractUint16(res, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input + *offset)) == -1) { return 0; } *offset += sizeof(uint16_t); return 1; } /** * \brief Extract 32 bits and move up the offset * @param res * @param input * @param offset */ static int ENIPExtractUint32(uint32_t *res, const uint8_t *input, uint16_t *offset, uint32_t input_len) { if (input_len < sizeof(uint32_t) || *offset > (input_len - sizeof(uint32_t))) { SCLogDebug("ENIPExtractUint32: Parsing beyond payload length"); return 0; } if (ByteExtractUint32(res, BYTE_LITTLE_ENDIAN, sizeof(uint32_t), (const uint8_t *)(input + *offset)) == -1) { return 0; } *offset += sizeof(uint32_t); return 1; } /** * \brief Extract 64 bits and move up the offset * @param res * @param input * @param offset */ static int ENIPExtractUint64(uint64_t *res, const uint8_t *input, uint16_t *offset, uint32_t input_len) { if (input_len < sizeof(uint64_t) || *offset > (input_len - sizeof(uint64_t))) { SCLogDebug("ENIPExtractUint64: Parsing beyond payload length"); return 0; } if (ByteExtractUint64(res, BYTE_LITTLE_ENDIAN, sizeof(uint64_t), (const uint8_t *)(input + *offset)) == -1) { return 0; } *offset += sizeof(uint64_t); return 1; } /** * \brief Create service entry, add to transaction * @param tx Transaction * @return service entry */ static CIPServiceEntry *CIPServiceAlloc(ENIPTransaction *tx) { CIPServiceEntry *svc = (CIPServiceEntry *) SCCalloc(1, sizeof(CIPServiceEntry)); if (unlikely(svc == NULL)) return NULL; memset(svc, 0x00, sizeof(CIPServiceEntry)); TAILQ_INIT(&svc->segment_list); TAILQ_INIT(&svc->attrib_list); TAILQ_INSERT_TAIL(&tx->service_list, svc, next); tx->service_count++; return svc; } #if 0 /** * \brief Delete service entry */ static void CIPServiceFree(void *s) { SCEnter(); if (s) { CIPServiceEntry *svc = (CIPServiceEntry *) s; SegmentEntry *seg = NULL; while ((seg = TAILQ_FIRST(&svc->segment_list))) { TAILQ_REMOVE(&svc->segment_list, seg, next); SCFree(seg); } AttributeEntry *attr = NULL; while ((attr = TAILQ_FIRST(&svc->attrib_list))) { TAILQ_REMOVE(&svc->attrib_list, attr, next); SCFree(attr); } SCFree(s); } SCReturn; } #endif /** * \brief Decode ENIP Encapsulation Header * @param input, input_len data stream * @param enip_data stores data from Packet * @return 1 Packet ok * @return 0 Packet has errors */ int DecodeENIPPDU(const uint8_t *input, uint32_t input_len, ENIPTransaction *enip_data) { int ret = 1; uint16_t offset = 0; //byte offset //Decode Encapsulation Header uint16_t cmd; uint16_t len; uint32_t session; uint32_t status; uint64_t context; uint32_t option; if (ENIPExtractUint16(&cmd, input, &offset, input_len) != 1) { return 0; } if (ENIPExtractUint16(&len, input, &offset, input_len) != 1) { return 0; } if (ENIPExtractUint32(&session, input, &offset, input_len) != 1) { return 0; } if (ENIPExtractUint32(&status, input, &offset, input_len) != 1) { return 0; } if (ENIPExtractUint64(&context, input, &offset, input_len) != 1) { return 0; } if (ENIPExtractUint32(&option, input, &offset, input_len) != 1) { return 0; } enip_data->header.command = cmd; enip_data->header.length = len; enip_data->header.session = session; enip_data->header.status = status; enip_data->header.context = context; enip_data->header.option = option; switch (enip_data->header.command) { case NOP: SCLogDebug("DecodeENIP - NOP"); break; case LIST_SERVICES: SCLogDebug("DecodeENIP - LIST_SERVICES"); break; case LIST_IDENTITY: SCLogDebug("DecodeENIP - LIST_IDENTITY"); break; case LIST_INTERFACES: SCLogDebug("DecodeENIP - LIST_INTERFACES"); break; case REGISTER_SESSION: SCLogDebug("DecodeENIP - REGISTER_SESSION"); break; case UNREGISTER_SESSION: SCLogDebug("DecodeENIP - UNREGISTER_SESSION"); break; case SEND_RR_DATA: SCLogDebug( "DecodeENIP - SEND_RR_DATA - parse Common Packet Format"); ret = DecodeCommonPacketFormatPDU(input, input_len, enip_data, offset); break; case SEND_UNIT_DATA: SCLogDebug( "DecodeENIP - SEND UNIT DATA - parse Common Packet Format"); ret = DecodeCommonPacketFormatPDU(input, input_len, enip_data, offset); break; case INDICATE_STATUS: SCLogDebug("DecodeENIP - INDICATE_STATUS"); break; case CANCEL: SCLogDebug("DecodeENIP - CANCEL"); break; default: SCLogDebug("DecodeENIP - UNSUPPORTED COMMAND 0x%x", enip_data->header.command); } return ret; } /** * \brief Decode Common Packet Format * @param input, input_len data stream * @param enip_data stores data from Packet * @param offset current point in the packet * @return 1 Packet ok * @return 0 Packet has errors */ int DecodeCommonPacketFormatPDU(const uint8_t *input, uint32_t input_len, ENIPTransaction *enip_data, uint16_t offset) { if (enip_data->header.length < sizeof(ENIPEncapDataHdr)) { SCLogDebug("DecodeCommonPacketFormat: Malformed ENIP packet"); return 0; } uint32_t handle; uint16_t timeout; uint16_t count; if (ENIPExtractUint32(&handle, input, &offset, input_len) != 1) { return 0; } if (ENIPExtractUint16(&timeout, input, &offset, input_len) != 1) { return 0; } if (ENIPExtractUint16(&count, input, &offset, input_len) != 1) { return 0; } enip_data->encap_data_header.interface_handle = handle; enip_data->encap_data_header.timeout = timeout; enip_data->encap_data_header.item_count = count; uint16_t address_type; uint16_t address_length; //length of connection id in bytes uint32_t address_connectionid = 0; uint32_t address_sequence = 0; if (ENIPExtractUint16(&address_type, input, &offset, input_len) != 1) { return 0; } if (ENIPExtractUint16(&address_length, input, &offset, input_len) != 1) { return 0; } //depending on addr type, get connection id, sequence if needed. Can also use addr length too? if (address_type == CONNECTION_BASED) { //get 4 byte connection id if (ENIPExtractUint32(&address_connectionid, input, &offset, input_len) != 1) { return 0; } } else if (address_type == SEQUENCE_ADDR_ITEM) { // get 4 byte connection id and 4 byte sequence if (ENIPExtractUint32(&address_connectionid, input, &offset, input_len) != 1) { return 0; } if (ENIPExtractUint32(&address_sequence, input, &offset, input_len) != 1) { return 0; } } enip_data->encap_addr_item.type = address_type; enip_data->encap_addr_item.length = address_length; enip_data->encap_addr_item.conn_id = address_connectionid; enip_data->encap_addr_item.sequence_num = address_sequence; uint16_t data_type; uint16_t data_length; //length of data in bytes uint16_t data_sequence_count; if (ENIPExtractUint16(&data_type, input, &offset, input_len) != 1) { return 0; } if (ENIPExtractUint16(&data_length, input, &offset, input_len) != 1) { return 0; } enip_data->encap_data_item.type = data_type; enip_data->encap_data_item.length = data_length; if (enip_data->encap_data_item.type == CONNECTED_DATA_ITEM) { //connected data items have seq number if (ENIPExtractUint16(&data_sequence_count, input, &offset, input_len) != 1) { return 0; } enip_data->encap_data_item.sequence_count = data_sequence_count; } switch (enip_data->encap_data_item.type) { case CONNECTED_DATA_ITEM: SCLogDebug( "DecodeCommonPacketFormat - CONNECTED DATA ITEM - parse CIP"); DecodeCIPPDU(input, input_len, enip_data, offset); break; case UNCONNECTED_DATA_ITEM: SCLogDebug("DecodeCommonPacketFormat - UNCONNECTED DATA ITEM"); DecodeCIPPDU(input, input_len, enip_data, offset); break; default: SCLogDebug("DecodeCommonPacketFormat - UNKNOWN TYPE 0x%x", enip_data->encap_data_item.type); return 0; } return 1; } /** * \brief Decode CIP packet * @param input, input_len data stream * @param enip_data stores data from Packet * @param offset current point in the packet * @return 1 Packet ok * @return 0 Packet has errors */ int DecodeCIPPDU(const uint8_t *input, uint32_t input_len, ENIPTransaction *enip_data, uint16_t offset) { int ret = 1; if (enip_data->encap_data_item.length == 0) { SCLogDebug("DecodeCIP: No CIP Data"); return 0; } if (offset > (input_len - sizeof(uint8_t))) { SCLogDebug("DecodeCIP: Parsing beyond payload length"); return 0; } uint8_t service = 0; service = *(input + offset); //SCLogDebug("CIP Service 0x%x", service); //use service code first bit to determine request/response, no need to save or push offset if (service >> 7) { ret = DecodeCIPResponsePDU(input, input_len, enip_data, offset); } else { ret = DecodeCIPRequestPDU(input, input_len, enip_data, offset); } return ret; } /** * \brief Decode CIP Request * @param input, input_len data stream * @param enip_data stores data from Packet * @param offset current point in the packet * @return 1 Packet ok * @return 0 Packet has errors */ int DecodeCIPRequestPDU(const uint8_t *input, uint32_t input_len, ENIPTransaction *enip_data, uint16_t offset) { int ret = 1; if (enip_data->encap_data_item.length < sizeof(CIPReqHdr)) { SCLogDebug("DecodeCIPRequest - Malformed CIP Data"); return 0; } uint8_t service = 0; //<-----CIP SERVICE uint8_t path_size = 0; if (ENIPExtractUint8(&service, input, &offset, input_len) != 1) { return 0; } if (ENIPExtractUint8(&path_size, input, &offset, input_len) != 1) { return 0; } if (service > MAX_CIP_SERVICE) { // service codes of value 0x80 or greater are not permitted because in the CIP protocol the highest order bit is used to flag request(0)/response(1) SCLogDebug("DecodeCIPRequest - INVALID CIP SERVICE 0x%x", service); return 0; } //reached maximum number of services if (enip_data->service_count > 32) { SCLogDebug("DecodeCIPRequest: Maximum services reached"); return 0; } //save CIP data CIPServiceEntry *node = CIPServiceAlloc(enip_data); if (node == NULL) { SCLogDebug("DecodeCIPRequest: Unable to create CIP service"); return 0; } node->direction = 0; node->service = service; node->request.path_size = path_size; node->request.path_offset = offset; // SCLogDebug("DecodeCIPRequestPDU: service 0x%x size %d", node->service, // node->request.path_size); DecodeCIPRequestPathPDU(input, input_len, node, offset); offset += path_size * sizeof(uint16_t); //move offset past pathsize //list of CIP services is large and can be vendor specific, store CIP service anyways and let the rule decide the action switch (service) { case CIP_RESERVED: SCLogDebug("DecodeCIPRequest - CIP_RESERVED"); break; case CIP_GET_ATTR_ALL: SCLogDebug("DecodeCIPRequest - CIP_GET_ATTR_ALL"); break; case CIP_GET_ATTR_LIST: SCLogDebug("DecodeCIPRequest - CIP_GET_ATTR_LIST"); break; case CIP_SET_ATTR_LIST: SCLogDebug("DecodeCIPRequest - CIP_SET_ATTR_LIST"); break; case CIP_RESET: SCLogDebug("DecodeCIPRequest - CIP_RESET"); break; case CIP_START: SCLogDebug("DecodeCIPRequest - CIP_START"); break; case CIP_STOP: SCLogDebug("DecodeCIPRequest - CIP_STOP"); break; case CIP_CREATE: SCLogDebug("DecodeCIPRequest - CIP_CREATE"); break; case CIP_DELETE: SCLogDebug("DecodeCIPRequest - CIP_DELETE"); break; case CIP_MSP: SCLogDebug("DecodeCIPRequest - CIP_MSP"); DecodeCIPRequestMSPPDU(input, input_len, enip_data, offset); break; case CIP_APPLY_ATTR: SCLogDebug("DecodeCIPRequest - CIP_APPLY_ATTR"); break; case CIP_KICK_TIMER: SCLogDebug("DecodeCIPRequest - CIP_KICK_TIMER"); break; case CIP_OPEN_CONNECTION: SCLogDebug("DecodeCIPRequest - CIP_OPEN_CONNECTION"); break; case CIP_CHANGE_START: SCLogDebug("DecodeCIPRequest - CIP_CHANGE_START"); break; case CIP_GET_STATUS: SCLogDebug("DecodeCIPRequest - CIP_GET_STATUS"); break; default: SCLogDebug("DecodeCIPRequest - CIP SERVICE 0x%x", service); } return ret; } /** * \brief Decode CIP Request Path * @param input, input_len data stream * @param enip_data stores data from Packet * @param offset current point in the packet * @param cipserviced the cip service rule * @return 1 Packet matches * @return 0 Packet not match */ int DecodeCIPRequestPathPDU(const uint8_t *input, uint32_t input_len, CIPServiceEntry *node, uint16_t offset) { //SCLogDebug("DecodeCIPRequestPath: service 0x%x size %d length %d", // node->service, node->request.path_size, input_len); if (node->request.path_size < 1) { //SCLogDebug("DecodeCIPRequestPath: empty path or CIP Response"); return 0; } int bytes_remain = node->request.path_size; uint8_t reserved; //unused byte reserved by ODVA //8 bit fields uint8_t req_path_instance8; uint8_t req_path_attr8; //16 bit fields uint16_t req_path_class16; uint16_t req_path_instance16; uint16_t class = 0; SegmentEntry *seg = NULL; while (bytes_remain > 0) { uint8_t segment = 0; if (ENIPExtractUint8(&segment, input, &offset, input_len) != 1) { return 0; } switch (segment) { //assume order is class then instance. Can have multiple case PATH_CLASS_8BIT: { uint8_t req_path_class8 = 0; if (ENIPExtractUint8(&req_path_class8, input, &offset, input_len) != 1) { return 0; } class = (uint16_t) req_path_class8; SCLogDebug("DecodeCIPRequestPathPDU: 8bit class 0x%x", class); seg = SCMalloc(sizeof(SegmentEntry)); if (unlikely(seg == NULL)) return 0; seg->segment = segment; seg->value = class; TAILQ_INSERT_TAIL(&node->segment_list, seg, next); bytes_remain--; break; } case PATH_INSTANCE_8BIT: if (ENIPExtractUint8(&req_path_instance8, input, &offset, input_len) != 1) { return 0; } //skip instance, don't need to store bytes_remain--; break; case PATH_ATTR_8BIT: //single attribute if (ENIPExtractUint8(&req_path_attr8, input, &offset, input_len) != 1) { return 0; } //uint16_t attrib = (uint16_t) req_path_attr8; //SCLogDebug("DecodeCIPRequestPath: 8bit attr 0x%x", attrib); seg = SCMalloc(sizeof(SegmentEntry)); if (unlikely(seg == NULL)) return 0; seg->segment = segment; seg->value = class; TAILQ_INSERT_TAIL(&node->segment_list, seg, next); bytes_remain--; break; case PATH_CLASS_16BIT: if (ENIPExtractUint8(&reserved, input, &offset, input_len) != 1) //skip reserved { return 0; } if (ENIPExtractUint16(&req_path_class16, input, &offset, input_len) != 1) { return 0; } class = req_path_class16; SCLogDebug("DecodeCIPRequestPath: 16bit class 0x%x", class); seg = SCMalloc(sizeof(SegmentEntry)); if (unlikely(seg == NULL)) return 0; seg->segment = segment; seg->value = class; TAILQ_INSERT_TAIL(&node->segment_list, seg, next); if (bytes_remain >= 2) { bytes_remain = bytes_remain - 2; } else { bytes_remain = 0; } break; case PATH_INSTANCE_16BIT: if (ENIPExtractUint8(&reserved, input, &offset, input_len) != 1) // skip reserved { return 0; } if (ENIPExtractUint16(&req_path_instance16, input, &offset, input_len) != 1) { return 0; } //skip instance, don't need to store if (bytes_remain >= 2) { bytes_remain = bytes_remain - 2; } else { bytes_remain = 0; } break; default: SCLogDebug( "DecodeCIPRequestPath: UNKNOWN SEGMENT 0x%x service 0x%x", segment, node->service); return 0; } } if ((node->service == CIP_SET_ATTR_LIST) || (node->service == CIP_GET_ATTR_LIST)) { uint16_t attr_list_count; uint16_t attribute; //parse get/set attribute list if (ENIPExtractUint16(&attr_list_count, input, &offset, input_len) != 1) { return 0; } SCLogDebug("DecodeCIPRequestPathPDU: attribute list count %d", attr_list_count); for (int i = 0; i < attr_list_count; i++) { if (ENIPExtractUint16(&attribute, input, &offset, input_len) != 1) { return 0; } SCLogDebug("DecodeCIPRequestPathPDU: attribute %d", attribute); //save attrs AttributeEntry *attr = SCMalloc(sizeof(AttributeEntry)); if (unlikely(attr == NULL)) return 0; attr->attribute = attribute; TAILQ_INSERT_TAIL(&node->attrib_list, attr, next); } } return 1; } /** * \brief Decode CIP Response * @param input, input_len data stream * @param enip_data stores data from Packet * @param offset current point in the packet * @return 1 Packet ok * @return 0 Packet has errors */ int DecodeCIPResponsePDU(const uint8_t *input, uint32_t input_len, ENIPTransaction *enip_data, uint16_t offset) { int ret = 1; if (enip_data->encap_data_item.length < sizeof(CIPRespHdr)) { SCLogDebug("DecodeCIPResponse - Malformed CIP Data"); return 0; } uint8_t service = 0; //<----CIP SERVICE uint8_t reserved; //unused byte reserved by ODVA uint16_t status; if (ENIPExtractUint8(&service, input, &offset, input_len) != 1) { return 0; } if (ENIPExtractUint8(&reserved, input, &offset, input_len) != 1) { return 0; } if (ENIPExtractUint16(&status, input, &offset, input_len) != 1) { return 0; } //SCLogDebug("DecodeCIPResponse: service 0x%x",service); service &= 0x7f; //strip off top bit to get service code. Responses have first bit as 1 SCLogDebug("CIP service 0x%x status 0x%x", service, status); //reached maximum number of services if (enip_data->service_count > 32) { SCLogDebug("DecodeCIPRequest: Maximum services reached"); return 0; } //save CIP data CIPServiceEntry *node = CIPServiceAlloc(enip_data); if (node == NULL) { SCLogDebug("DecodeCIPRequest: Unable to create CIP service"); return 0; } node->direction = 1; node->service = service; node->response.status = status; SCLogDebug("DecodeCIPResponsePDU: service 0x%x size %d", node->service, node->request.path_size); //list of CIP services is large and can be vendor specific, store CIP service anyways and let the rule decide the action switch (service) { case CIP_RESERVED: SCLogDebug("DecodeCIPResponse - CIP_RESERVED"); break; case CIP_GET_ATTR_ALL: SCLogDebug("DecodeCIPResponse - CIP_GET_ATTR_ALL"); break; case CIP_GET_ATTR_LIST: SCLogDebug("DecodeCIPResponse - CIP_GET_ATTR_LIST"); break; case CIP_SET_ATTR_LIST: SCLogDebug("DecodeCIPResponse - CIP_SET_ATTR_LIST"); break; case CIP_RESET: SCLogDebug("DecodeCIPResponse - CIP_RESET"); break; case CIP_START: SCLogDebug("DecodeCIPResponse - CIP_START"); break; case CIP_STOP: SCLogDebug("DecodeCIPResponse - CIP_STOP"); break; case CIP_CREATE: SCLogDebug("DecodeCIPResponse - CIP_CREATE"); break; case CIP_DELETE: SCLogDebug("DecodeCIPResponse - CIP_DELETE"); break; case CIP_MSP: SCLogDebug("DecodeCIPResponse - CIP_MSP"); DecodeCIPResponseMSPPDU(input, input_len, enip_data, offset); break; case CIP_APPLY_ATTR: SCLogDebug("DecodeCIPResponse - CIP_APPLY_ATTR"); break; case CIP_KICK_TIMER: SCLogDebug("DecodeCIPResponse - CIP_KICK_TIMER"); break; case CIP_OPEN_CONNECTION: SCLogDebug("DecodeCIPResponse - CIP_OPEN_CONNECTION"); break; case CIP_CHANGE_START: SCLogDebug("DecodeCIPResponse - CIP_CHANGE_START"); break; case CIP_GET_STATUS: SCLogDebug("DecodeCIPResponse - CIP_GET_STATUS"); break; default: SCLogDebug("DecodeCIPResponse - CIP SERVICE 0x%x", service); } return ret; } /** * \brief Decode CIP Request Multi Service Packet * @param input, input_len data stream * @param enip_data stores data from Packet * @param offset current point in the packet * @return 1 Packet ok * @return 0 Packet has errors */ int DecodeCIPRequestMSPPDU(const uint8_t *input, uint32_t input_len, ENIPTransaction *enip_data, uint16_t offset) { int ret = 1; if (offset >= (input_len - sizeof(uint16_t))) { SCLogDebug("DecodeCIPRequestMSPPDU: Parsing beyond payload length"); return 0; } //use temp_offset just to grab the service offset, don't want to use and push offset uint16_t temp_offset = offset; uint16_t num_services; if (ByteExtractUint16(&num_services, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input + temp_offset)) == -1) { return 0; } temp_offset += sizeof(uint16_t); //SCLogDebug("DecodeCIPRequestMSP number of services %d",num_services); for (int svc = 1; svc < num_services + 1; svc++) { if (temp_offset >= (input_len - sizeof(uint16_t))) { SCLogDebug("DecodeCIPRequestMSPPDU: Parsing beyond payload length"); return 0; } uint16_t svc_offset; //read set of service offsets if (ByteExtractUint16(&svc_offset, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input + temp_offset)) == -1) { return 0; } temp_offset += sizeof(uint16_t); //SCLogDebug("parseCIPRequestMSP service %d offset %d",svc, svc_offset); DecodeCIPPDU(input, input_len, enip_data, offset + svc_offset); //parse CIP at found offset } return ret; } /** * \brief Decode CIP Response MultiService Packet. * @param input, input_len data stream * @param enip_data stores data from Packet * @param offset current point in the packet * @return 1 Packet ok * @return 0 Packet has errors */ int DecodeCIPResponseMSPPDU(const uint8_t *input, uint32_t input_len, ENIPTransaction *enip_data, uint16_t offset) { int ret = 1; if (offset >= (input_len - sizeof(uint16_t))) { SCLogDebug("DecodeCIPResponseMSPPDU: Parsing beyond payload length"); return 0; } //use temp_offset just to grab the service offset, don't want to use and push offset uint16_t temp_offset = offset; uint16_t num_services; if (ByteExtractUint16(&num_services, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input + temp_offset)) == -1) { return 0; } temp_offset += sizeof(uint16_t); //SCLogDebug("DecodeCIPResponseMSP number of services %d", num_services); for (int svc = 0; svc < num_services; svc++) { if (temp_offset >= (input_len - sizeof(uint16_t))) { SCLogDebug("DecodeCIPResponseMSP: Parsing beyond payload length"); return 0; } uint16_t svc_offset; //read set of service offsets if (ByteExtractUint16(&svc_offset, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input + temp_offset)) == -1) { return 0; } temp_offset += sizeof(uint16_t); //SCLogDebug("parseCIPResponseMSP service %d offset %d", svc, svc_offset); DecodeCIPPDU(input, input_len, enip_data, offset + svc_offset); //parse CIP at found offset } return ret; }