diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
commit | a0aa2307322cd47bbf416810ac0292925e03be87 (patch) | |
tree | 37076262a026c4b48c8a0e84f44ff9187556ca35 /src/app-layer-enip.c | |
parent | Initial commit. (diff) | |
download | suricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip |
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/app-layer-enip.c')
-rw-r--r-- | src/app-layer-enip.c | 712 |
1 files changed, 712 insertions, 0 deletions
diff --git a/src/app-layer-enip.c b/src/app-layer-enip.c new file mode 100644 index 0000000..94c707c --- /dev/null +++ b/src/app-layer-enip.c @@ -0,0 +1,712 @@ +/* Copyright (C) 2015 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 <kwong@solananetworks.com> + * + * App-layer parser for ENIP protocol + * + */ + +#include "suricata-common.h" +#include "suricata.h" + +#include "util-debug.h" +#include "util-byte.h" +#include "util-enum.h" +#include "util-mem.h" +#include "util-misc.h" + +#include "stream.h" + +#include "app-layer.h" +#include "app-layer-protos.h" +#include "app-layer-parser.h" +#include "app-layer-enip.h" +#include "app-layer-enip-common.h" + +#include "app-layer-detect-proto.h" + +#include "conf.h" +#include "decode.h" + +#include "detect-parse.h" +#include "detect-engine.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "pkt-var.h" +#include "util-profiling.h" + + +SCEnumCharMap enip_decoder_event_table[ ] = { + { NULL, -1 }, +}; + +/** \brief get value for 'complete' status in ENIP + * + * For ENIP we use a simple bool. + */ +static int ENIPGetAlstateProgress(void *tx, uint8_t direction) +{ + return 1; +} + +static AppLayerTxData *ENIPGetTxData(void *vtx) +{ + ENIPTransaction *tx = (ENIPTransaction *)vtx; + return &tx->tx_data; +} + +static AppLayerStateData *ENIPGetStateData(void *vstate) +{ + ENIPState *state = (ENIPState *)vstate; + return &state->state_data; +} + +static void *ENIPGetTx(void *alstate, uint64_t tx_id) +{ + ENIPState *enip = (ENIPState *) alstate; + ENIPTransaction *tx = NULL; + + if (enip->curr && enip->curr->tx_num == tx_id + 1) + return enip->curr; + + TAILQ_FOREACH(tx, &enip->tx_list, next) { + if (tx->tx_num != (tx_id+1)) + continue; + + SCLogDebug("returning tx %p", tx); + return tx; + } + + return NULL; +} + +static uint64_t ENIPGetTxCnt(void *alstate) +{ + return ((ENIPState *)alstate)->transaction_max; +} + +static int ENIPStateGetEventInfo(const char *event_name, int *event_id, AppLayerEventType *event_type) +{ + *event_id = SCMapEnumNameToValue(event_name, enip_decoder_event_table); + + if (*event_id == -1) { + SCLogError("event \"%s\" not present in " + "enip's enum map table.", + event_name); + /* yes this is fatal */ + return -1; + } + + *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION; + + return 0; +} + +static int ENIPStateGetEventInfoById(int event_id, const char **event_name, + AppLayerEventType *event_type) +{ + *event_name = SCMapEnumValueToName(event_id, enip_decoder_event_table); + if (*event_name == NULL) { + SCLogError("event \"%d\" not present in " + "enip's enum map table.", + event_id); + /* yes this is fatal */ + return -1; + } + + *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION; + + return 0; +} + +/** \brief Allocate enip state + * + * return state + */ +static void *ENIPStateAlloc(void *orig_state, AppProto proto_orig) +{ + SCLogDebug("ENIPStateAlloc"); + void *s = SCMalloc(sizeof(ENIPState)); + if (unlikely(s == NULL)) + return NULL; + + memset(s, 0, sizeof(ENIPState)); + + ENIPState *enip_state = (ENIPState *) s; + + TAILQ_INIT(&enip_state->tx_list); + return s; +} + +/** \internal + * \brief Free a ENIP TX + * \param tx ENIP TX to free */ +static void ENIPTransactionFree(ENIPTransaction *tx, ENIPState *state) +{ + SCEnter(); + SCLogDebug("ENIPTransactionFree"); + CIPServiceEntry *svc = NULL; + while ((svc = TAILQ_FIRST(&tx->service_list))) + { + TAILQ_REMOVE(&tx->service_list, svc, next); + + 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(svc); + } + + AppLayerDecoderEventsFreeEvents(&tx->tx_data.events); + + if (tx->tx_data.de_state != NULL) { + DetectEngineStateFree(tx->tx_data.de_state); + + state->tx_with_detect_state_cnt--; + } + + if (state->iter == tx) + state->iter = NULL; + + SCFree(tx); + SCReturn; +} + +/** \brief Free enip state + * + */ +static void ENIPStateFree(void *s) +{ + SCEnter(); + SCLogDebug("ENIPStateFree"); + if (s) + { + ENIPState *enip_state = (ENIPState *) s; + + ENIPTransaction *tx = NULL; + while ((tx = TAILQ_FIRST(&enip_state->tx_list))) + { + TAILQ_REMOVE(&enip_state->tx_list, tx, next); + ENIPTransactionFree(tx, enip_state); + } + + if (enip_state->buffer != NULL) + { + SCFree(enip_state->buffer); + } + + SCFree(s); + } + SCReturn; +} + +/** \internal + * \brief Allocate a ENIP TX + * \retval tx or NULL */ +static ENIPTransaction *ENIPTransactionAlloc(ENIPState *state) +{ + SCLogDebug("ENIPStateTransactionAlloc"); + ENIPTransaction *tx = (ENIPTransaction *) SCCalloc(1, + sizeof(ENIPTransaction)); + if (unlikely(tx == NULL)) + return NULL; + + state->curr = tx; + state->transaction_max++; + + memset(tx, 0x00, sizeof(ENIPTransaction)); + TAILQ_INIT(&tx->service_list); + + tx->enip = state; + tx->tx_num = state->transaction_max; + tx->service_count = 0; + + TAILQ_INSERT_TAIL(&state->tx_list, tx, next); + + return tx; +} + +/** + * \brief enip transaction cleanup callback + */ +static void ENIPStateTransactionFree(void *state, uint64_t tx_id) +{ + SCEnter(); + SCLogDebug("ENIPStateTransactionFree"); + ENIPState *enip_state = state; + ENIPTransaction *tx = NULL; + TAILQ_FOREACH(tx, &enip_state->tx_list, next) + { + + if ((tx_id+1) < tx->tx_num) + break; + else if ((tx_id+1) > tx->tx_num) + continue; + + if (tx == enip_state->curr) + enip_state->curr = NULL; + + if (tx->tx_data.events != NULL) { + if (tx->tx_data.events->cnt <= enip_state->events) + enip_state->events -= tx->tx_data.events->cnt; + else + enip_state->events = 0; + } + + TAILQ_REMOVE(&enip_state->tx_list, tx, next); + ENIPTransactionFree(tx, state); + break; + } + SCReturn; +} + +/** \internal + * + * \brief This function is called to retrieve a ENIP + * + * \param state ENIP state structure for the parser + * \param input Input line of the command + * \param input_len Length of the request + * + * \retval 1 when the command is parsed, 0 otherwise + */ +static AppLayerResult ENIPParse(Flow *f, void *state, AppLayerParserState *pstate, + StreamSlice stream_slice, void *local_data, uint8_t direction) +{ + SCEnter(); + ENIPState *enip = (ENIPState *) state; + ENIPTransaction *tx; + + const uint8_t *input = StreamSliceGetData(&stream_slice); + uint32_t input_len = StreamSliceGetDataLen(&stream_slice); + + if (input == NULL && AppLayerParserStateIssetFlag(pstate, + APP_LAYER_PARSER_EOF_TS|APP_LAYER_PARSER_EOF_TC)) + { + SCReturnStruct(APP_LAYER_OK); + } else if (input == NULL && input_len != 0) { + // GAP + SCReturnStruct(APP_LAYER_OK); + } else if (input == NULL || input_len == 0) + { + SCReturnStruct(APP_LAYER_ERROR); + } + + while (input_len > 0) + { + tx = ENIPTransactionAlloc(enip); + if (tx == NULL) + SCReturnStruct(APP_LAYER_OK); + + if (direction == STREAM_TOCLIENT) + tx->tx_data.detect_flags_ts |= APP_LAYER_TX_SKIP_INSPECT_FLAG; + else + tx->tx_data.detect_flags_tc |= APP_LAYER_TX_SKIP_INSPECT_FLAG; + + SCLogDebug("ENIPParse input len %d", input_len); + DecodeENIPPDU(input, input_len, tx); + uint32_t pkt_len = tx->header.length + sizeof(ENIPEncapHdr); + SCLogDebug("ENIPParse packet len %d", pkt_len); + if (pkt_len > input_len) + { + SCLogDebug("Invalid packet length"); + break; + } + + input += pkt_len; + input_len -= pkt_len; + //SCLogDebug("remaining %d", input_len); + + if (input_len < sizeof(ENIPEncapHdr)) + { + //SCLogDebug("Not enough data"); //not enough data for ENIP + break; + } + } + + SCReturnStruct(APP_LAYER_OK); +} + +static AppLayerResult ENIPParseRequest(Flow *f, void *state, AppLayerParserState *pstate, + StreamSlice stream_slice, void *local_data) +{ + return ENIPParse(f, state, pstate, stream_slice, local_data, STREAM_TOSERVER); +} + +static AppLayerResult ENIPParseResponse(Flow *f, void *state, AppLayerParserState *pstate, + StreamSlice stream_slice, void *local_data) +{ + return ENIPParse(f, state, pstate, stream_slice, local_data, STREAM_TOCLIENT); +} + +#define ENIP_LEN_REGISTER_SESSION 4 // protocol u16, options u16 + +static uint16_t ENIPProbingParser(Flow *f, uint8_t direction, + const uint8_t *input, uint32_t input_len, uint8_t *rdir) +{ + // SCLogDebug("ENIPProbingParser %d", input_len); + if (input_len < sizeof(ENIPEncapHdr)) + { + SCLogDebug("length too small to be a ENIP header"); + return ALPROTO_UNKNOWN; + } + uint16_t cmd; + uint16_t enip_len; + uint32_t status; + uint32_t option; + uint16_t nbitems; + + int ret = ByteExtractUint32( + &status, BYTE_LITTLE_ENDIAN, sizeof(uint32_t), (const uint8_t *)(input + 8)); + if (ret < 0) { + return ALPROTO_FAILED; + } + switch (status) { + case SUCCESS: + case INVALID_CMD: + case NO_RESOURCES: + case INCORRECT_DATA: + case INVALID_SESSION: + case INVALID_LENGTH: + case UNSUPPORTED_PROT_REV: + case ENCAP_HEADER_ERROR: + break; + default: + return ALPROTO_FAILED; + } + ret = ByteExtractUint16(&cmd, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input)); + if(ret < 0) { + return ALPROTO_FAILED; + } + ret = ByteExtractUint32( + &option, BYTE_LITTLE_ENDIAN, sizeof(uint32_t), (const uint8_t *)(input + 20)); + if (ret < 0) { + return ALPROTO_FAILED; + } + ret = ByteExtractUint16( + &enip_len, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input + 2)); + if (ret < 0) { + return ALPROTO_FAILED; + } + + //ok for all the known commands + switch(cmd) { + case NOP: + if (option != 0) { + return ALPROTO_FAILED; + } + break; + case REGISTER_SESSION: + if (enip_len != ENIP_LEN_REGISTER_SESSION) { + return ALPROTO_FAILED; + } + break; + case UNREGISTER_SESSION: + if (enip_len != ENIP_LEN_REGISTER_SESSION && enip_len != 0) { + // 0 for request and 4 for response + return ALPROTO_FAILED; + } + break; + case LIST_SERVICES: + case LIST_IDENTITY: + case SEND_RR_DATA: + case SEND_UNIT_DATA: + case INDICATE_STATUS: + case CANCEL: + break; + case LIST_INTERFACES: + if (input_len < sizeof(ENIPEncapHdr) + 2) { + SCLogDebug("length too small to be a ENIP LIST_INTERFACES"); + return ALPROTO_UNKNOWN; + } + ret = ByteExtractUint16( + &nbitems, BYTE_LITTLE_ENDIAN, sizeof(uint16_t), (const uint8_t *)(input)); + if(ret < 0) { + return ALPROTO_FAILED; + } + if (enip_len < sizeof(ENIPEncapHdr) + 2 * (size_t)nbitems) { + return ALPROTO_FAILED; + } + break; + default: + return ALPROTO_FAILED; + } + return ALPROTO_ENIP; +} + +static AppLayerGetTxIterTuple ENIPGetTxIterator(const uint8_t ipproto, const AppProto alproto, + void *alstate, uint64_t min_tx_id, uint64_t max_tx_id, AppLayerGetTxIterState *state) +{ + ENIPState *enip_state = (ENIPState *)alstate; + AppLayerGetTxIterTuple no_tuple = { NULL, 0, false }; + if (enip_state) { + ENIPTransaction *tx_ptr; + if (state->un.ptr == NULL) { + tx_ptr = TAILQ_FIRST(&enip_state->tx_list); + } else { + tx_ptr = (ENIPTransaction *)state->un.ptr; + } + if (tx_ptr) { + while (tx_ptr->tx_num < min_tx_id + 1) { + tx_ptr = TAILQ_NEXT(tx_ptr, next); + if (!tx_ptr) { + return no_tuple; + } + } + if (tx_ptr->tx_num >= max_tx_id + 1) { + return no_tuple; + } + state->un.ptr = TAILQ_NEXT(tx_ptr, next); + AppLayerGetTxIterTuple tuple = { + .tx_ptr = tx_ptr, + .tx_id = tx_ptr->tx_num - 1, + .has_next = (state->un.ptr != NULL), + }; + return tuple; + } + } + return no_tuple; +} + +/** + * \brief Function to register the ENIP protocol parsers and other functions + */ +void RegisterENIPUDPParsers(void) +{ + SCEnter(); + const char *proto_name = "enip"; + + if (AppLayerProtoDetectConfProtoDetectionEnabledDefault("udp", proto_name, false)) { + AppLayerProtoDetectRegisterProtocol(ALPROTO_ENIP, proto_name); + + if (RunmodeIsUnittests()) + { + AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP, + 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, ENIPProbingParser, NULL); + + AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", ALPROTO_ENIP, + 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, ENIPProbingParser, NULL); + + } else + { + if (!AppLayerProtoDetectPPParseConfPorts("udp", IPPROTO_UDP, + proto_name, ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), + ENIPProbingParser, ENIPProbingParser)) + { + SCLogDebug( + "no ENIP UDP config found enabling ENIP detection on port 44818."); + + AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", + ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, + ENIPProbingParser, NULL); + + AppLayerProtoDetectPPRegister(IPPROTO_UDP, "44818", + ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, + ENIPProbingParser, NULL); + } + } + + } else { + SCLogConfig("Protocol detection and parser disabled for %s protocol.", + proto_name); + return; + } + + if (AppLayerParserConfParserEnabled("udp", proto_name)) + { + AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_ENIP, STREAM_TOSERVER, ENIPParseRequest); + AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_ENIP, STREAM_TOCLIENT, ENIPParseResponse); + + AppLayerParserRegisterStateFuncs(IPPROTO_UDP, ALPROTO_ENIP, + ENIPStateAlloc, ENIPStateFree); + + AppLayerParserRegisterGetTx(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTx); + AppLayerParserRegisterGetTxIterator(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxIterator); + AppLayerParserRegisterTxDataFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxData); + AppLayerParserRegisterStateDataFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetStateData); + AppLayerParserRegisterGetTxCnt(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetTxCnt); + AppLayerParserRegisterTxFreeFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateTransactionFree); + + AppLayerParserRegisterGetStateProgressFunc(IPPROTO_UDP, ALPROTO_ENIP, ENIPGetAlstateProgress); + AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_ENIP, 1, 1); + + AppLayerParserRegisterGetEventInfo(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateGetEventInfo); + AppLayerParserRegisterGetEventInfoById(IPPROTO_UDP, ALPROTO_ENIP, ENIPStateGetEventInfoById); + + AppLayerParserRegisterParserAcceptableDataDirection( + IPPROTO_UDP, ALPROTO_ENIP, STREAM_TOSERVER | STREAM_TOCLIENT); + } else + { + SCLogInfo( + "Parsed disabled for %s protocol. Protocol detection" "still on.", + proto_name); + } + +#ifdef UNITTESTS + AppLayerParserRegisterProtocolUnittests(IPPROTO_UDP, ALPROTO_ENIP, ENIPParserRegisterTests); +#endif + + SCReturn; +} + +/** + * \brief Function to register the ENIP protocol parsers and other functions + */ +void RegisterENIPTCPParsers(void) +{ + SCEnter(); + const char *proto_name = "enip"; + + if (AppLayerProtoDetectConfProtoDetectionEnabledDefault("tcp", proto_name, false)) { + AppLayerProtoDetectRegisterProtocol(ALPROTO_ENIP, proto_name); + + if (RunmodeIsUnittests()) + { + AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", ALPROTO_ENIP, + 0, sizeof(ENIPEncapHdr), STREAM_TOSERVER, ENIPProbingParser, NULL); + + AppLayerProtoDetectPPRegister(IPPROTO_TCP, "44818", ALPROTO_ENIP, + 0, sizeof(ENIPEncapHdr), STREAM_TOCLIENT, ENIPProbingParser, NULL); + + } else + { + if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP, + proto_name, ALPROTO_ENIP, 0, sizeof(ENIPEncapHdr), + ENIPProbingParser, ENIPProbingParser)) + { + return; + } + } + + } else { + SCLogDebug("Protocol detection and parser disabled for %s protocol.", + proto_name); + return; + } + + if (AppLayerParserConfParserEnabled("tcp", proto_name)) + { + AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_ENIP, STREAM_TOSERVER, ENIPParseRequest); + AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_ENIP, STREAM_TOCLIENT, ENIPParseResponse); + AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_ENIP, + ENIPStateAlloc, ENIPStateFree); + + AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTx); + AppLayerParserRegisterGetTxIterator(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxIterator); + AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxData); + AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetStateData); + AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetTxCnt); + AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPStateTransactionFree); + + AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_ENIP, ENIPGetAlstateProgress); + AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_ENIP, 1, 1); + + AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_ENIP, ENIPStateGetEventInfo); + + AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, + ALPROTO_ENIP, STREAM_TOSERVER | STREAM_TOCLIENT); + + /* This parser accepts gaps. */ + AppLayerParserRegisterOptionFlags(IPPROTO_TCP, ALPROTO_ENIP, + APP_LAYER_PARSER_OPT_ACCEPT_GAPS); + + AppLayerParserRegisterOptionFlags(IPPROTO_TCP, ALPROTO_ENIP, 0); + } else + { + SCLogConfig("Parser disabled for %s protocol. Protocol detection still on.", + proto_name); + } + +#ifdef UNITTESTS + AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_ENIP, ENIPParserRegisterTests); +#endif + + SCReturn; +} + +/* UNITTESTS */ +#ifdef UNITTESTS +#include "flow-util.h" +#include "stream-tcp.h" + +static uint8_t listIdentity[] = {/* List ID */ 0x63, 0x00, + /* Length */ 0x00, 0x00, + /* Session */ 0x00, 0x00, 0x00, 0x00, + /* Status */ 0x00, 0x00, 0x00, 0x00, + /* Delay*/ 0x00, + /* Context */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Quantity of coils */ 0x00, 0x00, 0x00, 0x00, 0x00}; + +/** + * \brief Test if ENIP Packet matches signature + */ +static int ALDecodeENIPTest(void) +{ + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + Flow f; + TcpSession ssn; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + f.alproto = ALPROTO_ENIP; + + StreamTcpInitConfig(true); + + int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_ENIP, STREAM_TOSERVER, + listIdentity, sizeof(listIdentity)); + FAIL_IF(r != 0); + + ENIPState *enip_state = f.alstate; + FAIL_IF_NULL(enip_state); + + ENIPTransaction *tx = ENIPGetTx(enip_state, 0); + FAIL_IF_NULL(tx); + + FAIL_IF(tx->header.command != 99); + + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(true); + FLOW_DESTROY(&f); + + PASS; +} + +#endif /* UNITTESTS */ + +void ENIPParserRegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("ALDecodeENIPTest", ALDecodeENIPTest); +#endif /* UNITTESTS */ +} |