diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
commit | 8879b125d26e8db1a5633de5a9c692eb2d1c4f83 (patch) | |
tree | c7259d85a991b83dfa85ab2e339360669fc1f58e /framework/src/suricata/src/app-layer-modbus.c | |
parent | 13d05bc8458758ee39cb829098241e89616717ee (diff) |
suricata checkin based on commit id a4bce14770beee46a537eda3c3f6e8e8565d5d0a
Change-Id: I9a214fa0ee95e58fc640e50bd604dac7f42db48f
Diffstat (limited to 'framework/src/suricata/src/app-layer-modbus.c')
-rw-r--r-- | framework/src/suricata/src/app-layer-modbus.c | 2671 |
1 files changed, 2671 insertions, 0 deletions
diff --git a/framework/src/suricata/src/app-layer-modbus.c b/framework/src/suricata/src/app-layer-modbus.c new file mode 100644 index 00000000..fa965135 --- /dev/null +++ b/framework/src/suricata/src/app-layer-modbus.c @@ -0,0 +1,2671 @@ +/* + * Copyright (C) 2014 ANSSI + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * + * \author David DIALLO <diallo@et.esiea.fr> + * + * App-layer parser for Modbus protocol + * + */ + +#include "suricata-common.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-protos.h" +#include "app-layer-parser.h" +#include "app-layer-modbus.h" + +#include "app-layer-detect-proto.h" + +#include "conf.h" +#include "decode.h" + +SCEnumCharMap modbus_decoder_event_table[ ] = { + /* Modbus Application Data Unit messages - ADU Modbus */ + { "INVALID_PROTOCOL_ID", MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID }, + { "UNSOLICITED_RESPONSE", MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE }, + { "INVALID_LENGTH", MODBUS_DECODER_EVENT_INVALID_LENGTH }, + { "INVALID_UNIT_IDENTIFIER", MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER}, + + /* Modbus Protocol Data Unit messages - PDU Modbus */ + { "INVALID_FUNCTION_CODE", MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE }, + { "INVALID_VALUE", MODBUS_DECODER_EVENT_INVALID_VALUE }, + { "INVALID_EXCEPTION_CODE", MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE }, + { "VALUE_MISMATCH", MODBUS_DECODER_EVENT_VALUE_MISMATCH }, + + /* Modbus Decoder event */ + { "FLOODED", MODBUS_DECODER_EVENT_FLOODED}, + { NULL, -1 }, +}; + +/* Modbus Application Data Unit (ADU) length range. */ +#define MODBUS_MIN_ADU_LEN 2 +#define MODBUS_MAX_ADU_LEN 254 + +/* Modbus Protocol version. */ +#define MODBUS_PROTOCOL_VER 0 + +/* Modbus Unit Identifier range. */ +#define MODBUS_MIN_INVALID_UNIT_ID 247 +#define MODBUS_MAX_INVALID_UNIT_ID 255 + +/* Modbus Quantity range. */ +#define MODBUS_MIN_QUANTITY 0 +#define MODBUS_MAX_QUANTITY_IN_BIT_ACCESS 2000 +#define MODBUS_MAX_QUANTITY_IN_WORD_ACCESS 125 + +/* Modbus Count range. */ +#define MODBUS_MIN_COUNT 1 +#define MODBUS_MAX_COUNT 250 + +/* Modbus Function Code. */ +#define MODBUS_FUNC_NONE 0x00 +#define MODBUS_FUNC_READCOILS 0x01 +#define MODBUS_FUNC_READDISCINPUTS 0x02 +#define MODBUS_FUNC_READHOLDREGS 0x03 +#define MODBUS_FUNC_READINPUTREGS 0x04 +#define MODBUS_FUNC_WRITESINGLECOIL 0x05 +#define MODBUS_FUNC_WRITESINGLEREG 0x06 +#define MODBUS_FUNC_READEXCSTATUS 0x07 +#define MODBUS_FUNC_DIAGNOSTIC 0x08 +#define MODBUS_FUNC_GETCOMEVTCOUNTER 0x0b +#define MODBUS_FUNC_GETCOMEVTLOG 0x0c +#define MODBUS_FUNC_WRITEMULTCOILS 0x0f +#define MODBUS_FUNC_WRITEMULTREGS 0x10 +#define MODBUS_FUNC_REPORTSERVERID 0x11 +#define MODBUS_FUNC_READFILERECORD 0x14 +#define MODBUS_FUNC_WRITEFILERECORD 0x15 +#define MODBUS_FUNC_MASKWRITEREG 0x16 +#define MODBUS_FUNC_READWRITEMULTREGS 0x17 +#define MODBUS_FUNC_READFIFOQUEUE 0x18 +#define MODBUS_FUNC_ENCAPINTTRANS 0x2b +#define MODBUS_FUNC_MASK 0x7f +#define MODBUS_FUNC_ERRORMASK 0x80 + +/* Modbus Diagnostic functions: Subfunction Code. */ +#define MODBUS_SUBFUNC_QUERY_DATA 0x00 +#define MODBUS_SUBFUNC_RESTART_COM 0x01 +#define MODBUS_SUBFUNC_DIAG_REGS 0x02 +#define MODBUS_SUBFUNC_CHANGE_DELIMITER 0x03 +#define MODBUS_SUBFUNC_LISTEN_MODE 0x04 +#define MODBUS_SUBFUNC_CLEAR_REGS 0x0a +#define MODBUS_SUBFUNC_BUS_MSG_COUNT 0x0b +#define MODBUS_SUBFUNC_COM_ERR_COUNT 0x0c +#define MODBUS_SUBFUNC_EXCEPT_ERR_COUNT 0x0d +#define MODBUS_SUBFUNC_SERVER_MSG_COUNT 0x0e +#define MODBUS_SUBFUNC_SERVER_NO_RSP_COUNT 0x0f +#define MODBUS_SUBFUNC_SERVER_NAK_COUNT 0x10 +#define MODBUS_SUBFUNC_SERVER_BUSY_COUNT 0x11 +#define MODBUS_SUBFUNC_SERVER_CHAR_COUNT 0x12 +#define MODBUS_SUBFUNC_CLEAR_COUNT 0x14 + +/* Modbus Encapsulated Interface Transport function: MEI type. */ +#define MODBUS_MEI_ENCAPINTTRANS_CAN 0x0d +#define MODBUS_MEI_ENCAPINTTRANS_READ 0x0e + +/* Modbus Exception Codes. */ +#define MODBUS_ERROR_CODE_ILLEGAL_FUNCTION 0x01 +#define MODBUS_ERROR_CODE_ILLEGAL_DATA_ADDRESS 0x02 +#define MODBUS_ERROR_CODE_ILLEGAL_DATA_VALUE 0x03 +#define MODBUS_ERROR_CODE_SERVER_DEVICE_FAILURE 0x04 +#define MODBUS_ERROR_CODE_MEMORY_PARITY_ERROR 0x08 + +/* Modbus Application Protocol (MBAP) header. */ +struct ModbusHeader_ { + uint16_t transactionId; + uint16_t protocolId; + uint16_t length; + uint8_t unitId; +} __attribute__((__packed__)); +typedef struct ModbusHeader_ ModbusHeader; + +/* Modbus Read/Write function and Access Types. */ +#define MODBUS_TYP_WRITE_SINGLE (MODBUS_TYP_WRITE | MODBUS_TYP_SINGLE) +#define MODBUS_TYP_WRITE_MULTIPLE (MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE) +#define MODBUS_TYP_READ_WRITE_MULTIPLE (MODBUS_TYP_READ | MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE) + +/* Macro to convert quantity value (in bit) into count value (in word): count = Ceil(quantity/8) */ +#define CEIL(quantity) (((quantity) + 7)>>3) + +/* Modbus Default unreplied Modbus requests are considered a flood */ +#define MODBUS_CONFIG_DEFAULT_REQUEST_FLOOD 500 + +static uint32_t request_flood = MODBUS_CONFIG_DEFAULT_REQUEST_FLOOD; + +int ModbusStateGetEventInfo(const char *event_name, int *event_id, AppLayerEventType *event_type) { + *event_id = SCMapEnumNameToValue(event_name, modbus_decoder_event_table); + + if (*event_id == -1) { + SCLogError(SC_ERR_INVALID_ENUM_MAP, "event \"%s\" not present in " + "modbus's enum map table.", event_name); + /* yes this is fatal */ + return -1; + } + + *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION; + + return 0; +} + +void ModbusSetEvent(ModbusState *modbus, uint8_t e) { + if (modbus && modbus->curr) { + SCLogDebug("modbus->curr->decoder_events %p", modbus->curr->decoder_events); + AppLayerDecoderEventsSetEventRaw(&modbus->curr->decoder_events, e); + SCLogDebug("modbus->curr->decoder_events %p", modbus->curr->decoder_events); + modbus->events++; + } else + SCLogDebug("couldn't set event %u", e); +} + +AppLayerDecoderEvents *ModbusGetEvents(void *state, uint64_t id) { + ModbusState *modbus = (ModbusState *) state; + ModbusTransaction *tx; + + if (modbus->curr && modbus->curr->tx_num == (id + 1)) + return modbus->curr->decoder_events; + + TAILQ_FOREACH(tx, &modbus->tx_list, next) { + if (tx->tx_num == (id+1)) + return tx->decoder_events; + } + + return NULL; +} + +int ModbusHasEvents(void *state) { + return (((ModbusState *) state)->events > 0); +} + +int ModbusGetAlstateProgress(void *modbus_tx, uint8_t direction) { + ModbusTransaction *tx = (ModbusTransaction *) modbus_tx; + ModbusState *modbus = tx->modbus; + + if (tx->replied == 1) + return 1; + + /* Check flood limit */ + if ((modbus->givenup == 1) && + ((modbus->transaction_max - tx->tx_num) > request_flood)) + return 1; + + return 0; +} + +/** \brief Get value for 'complete' status in Modbus + */ +int ModbusGetAlstateProgressCompletionStatus(uint8_t direction) { + return 1; +} + +void *ModbusGetTx(void *alstate, uint64_t tx_id) { + ModbusState *modbus = (ModbusState *) alstate; + ModbusTransaction *tx = NULL; + + if (modbus->curr && modbus->curr->tx_num == tx_id + 1) + return modbus->curr; + + TAILQ_FOREACH(tx, &modbus->tx_list, next) { + SCLogDebug("tx->tx_num %"PRIu64", tx_id %"PRIu64, tx->tx_num, (tx_id+1)); + if (tx->tx_num != (tx_id+1)) + continue; + + SCLogDebug("returning tx %p", tx); + return tx; + } + + return NULL; +} + +uint64_t ModbusGetTxCnt(void *alstate) { + return ((uint64_t) ((ModbusState *) alstate)->transaction_max); +} + +/** \internal + * \brief Find the Modbus Transaction in the state based on Transaction ID. + * + * \param modbus Pointer to Modbus state structure + * \param transactionId Transaction ID of the transaction + * + * \retval tx or NULL if not found + */ +static ModbusTransaction *ModbusTxFindByTransaction(const ModbusState *modbus, + const uint16_t transactionId) { + ModbusTransaction *tx = NULL; + + if (modbus->curr == NULL) + return NULL; + + /* fast path */ + if ((modbus->curr->transactionId == transactionId) && + !(modbus->curr->replied)) { + return modbus->curr; + /* slow path, iterate list */ + } else { + TAILQ_FOREACH(tx, &modbus->tx_list, next) { + if ((tx->transactionId == transactionId) && + !(modbus->curr->replied)) + return tx; + } + } + /* not found */ + return NULL; +} + +/** \internal + * \brief Allocate a Modbus Transaction and + * add it into Transaction list of Modbus State + * + * \param modbus Pointer to Modbus state structure + * + * \retval Pointer to Transaction or NULL pointer + */ +static ModbusTransaction *ModbusTxAlloc(ModbusState *modbus) { + ModbusTransaction *tx; + + tx = (ModbusTransaction *) SCCalloc(1, sizeof(ModbusTransaction)); + if (unlikely(tx == NULL)) + return NULL; + + modbus->transaction_max++; + modbus->unreplied_cnt++; + + /* Check flood limit */ + if ((request_flood != 0) && (modbus->unreplied_cnt > request_flood)) { + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_FLOODED); + modbus->givenup = 1; + } + + modbus->curr = tx; + + SCLogDebug("modbus->transaction_max updated to %"PRIu64, modbus->transaction_max); + + TAILQ_INSERT_TAIL(&modbus->tx_list, tx, next); + + tx->modbus = modbus; + tx->tx_num = modbus->transaction_max; + + return tx; +} + +/** \internal + * \brief Free a Modbus Transaction + * + * \retval Pointer to Transaction or NULL pointer + */ +static void ModbusTxFree(ModbusTransaction *tx) { + SCEnter(); + if (tx->data != NULL) + SCFree(tx->data); + + AppLayerDecoderEventsFreeEvents(&tx->decoder_events); + + if (tx->de_state != NULL) + DetectEngineStateFree(tx->de_state); + + SCFree(tx); + SCReturn; +} + +/** + * \brief Modbus transaction cleanup callback + */ +void ModbusStateTxFree(void *state, uint64_t tx_id) { + SCEnter(); + ModbusState *modbus = (ModbusState *) state; + ModbusTransaction *tx = NULL, *ttx; + + SCLogDebug("state %p, id %"PRIu64, modbus, tx_id); + + TAILQ_FOREACH_SAFE(tx, &modbus->tx_list, next, ttx) { + SCLogDebug("tx %p tx->tx_num %"PRIu64", tx_id %"PRIu64, tx, tx->tx_num, (tx_id+1)); + + if (tx->tx_num != (tx_id+1)) + continue; + + if (tx == modbus->curr) + modbus->curr = NULL; + + if (tx->decoder_events != NULL) { + if (tx->decoder_events->cnt <= modbus->events) + modbus->events -= tx->decoder_events->cnt; + else + modbus->events = 0; + } + + modbus->unreplied_cnt--; + + /* Check flood limit */ + if ((modbus->givenup == 1) && + (request_flood != 0) && + (modbus->unreplied_cnt < request_flood) ) + modbus->givenup = 0; + + TAILQ_REMOVE(&modbus->tx_list, tx, next); + ModbusTxFree(tx); + break; + } + SCReturn; +} + +/** \internal + * \brief Extract 8bits data from pointer the received input data + * + * \param res Pointer to the result + * \param input Pointer the received input data + * \param input_len Length of the received input data + * \param offset Offset of the received input data pointer + */ +static int ModbusExtractUint8(ModbusState *modbus, + uint8_t *res, + uint8_t *input, + uint32_t input_len, + uint16_t *offset) { + SCEnter(); + if (input_len < (uint32_t) (*offset + sizeof(uint8_t))) { + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH); + SCReturnInt(-1); + } + + *res = *(input + *offset); + *offset += sizeof(uint8_t); + SCReturnInt(0); +} + +/** \internal + * \brief Extract 16bits data from pointer the received input data + * + * \param res Pointer to the result + * \param input Pointer the received input data + * \param input_len Length of the received input data + * \param offset Offset of the received input data pointer + */ +static int ModbusExtractUint16(ModbusState *modbus, + uint16_t *res, + uint8_t *input, + uint32_t input_len, + uint16_t *offset) { + SCEnter(); + if (input_len < (uint32_t) (*offset + sizeof(uint16_t))) { + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH); + SCReturnInt(-1); + } + + ByteExtractUint16(res, BYTE_BIG_ENDIAN, sizeof(uint16_t), (const uint8_t *) (input + *offset)); + *offset += sizeof(uint16_t); + SCReturnInt(0); +} + +/** \internal + * \brief Check length field in Modbus header according to code function + * + * \param modbus Pointer to Modbus state structure + * \param length Length field in Modbus Header + * \param len Length according to code functio + */ +static int ModbusCheckHeaderLength(ModbusState *modbus, + uint16_t length, + uint16_t len) { + SCEnter(); + if (length != len) { + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH); + SCReturnInt(-1); + } + SCReturnInt(0); +} + +/** \internal + * \brief Check Modbus header + * + * \param tx Pointer to Modbus Transaction structure + * \param modbus Pointer to Modbus state structure + * \param header Pointer to Modbus header state in which the value to be stored + */ +static void ModbusCheckHeader(ModbusState *modbus, + ModbusHeader *header) +{ + SCEnter(); + /* MODBUS protocol is identified by the value 0. */ + if (header->protocolId != MODBUS_PROTOCOL_VER) + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID); + + /* Check Length field that is a byte count of the following fields */ + if ((header->length < MODBUS_MIN_ADU_LEN) || + (header->length > MODBUS_MAX_ADU_LEN) ) + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH); + + /* Check Unit Identifier field that is not in invalid range */ + if ((header->unitId > MODBUS_MIN_INVALID_UNIT_ID) && + (header->unitId < MODBUS_MAX_INVALID_UNIT_ID) ) + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER); + + SCReturn; +} + +/** \internal + * \brief Parse Exception Response and verify protocol compliance. + * + * \param tx Pointer to Modbus Transaction structure + * \param modbus Pointer to Modbus state structure + * \param input Pointer the received input data + * \param input_len Length of the received input data + * \param offset Offset of the received input data pointer + */ +static void ModbusExceptionResponse(ModbusTransaction *tx, + ModbusState *modbus, + uint8_t *input, + uint32_t input_len, + uint16_t *offset) +{ + SCEnter(); + uint8_t exception; + + /* Exception code (1 byte) */ + if (ModbusExtractUint8(modbus, &exception, input, input_len, offset)) + SCReturn; + + switch (exception) { + case MODBUS_ERROR_CODE_ILLEGAL_FUNCTION: + case MODBUS_ERROR_CODE_SERVER_DEVICE_FAILURE: + break; + case MODBUS_ERROR_CODE_ILLEGAL_DATA_VALUE: + if (tx->function == MODBUS_FUNC_DIAGNOSTIC) { + break; + } + /* Fallthrough */ + case MODBUS_ERROR_CODE_ILLEGAL_DATA_ADDRESS: + if ( (tx->type & MODBUS_TYP_ACCESS_FUNCTION_MASK) || + (tx->function == MODBUS_FUNC_READFIFOQUEUE) || + (tx->function == MODBUS_FUNC_ENCAPINTTRANS)) { + break; + } + /* Fallthrough */ + case MODBUS_ERROR_CODE_MEMORY_PARITY_ERROR: + if ( (tx->function == MODBUS_FUNC_READFILERECORD) || + (tx->function == MODBUS_FUNC_WRITEFILERECORD) ) { + break; + } + /* Fallthrough */ + default: + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE); + break; + } + + SCReturn; +} + +/** \internal + * \brief Parse Read data Request, complete Transaction structure + * and verify protocol compliance. + * + * \param tx Pointer to Modbus Transaction structure + * \param modbus Pointer to Modbus state structure + * \param input Pointer the received input data + * \param input_len Length of the received input data + * \param offset Offset of the received input data pointer + */ +static void ModbusParseReadRequest(ModbusTransaction *tx, + ModbusState *modbus, + uint8_t *input, + uint32_t input_len, + uint16_t *offset) +{ + SCEnter(); + uint16_t quantity; + uint8_t type = tx->type; + + /* Starting Address (2 bytes) */ + if (ModbusExtractUint16(modbus, &(tx->read.address), input, input_len, offset)) + goto end; + + /* Quantity (2 bytes) */ + if (ModbusExtractUint16(modbus, &(tx->read.quantity), input, input_len, offset)) + goto end; + quantity = tx->read.quantity; + + /* Check Quantity range */ + if (type & MODBUS_TYP_BIT_ACCESS_MASK) { + if ((quantity == MODBUS_MIN_QUANTITY) || + (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS)) + goto error; + } else { + if ((quantity == MODBUS_MIN_QUANTITY) || + (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS)) + goto error; + } + + if (~type & MODBUS_TYP_WRITE) + /* Except from Read/Write Multiple Registers function (code 23) */ + /* The length of all Read Data function requests is 6 bytes */ + /* Modbus Application Protocol Specification V1.1b3 from 6.1 to 6.4 */ + ModbusCheckHeaderLength(modbus, tx->length, 6); + + goto end; + +error: + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE); +end: + SCReturn; +} + +/** \internal + * \brief Parse Read data Response and verify protocol compliance + * + * \param tx Pointer to Modbus Transaction structure + * \param modbus Pointer to Modbus state structure + * \param input Pointer the received input data + * \param input_len Length of the received input data + * \param offset Offset of the received input data pointer + */ +static void ModbusParseReadResponse(ModbusTransaction *tx, + ModbusState *modbus, + uint8_t *input, + uint32_t input_len, + uint16_t *offset) +{ + SCEnter(); + uint8_t count; + + /* Count (1 bytes) */ + if (ModbusExtractUint8(modbus, &count, input, input_len, offset)) + goto end; + + /* Check Count range and value according to the request */ + if ((tx->type) & MODBUS_TYP_BIT_ACCESS_MASK) { + if ( (count < MODBUS_MIN_COUNT) || + (count > MODBUS_MAX_COUNT) || + (count != CEIL(tx->read.quantity))) + goto error; + } else { + if ( (count == MODBUS_MIN_COUNT) || + (count > MODBUS_MAX_COUNT) || + (count != (2 * (tx->read.quantity)))) + goto error; + } + + /* Except from Read/Write Multiple Registers function (code 23) */ + /* The length of all Read Data function responses is (3 bytes + count) */ + /* Modbus Application Protocol Specification V1.1b3 from 6.1 to 6.4 */ + ModbusCheckHeaderLength(modbus, tx->length, 3 + count); + goto end; + +error: + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH); +end: + SCReturn; +} + +/** \internal + * \brief Parse Write data Request, complete Transaction structure + * and verify protocol compliance. + * + * \param tx Pointer to Modbus Transaction structure + * \param modbus Pointer to Modbus state structure + * \param input Pointer the received input data + * \param input_len Length of the received input data + * \param offset Offset of the received input data pointer + * + * \retval On success returns 0 or on failure returns -1. + */ +static int ModbusParseWriteRequest(ModbusTransaction *tx, + ModbusState *modbus, + uint8_t *input, + uint32_t input_len, + uint16_t *offset) +{ + SCEnter(); + uint16_t quantity = 1, word; + uint8_t byte, count = 1, type = tx->type; + + int i = 0; + + /* Starting/Output/Register Address (2 bytes) */ + if (ModbusExtractUint16(modbus, &(tx->write.address), input, input_len, offset)) + goto end; + + if (type & MODBUS_TYP_SINGLE) { + /* The length of Write Single Coil (code 5) and */ + /* Write Single Register (code 6) requests is 6 bytes */ + /* Modbus Application Protocol Specification V1.1b3 6.5 and 6.6 */ + if (ModbusCheckHeaderLength(modbus, tx->length, 6)) + goto end; + } else if (type & MODBUS_TYP_MULTIPLE) { + /* Quantity (2 bytes) */ + if (ModbusExtractUint16(modbus, &quantity, input, input_len, offset)) + goto end; + tx->write.quantity = quantity; + + /* Count (1 bytes) */ + if (ModbusExtractUint8(modbus, &count, input, input_len, offset)) + goto end; + tx->write.count = count; + + if (type & MODBUS_TYP_BIT_ACCESS_MASK) { + /* Check Quantity range and conversion in byte (count) */ + if ((quantity == MODBUS_MIN_QUANTITY) || + (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS) || + (quantity != CEIL(count))) + goto error; + + /* The length of Write Multiple Coils (code 15) request is (7 + count) */ + /* Modbus Application Protocol Specification V1.1b3 6.11 */ + if (ModbusCheckHeaderLength(modbus, tx->length, 7 + count)) + goto end; + } else { + /* Check Quantity range and conversion in byte (count) */ + if ((quantity == MODBUS_MIN_QUANTITY) || + (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS) || + (count != (2 * quantity))) + goto error; + + if (type & MODBUS_TYP_READ) { + /* The length of Read/Write Multiple Registers function (code 23) */ + /* request is (11 bytes + count) */ + /* Modbus Application Protocol Specification V1.1b3 6.17 */ + if (ModbusCheckHeaderLength(modbus, tx->length, 11 + count)) + goto end; + } else { + /* The length of Write Multiple Coils (code 15) and */ + /* Write Multiple Registers (code 16) functions requests is (7 bytes + count) */ + /* Modbus Application Protocol Specification V1.1b3 from 6.11 and 6.12 */ + if (ModbusCheckHeaderLength(modbus, tx->length, 7 + count)) + goto end; + } + } + } else { + /* Mask Write Register function (And_Mask and Or_Mask) */ + quantity = 2; + + /* The length of Mask Write Register (code 22) function request is 8 */ + /* Modbus Application Protocol Specification V1.1b3 6.16 */ + if (ModbusCheckHeaderLength(modbus, tx->length, 8)) + goto end; + } + + if (type & MODBUS_TYP_COILS) { + /* Output value (data block) unit is count */ + tx->data = (uint16_t *) SCCalloc(1, count * sizeof(uint16_t)); + if (unlikely(tx->data == NULL)) + SCReturnInt(-1); + + if (type & MODBUS_TYP_SINGLE) { + /* Outputs value (2 bytes) */ + if (ModbusExtractUint16(modbus, &word, input, input_len, offset)) + goto end; + tx->data[i] = word; + + if ((word != 0x00) && (word != 0xFF00)) + goto error; + } else { + for (i = 0; i < count; i++) { + /* Outputs value (1 byte) */ + if (ModbusExtractUint8(modbus, &byte, input, input_len, offset)) + goto end; + tx->data[i] = (uint16_t) byte; + } + } + } else { + /* Registers value (data block) unit is quantity */ + tx->data = (uint16_t *) SCCalloc(1, quantity * sizeof(uint16_t)); + if (unlikely(tx->data == NULL)) + SCReturnInt(-1); + + for (i = 0; i < quantity; i++) { + /* Outputs/Registers value (2 bytes) */ + if (ModbusExtractUint16(modbus, &word, input, input_len, offset)) + goto end; + tx->data[i] = word; + } + } + goto end; + +error: + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE); +end: + SCReturnInt(0); +} + +/** \internal + * \brief Parse Write data Response and verify protocol compliance + * + * \param tx Pointer to Modbus Transaction structure + * \param modbus Pointer to Modbus state structure + * \param input Pointer the received input data + * \param input_len Length of the received input data + * \param offset Offset of the received input data pointer + */ +static void ModbusParseWriteResponse(ModbusTransaction *tx, + ModbusState *modbus, + uint8_t *input, + uint32_t input_len, + uint16_t *offset) +{ + SCEnter(); + uint16_t address, quantity, word; + uint8_t type = tx->type; + + /* Starting Address (2 bytes) */ + if (ModbusExtractUint16(modbus, &address, input, input_len, offset)) + goto end; + + if (address != tx->write.address) + goto error; + + if (type & MODBUS_TYP_SINGLE) { + /* Outputs/Registers value (2 bytes) */ + if (ModbusExtractUint16(modbus, &word, input, input_len, offset)) + goto end; + + /* Check with Outputs/Registers from request */ + if (word != tx->data[0]) + goto error; + } else if (type & MODBUS_TYP_MULTIPLE) { + /* Quantity (2 bytes) */ + if (ModbusExtractUint16(modbus, &quantity, input, input_len, offset)) + goto end; + + /* Check Quantity range */ + if (type & MODBUS_TYP_BIT_ACCESS_MASK) { + if ((quantity == MODBUS_MIN_QUANTITY) || + (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS)) + goto error; + } else { + if ((quantity == MODBUS_MIN_QUANTITY) || + (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS)) + goto error; + } + + /* Check Quantity value according to the request */ + if (quantity != tx->write.quantity) + goto error; + } else { + /* And_Mask value (2 bytes) */ + if (ModbusExtractUint16(modbus, &word, input, input_len, offset)) + goto end; + + /* Check And_Mask value according to the request */ + if (word != tx->data[0]) + goto error; + + /* And_Or_Mask value (2 bytes) */ + if (ModbusExtractUint16(modbus, &word, input, input_len, offset)) + + /* Check Or_Mask value according to the request */ + if (word != tx->data[1]) + goto error; + + /* The length of Mask Write Register (code 22) function response is 8 */ + /* Modbus Application Protocol Specification V1.1b3 6.16 */ + ModbusCheckHeaderLength(modbus, tx->length, 8); + goto end; + } + + /* Except from Mask Write Register (code 22) */ + /* The length of all Write Data function responses is 6 */ + /* Modbus Application Protocol Specification V1.1b3 6.5, 6.6, 6.11, 6.12 and 6.17 */ + ModbusCheckHeaderLength(modbus, tx->length, 6); + goto end; + +error: + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH); +end: + SCReturn; +} + +/** \internal + * \brief Parse Diagnostic Request, complete Transaction + * structure (Category) and verify protocol compliance. + * + * \param tx Pointer to Modbus Transaction structure + * \param modbus Pointer to Modbus state structure + * \param input Pointer the received input data + * \param input_len Length of the received input data + * \param offset Offset of the received input data pointer + * + * \retval Reserved category function returns 1 otherwise returns 0. + */ +static int ModbusParseDiagnosticRequest(ModbusTransaction *tx, + ModbusState *modbus, + uint8_t *input, + uint32_t input_len, + uint16_t *offset) +{ + SCEnter(); + uint16_t data; + + /* Sub-function (2 bytes) */ + if (ModbusExtractUint16(modbus, &(tx->subFunction), input, input_len, offset)) + goto end; + + /* Data (2 bytes) */ + if (ModbusExtractUint16(modbus, &data, input, input_len, offset)) + goto end; + + if (tx->subFunction != MODBUS_SUBFUNC_QUERY_DATA) { + switch (tx->subFunction) { + case MODBUS_SUBFUNC_RESTART_COM: + if ((data != 0x00) && (data != 0xFF00)) + goto error; + break; + + case MODBUS_SUBFUNC_CHANGE_DELIMITER: + if ((data & 0xFF) != 0x00) + goto error; + break; + + case MODBUS_SUBFUNC_LISTEN_MODE: + /* No answer is expected then mark tx as completed. */ + tx->replied = 1; + /* Fallthrough */ + case MODBUS_SUBFUNC_DIAG_REGS: + case MODBUS_SUBFUNC_CLEAR_REGS: + case MODBUS_SUBFUNC_BUS_MSG_COUNT: + case MODBUS_SUBFUNC_COM_ERR_COUNT: + case MODBUS_SUBFUNC_EXCEPT_ERR_COUNT: + case MODBUS_SUBFUNC_SERVER_MSG_COUNT: + case MODBUS_SUBFUNC_SERVER_NO_RSP_COUNT: + case MODBUS_SUBFUNC_SERVER_NAK_COUNT: + case MODBUS_SUBFUNC_SERVER_BUSY_COUNT: + case MODBUS_SUBFUNC_SERVER_CHAR_COUNT: + case MODBUS_SUBFUNC_CLEAR_COUNT: + if (data != 0x00) + goto error; + break; + + default: + /* Set function code category */ + tx->category = MODBUS_CAT_RESERVED; + SCReturnInt(1); + } + + /* The length of all Diagnostic Requests is 6 */ + /* Modbus Application Protocol Specification V1.1b3 6.8 */ + ModbusCheckHeaderLength(modbus, tx->length, 6); + } + + goto end; + +error: + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE); +end: + SCReturnInt(0); +} + +/* Modbus Function Code Categories structure. */ +typedef struct ModbusFunctionCodeRange_ { + uint8_t function; + uint8_t category; +} ModbusFunctionCodeRange; + +/* Modbus Function Code Categories table. */ +static ModbusFunctionCodeRange modbusFunctionCodeRanges[] = { + { 0, MODBUS_CAT_PUBLIC_UNASSIGNED}, + { 9, MODBUS_CAT_RESERVED }, + { 15, MODBUS_CAT_PUBLIC_UNASSIGNED}, + { 41, MODBUS_CAT_RESERVED }, + { 43, MODBUS_CAT_PUBLIC_UNASSIGNED}, + { 65, MODBUS_CAT_USER_DEFINED }, + { 73, MODBUS_CAT_PUBLIC_UNASSIGNED}, + { 90, MODBUS_CAT_RESERVED }, + { 92, MODBUS_CAT_PUBLIC_UNASSIGNED}, + { 100, MODBUS_CAT_USER_DEFINED }, + { 111, MODBUS_CAT_PUBLIC_UNASSIGNED}, + { 125, MODBUS_CAT_RESERVED }, + { 128, MODBUS_CAT_NONE } +}; + +/** \internal + * \brief Parse the Modbus Protocol Data Unit (PDU) Request + * + * \param tx Pointer to Modbus Transaction structure + * \param ModbusPdu Pointer the Modbus PDU state in which the value to be stored + * \param input Pointer the received input data + * \param input_len Length of the received input data + */ +static void ModbusParseRequestPDU(ModbusTransaction *tx, + ModbusState *modbus, + uint8_t *input, + uint32_t input_len) +{ + SCEnter(); + uint16_t offset = (uint16_t) sizeof(ModbusHeader); + uint8_t count; + + int i = 0; + + /* Standard function codes used on MODBUS application layer protocol (1 byte) */ + if (ModbusExtractUint8(modbus, &(tx->function), input, input_len, &offset)) + goto end; + + /* Set default function code category */ + tx->category = MODBUS_CAT_NONE; + + /* Set default function primary table */ + tx->type = MODBUS_TYP_NONE; + + switch (tx->function) { + case MODBUS_FUNC_NONE: + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE); + break; + + case MODBUS_FUNC_READCOILS: + /* Set function type */ + tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_READ); + break; + + case MODBUS_FUNC_READDISCINPUTS: + /* Set function type */ + tx->type = (MODBUS_TYP_DISCRETES | MODBUS_TYP_READ); + break; + + case MODBUS_FUNC_READHOLDREGS: + /* Set function type */ + tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_READ); + break; + + case MODBUS_FUNC_READINPUTREGS: + /* Set function type */ + tx->type = (MODBUS_TYP_INPUT | MODBUS_TYP_READ); + break; + + case MODBUS_FUNC_WRITESINGLECOIL: + /* Set function type */ + tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_WRITE_SINGLE); + break; + + case MODBUS_FUNC_WRITESINGLEREG: + /* Set function type */ + tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE_SINGLE); + break; + + case MODBUS_FUNC_WRITEMULTCOILS: + /* Set function type */ + tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_WRITE_MULTIPLE); + break; + + case MODBUS_FUNC_WRITEMULTREGS: + /* Set function type */ + tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE_MULTIPLE); + break; + + case MODBUS_FUNC_MASKWRITEREG: + /* Set function type */ + tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE); + break; + + case MODBUS_FUNC_READWRITEMULTREGS: + /* Set function type */ + tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_READ_WRITE_MULTIPLE); + break; + + case MODBUS_FUNC_READFILERECORD: + case MODBUS_FUNC_WRITEFILERECORD: + /* Count/length (1 bytes) */ + if (ModbusExtractUint8(modbus, &count, input, input_len, &offset)) + goto end; + + /* Modbus Application Protocol Specification V1.1b3 6.14 and 6.15 */ + ModbusCheckHeaderLength(modbus, tx->length, 2 + count); + break; + + case MODBUS_FUNC_DIAGNOSTIC: + if(ModbusParseDiagnosticRequest(tx, modbus, input, input_len, &offset)) + goto end; + break; + + case MODBUS_FUNC_READEXCSTATUS: + case MODBUS_FUNC_GETCOMEVTCOUNTER: + case MODBUS_FUNC_GETCOMEVTLOG: + case MODBUS_FUNC_REPORTSERVERID: + /* Modbus Application Protocol Specification V1.1b3 6.7, 6.9, 6.10 and 6.13 */ + ModbusCheckHeaderLength(modbus, tx->length, 2); + break; + + case MODBUS_FUNC_READFIFOQUEUE: + /* Modbus Application Protocol Specification V1.1b3 6.18 */ + ModbusCheckHeaderLength(modbus, tx->length, 4); + break; + + case MODBUS_FUNC_ENCAPINTTRANS: + /* MEI type (1 byte) */ + if (ModbusExtractUint8(modbus, &(tx->mei), input, input_len, &offset)) + goto end; + + if (tx->mei == MODBUS_MEI_ENCAPINTTRANS_READ) { + /* Modbus Application Protocol Specification V1.1b3 6.21 */ + ModbusCheckHeaderLength(modbus, tx->length, 5); + } else if (tx->mei != MODBUS_MEI_ENCAPINTTRANS_CAN) { + /* Set function code category */ + tx->category = MODBUS_CAT_RESERVED; + goto end; + } + break; + + default: + /* Check if request is error. */ + if (tx->function & MODBUS_FUNC_ERRORMASK) { + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE); + goto end; + } + + /* Get and store function code category */ + for (i = 0; modbusFunctionCodeRanges[i].category != MODBUS_CAT_NONE; i++) { + if (tx->function <= modbusFunctionCodeRanges[i].function) + break; + tx->category = modbusFunctionCodeRanges[i].category; + } + goto end; + } + + /* Set function code category */ + tx->category = MODBUS_CAT_PUBLIC_ASSIGNED; + + if (tx->type & MODBUS_TYP_READ) + ModbusParseReadRequest(tx, modbus, input, input_len, &offset); + + if (tx->type & MODBUS_TYP_WRITE) + ModbusParseWriteRequest(tx, modbus, input, input_len, &offset); + +end: + SCReturn; +} + +/** \internal + * \brief Parse the Modbus Protocol Data Unit (PDU) Response + * + * \param tx Pointer to Modbus Transaction structure + * \param modbus Pointer the Modbus PDU state in which the value to be stored + * \param input Pointer the received input data + * \param input_len Length of the received input data + * \param offset Offset of the received input data pointer + */ +static void ModbusParseResponsePDU(ModbusTransaction *tx, + ModbusState *modbus, + uint8_t *input, + uint32_t input_len) +{ + SCEnter(); + uint16_t offset = (uint16_t) sizeof(ModbusHeader); + uint8_t count, error = FALSE, function, mei; + + /* Standard function codes used on MODBUS application layer protocol (1 byte) */ + if (ModbusExtractUint8(modbus, &function, input, input_len, &offset)) + goto end; + + /* Check if response is error */ + if(function & MODBUS_FUNC_ERRORMASK) { + function &= MODBUS_FUNC_MASK; + error = TRUE; + } + + if (tx->category == MODBUS_CAT_PUBLIC_ASSIGNED) { + /* Check if response is error. */ + if (error) { + ModbusExceptionResponse(tx, modbus, input, input_len, &offset); + } else { + switch(function) { + case MODBUS_FUNC_READEXCSTATUS: + /* Modbus Application Protocol Specification V1.1b3 6.7 */ + ModbusCheckHeaderLength(modbus, tx->length, 3); + goto end; + + case MODBUS_FUNC_GETCOMEVTCOUNTER: + /* Modbus Application Protocol Specification V1.1b3 6.9 */ + ModbusCheckHeaderLength(modbus, tx->length, 6); + goto end; + + case MODBUS_FUNC_READFILERECORD: + case MODBUS_FUNC_WRITEFILERECORD: + /* Count/length (1 bytes) */ + if (ModbusExtractUint8(modbus, &count, input, input_len, &offset)) + goto end; + + /* Modbus Application Protocol Specification V1.1b3 6.14 and 6.15 */ + ModbusCheckHeaderLength(modbus, tx->length, 2 + count); + goto end; + + case MODBUS_FUNC_ENCAPINTTRANS: + /* MEI type (1 byte) */ + if (ModbusExtractUint8(modbus, &mei, input, input_len, &offset)) + goto end; + + if (mei != tx->mei) + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH); + goto end; + } + + if (tx->type & MODBUS_TYP_READ) + ModbusParseReadResponse(tx, modbus, input, input_len, &offset); + /* Read/Write response contents none write response part */ + else if (tx->type & MODBUS_TYP_WRITE) + ModbusParseWriteResponse(tx, modbus, input, input_len, &offset); + } + } + +end: + SCReturn; +} + +/** \internal + * \brief Parse the Modbus Application Protocol (MBAP) header + * + * \param header Pointer the Modbus header state in which the value to be stored + * \param input Pointer the received input data + */ +static int ModbusParseHeader(ModbusState *modbus, + ModbusHeader *header, + uint8_t *input, + uint32_t input_len) +{ + SCEnter(); + uint16_t offset = 0; + + /* Transaction Identifier (2 bytes) */ + if (ModbusExtractUint16(modbus, &(header->transactionId), input, input_len, &offset) || + /* Protocol Identifier (2 bytes) */ + ModbusExtractUint16(modbus, &(header->protocolId), input, input_len, &offset) || + /* Length (2 bytes) */ + ModbusExtractUint16(modbus, &(header->length), input, input_len, &offset) || + /* Unit Identifier (1 byte) */ + ModbusExtractUint8(modbus, &(header->unitId), input, input_len, &offset)) + SCReturnInt(-1); + + SCReturnInt(0); +} + +/** \internal + * + * \brief This function is called to retrieve a Modbus Request + * + * \param state Modbus 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 int ModbusParseRequest(Flow *f, + void *state, + AppLayerParserState *pstate, + uint8_t *input, + uint32_t input_len, + void *local_data) +{ + SCEnter(); + ModbusState *modbus = (ModbusState *) state; + ModbusTransaction *tx; + ModbusHeader header; + + if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) { + SCReturnInt(1); + } else if (input == NULL || input_len == 0) { + SCReturnInt(-1); + } + + while (input_len > 0) { + uint32_t adu_len = input_len; + uint8_t *adu = input; + + /* Extract MODBUS Header */ + if (ModbusParseHeader(modbus, &header, adu, adu_len)) + SCReturnInt(0); + + /* Update ADU length with length in Modbus header. */ + adu_len = (uint32_t) sizeof(ModbusHeader) + (uint32_t) header.length - 1; + if (adu_len > input_len) + SCReturnInt(0); + + /* Allocate a Transaction Context and add it to Transaction list */ + tx = ModbusTxAlloc(modbus); + if (tx == NULL) + SCReturnInt(0); + + /* Check MODBUS Header */ + ModbusCheckHeader(modbus, &header); + + /* Store Transaction ID & PDU length */ + tx->transactionId = header.transactionId; + tx->length = header.length; + + /* Extract MODBUS PDU and fill Transaction Context */ + ModbusParseRequestPDU(tx, modbus, adu, adu_len); + + /* Update input line and remaining input length of the command */ + input += adu_len; + input_len -= adu_len; + } + + SCReturnInt(1); +} + +/** \internal + * \brief This function is called to retrieve a Modbus response + * + * \param state Pointer to Modbus 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 int ModbusParseResponse(Flow *f, + void *state, + AppLayerParserState *pstate, + uint8_t *input, + uint32_t input_len, + void *local_data) +{ + SCEnter(); + ModbusHeader header; + ModbusState *modbus = (ModbusState *) state; + ModbusTransaction *tx; + + if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) { + SCReturnInt(1); + } else if (input == NULL || input_len == 0) { + SCReturnInt(-1); + } + + while (input_len > 0) { + uint32_t adu_len = input_len; + uint8_t *adu = input; + + /* Extract MODBUS Header */ + if (ModbusParseHeader(modbus, &header, adu, adu_len)) + SCReturnInt(0); + + /* Update ADU length with length in Modbus header. */ + adu_len = (uint32_t) sizeof(ModbusHeader) + (uint32_t) header.length - 1; + if (adu_len > input_len) + SCReturnInt(0); + + /* Find the transaction context thanks to transaction ID (and function code) */ + tx = ModbusTxFindByTransaction(modbus, header.transactionId); + if (tx == NULL) { + /* Allocate a Transaction Context if not previous request */ + /* and add it to Transaction list */ + tx = ModbusTxAlloc(modbus); + if (tx == NULL) + SCReturnInt(0); + + SCLogDebug("MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE"); + ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE); + } else { + /* Store PDU length */ + tx->length = header.length; + + /* Extract MODBUS PDU and fill Transaction Context */ + ModbusParseResponsePDU(tx, modbus, adu, adu_len); + } + + /* Check and store MODBUS Header */ + ModbusCheckHeader(modbus, &header); + + /* Mark as completed */ + tx->replied = 1; + + /* Update input line and remaining input length of the command */ + input += adu_len; + input_len -= adu_len; + } + + SCReturnInt(1); +} + +/** \internal + * \brief Function to allocate the Modbus state memory + */ +static void *ModbusStateAlloc(void) +{ + ModbusState *modbus; + + modbus = (ModbusState *) SCCalloc(1, sizeof(ModbusState)); + if (unlikely(modbus == NULL)) + return NULL; + + TAILQ_INIT(&modbus->tx_list); + + return (void *) modbus; +} + +/** \internal + * \brief Function to free the Modbus state memory + */ +static void ModbusStateFree(void *state) +{ + SCEnter(); + ModbusState *modbus = (ModbusState *) state; + ModbusTransaction *tx = NULL, *ttx; + + if (state) { + TAILQ_FOREACH_SAFE(tx, &modbus->tx_list, next, ttx) { + ModbusTxFree(tx); + } + + SCFree(state); + } + SCReturn; +} + +static uint16_t ModbusProbingParser(uint8_t *input, + uint32_t input_len, + uint32_t *offset) +{ + ModbusHeader *header = (ModbusHeader *) input; + + /* Modbus header is 7 bytes long */ + if (input_len < sizeof(ModbusHeader)) + return ALPROTO_UNKNOWN; + + /* MODBUS protocol is identified by the value 0. */ + if (header->protocolId != 0) + return ALPROTO_FAILED; + + return ALPROTO_MODBUS; +} + +DetectEngineState *ModbusGetTxDetectState(void *vtx) +{ + ModbusTransaction *tx = (ModbusTransaction *)vtx; + return tx->de_state; +} + +int ModbusSetTxDetectState(void *state, void *vtx, DetectEngineState *s) +{ + ModbusTransaction *tx = (ModbusTransaction *)vtx; + tx->de_state = s; + return 0; +} + +/** + * \brief Function to register the Modbus protocol parsers and other functions + */ +void RegisterModbusParsers(void) +{ + SCEnter(); + char *proto_name = "modbus"; + + /* Modbus application protocol V1.1b3 */ + if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) { + AppLayerProtoDetectRegisterProtocol(ALPROTO_MODBUS, proto_name); + + if (RunmodeIsUnittests()) { + AppLayerProtoDetectPPRegister(IPPROTO_TCP, + "502", + ALPROTO_MODBUS, + 0, sizeof(ModbusHeader), + STREAM_TOSERVER, + ModbusProbingParser); + } else { + /* if we have no config, we enable the default port 502 */ + if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP, + proto_name, ALPROTO_MODBUS, + 0, sizeof(ModbusHeader), + ModbusProbingParser)) { + SCLogWarning(SC_ERR_MODBUS_CONFIG, "no Modbus TCP config found, " + "enabling Modbus detection on " + "port 502."); + + AppLayerProtoDetectPPRegister(IPPROTO_TCP, + "502", + ALPROTO_MODBUS, + 0, sizeof(ModbusHeader), + STREAM_TOSERVER, + ModbusProbingParser); + } + } + + ConfNode *p = ConfGetNode("app-layer.protocols.modbus.request-flood"); + if (p != NULL) { + uint32_t value; + if (ParseSizeStringU32(p->val, &value) < 0) { + SCLogError(SC_ERR_MODBUS_CONFIG, "invalid value for request-flood %s", p->val); + } else { + request_flood = value; + } + } + SCLogInfo("Modbus request flood protection level: %u", request_flood); + } else { + SCLogInfo("Protocol detection and parser disabled for %s protocol.", proto_name); + return; + } + + if (AppLayerParserConfParserEnabled("tcp", proto_name)) { + AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOSERVER, ModbusParseRequest); + AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOCLIENT, ModbusParseResponse); + AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateAlloc, ModbusStateFree); + + AppLayerParserRegisterGetEventsFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetEvents); + AppLayerParserRegisterHasEventsFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusHasEvents); + AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_MODBUS, NULL, + ModbusGetTxDetectState, ModbusSetTxDetectState); + + AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTx); + AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTxCnt); + AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateTxFree); + + AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetAlstateProgress); + AppLayerParserRegisterGetStateProgressCompletionStatus(IPPROTO_TCP, ALPROTO_MODBUS, + ModbusGetAlstateProgressCompletionStatus); + + AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateGetEventInfo); + + AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOSERVER); + } else { + SCLogInfo("Parsed disabled for %s protocol. Protocol detection" "still on.", proto_name); + } +#ifdef UNITTESTS + AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_MODBUS, ModbusParserRegisterTests); +#endif + + SCReturn; +} + +/* UNITTESTS */ +#ifdef UNITTESTS +#include "detect.h" +#include "detect-engine.h" +#include "detect-parse.h" + +#include "flow-util.h" + +#include "util-unittest.h" +#include "util-unittest-helper.h" + +#include "stream-tcp.h" +#include "stream-tcp-private.h" + +/* Modbus Application Protocol Specification V1.1b3 6.1: Read Coils */ +/* Example of a request to read discrete outputs 20-38 */ +static uint8_t readCoilsReq[] = {/* Transaction ID */ 0x00, 0x00, + /* Protocol ID */ 0x00, 0x00, + /* Length */ 0x00, 0x06, + /* Unit ID */ 0x00, + /* Function code */ 0x01, + /* Starting Address */ 0x78, 0x90, + /* Quantity of coils */ 0x00, 0x13 }; + +static uint8_t readCoilsRsp[] = {/* Transaction ID */ 0x00, 0x00, + /* Protocol ID */ 0x00, 0x00, + /* Length */ 0x00, 0x06, + /* Unit ID */ 0x00, + /* Function code */ 0x01, + /* Byte count */ 0x03, + /* Coil Status */ 0xCD, 0x6B, 0x05 }; + +static uint8_t readCoilsErrorRsp[] = {/* Transaction ID */ 0x00, 0x00, + /* Protocol ID */ 0x00, 0x00, + /* Length */ 0x00, 0x03, + /* Unit ID */ 0x00, + /* Function code */ 0x81, + /* Exception code */ 0x05}; + +/* Modbus Application Protocol Specification V1.1b3 6.12: Write Multiple registers */ +/* Example of a request to write two registers starting at 2 to 00 0A and 01 02 hex */ +static uint8_t writeMultipleRegistersReq[] = {/* Transaction ID */ 0x00, 0x0A, + /* Protocol ID */ 0x00, 0x00, + /* Length */ 0x00, 0x0B, + /* Unit ID */ 0x00, + /* Function code */ 0x10, + /* Starting Address */ 0x00, 0x01, + /* Quantity of Registers */ 0x00, 0x02, + /* Byte count */ 0x04, + /* Registers Value */ 0x00, 0x0A, + 0x01, 0x02}; + +static uint8_t writeMultipleRegistersRsp[] = {/* Transaction ID */ 0x00, 0x0A, + /* Protocol ID */ 0x00, 0x00, + /* Length */ 0x00, 0x06, + /* Unit ID */ 0x00, + /* Function code */ 0x10, + /* Starting Address */ 0x00, 0x01, + /* Quantity of Registers */ 0x00, 0x02}; + +/* Modbus Application Protocol Specification V1.1b3 6.17: Read/Write Multiple registers */ +/* Example of a request to read six registers starting at register 4, */ +/* and to write three registers starting at register 15 */ +static uint8_t readWriteMultipleRegistersReq[] = {/* Transaction ID */ 0x12, 0x34, + /* Protocol ID */ 0x00, 0x00, + /* Length */ 0x00, 0x11, + /* Unit ID */ 0x00, + /* Function code */ 0x17, + /* Read Starting Address */ 0x00, 0x03, + /* Quantity to Read */ 0x00, 0x06, + /* Write Starting Address */ 0x00, 0x0E, + /* Quantity to Write */ 0x00, 0x03, + /* Write Byte count */ 0x06, + /* Write Registers Value */ 0x12, 0x34, + 0x56, 0x78, + 0x9A, 0xBC}; + +/* Mismatch value in Byte count 0x0B instead of 0x0C */ +static uint8_t readWriteMultipleRegistersRsp[] = {/* Transaction ID */ 0x12, 0x34, + /* Protocol ID */ 0x00, 0x00, + /* Length */ 0x00, 0x0E, + /* Unit ID */ 0x00, + /* Function code */ 0x17, + /* Byte count */ 0x0B, + /* Read Registers Value */ 0x00, 0xFE, + 0x0A, 0xCD, + 0x00, 0x01, + 0x00, 0x03, + 0x00, 0x0D, + 0x00}; + +/* Modbus Application Protocol Specification V1.1b3 6.8.1: 04 Force Listen Only Mode */ +/* Example of a request to to remote device to its Listen Only MOde for Modbus Communications. */ +static uint8_t forceListenOnlyMode[] = {/* Transaction ID */ 0x0A, 0x00, + /* Protocol ID */ 0x00, 0x00, + /* Length */ 0x00, 0x06, + /* Unit ID */ 0x00, + /* Function code */ 0x08, + /* Sub-function code */ 0x00, 0x04, + /* Data */ 0x00, 0x00}; + +static uint8_t invalidProtocolIdReq[] = {/* Transaction ID */ 0x00, 0x00, + /* Protocol ID */ 0x00, 0x01, + /* Length */ 0x00, 0x06, + /* Unit ID */ 0x00, + /* Function code */ 0x01, + /* Starting Address */ 0x78, 0x90, + /* Quantity of coils */ 0x00, 0x13 }; + +static uint8_t invalidLengthWriteMultipleRegistersReq[] = { + /* Transaction ID */ 0x00, 0x0A, + /* Protocol ID */ 0x00, 0x00, + /* Length */ 0x00, 0x09, + /* Unit ID */ 0x00, + /* Function code */ 0x10, + /* Starting Address */ 0x00, 0x01, + /* Quantity of Registers */ 0x00, 0x02, + /* Byte count */ 0x04, + /* Registers Value */ 0x00, 0x0A, + 0x01, 0x02}; + +static uint8_t exceededLengthWriteMultipleRegistersReq[] = { + /* Transaction ID */ 0x00, 0x0A, + /* Protocol ID */ 0x00, 0x00, + /* Length */ 0xff, 0xfa, + /* Unit ID */ 0x00, + /* Function code */ 0x10, + /* Starting Address */ 0x00, 0x01, + /* Quantity of Registers */ 0x7f, 0xf9, + /* Byte count */ 0xff}; + +static uint8_t invalidLengthPDUWriteMultipleRegistersReq[] = { + /* Transaction ID */ 0x00, 0x0A, + /* Protocol ID */ 0x00, 0x00, + /* Length */ 0x00, 0x02, + /* Unit ID */ 0x00, + /* Function code */ 0x10}; + +/** \test Send Modbus Read Coils request/response. */ +static int ModbusParserTest01(void) { + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + Flow f; + TcpSession ssn; + + int result = 0; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, + readCoilsReq, sizeof(readCoilsReq)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + ModbusState *modbus_state = f.alstate; + if (modbus_state == NULL) { + printf("no modbus state: "); + goto end; + } + + ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + + if ((tx->function != 1) || (tx->read.address != 0x7890) || (tx->read.quantity != 19)) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 1, tx->function); + printf("expected address %" PRIu8 ", got %" PRIu8 ": ", 0x7890, tx->read.address); + printf("expected quantity %" PRIu8 ", got %" PRIu8 ": ", 19, tx->read.quantity); + goto end; + } + + SCMutexLock(&f.m); + r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT, + readCoilsRsp, sizeof(readCoilsRsp)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + if (modbus_state->transaction_max !=1) { + printf("expected transaction_max %" PRIu8 ", got %" PRIu64 ": ", 1, modbus_state->transaction_max); + goto end; + } + + result = 1; +end: + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + return result; +} + +/** \test Send Modbus Write Multiple registers request/response. */ +static int ModbusParserTest02(void) { + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + Flow f; + TcpSession ssn; + + int result = 0; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, + writeMultipleRegistersReq, sizeof(writeMultipleRegistersReq)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + ModbusState *modbus_state = f.alstate; + if (modbus_state == NULL) { + printf("no modbus state: "); + goto end; + } + + ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + + if ((tx->function != 16) || (tx->write.address != 0x01) || (tx->write.quantity != 2) || + (tx->write.count != 4) || (tx->data[0] != 0x000A) || (tx->data[1] != 0x0102)) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 16, tx->function); + printf("expected write address %" PRIu8 ", got %" PRIu8 ": ", 0x01, tx->write.address); + printf("expected write quantity %" PRIu8 ", got %" PRIu8 ": ", 2, tx->write.quantity); + printf("expected write count %" PRIu8 ", got %" PRIu8 ": ", 4, tx->write.count); + printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x000A, tx->data[0]); + printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x0102, tx->data[1]); + goto end; + } + + SCMutexLock(&f.m); + r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT, + writeMultipleRegistersRsp, sizeof(writeMultipleRegistersRsp)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + if (modbus_state->transaction_max !=1) { + printf("expected transaction_max %" PRIu8 ", got %" PRIu64 ": ", 1, modbus_state->transaction_max); + goto end; + } + + result = 1; +end: + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + return result; +} + +/** \test Send Modbus Read/Write Multiple registers request/response with mismatch value. */ +static int ModbusParserTest03(void) { + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + DetectEngineThreadCtx *det_ctx = NULL; + Flow f; + Packet *p = NULL; + Signature *s = NULL; + TcpSession ssn; + ThreadVars tv; + + int result = 0; + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&f, 0, sizeof(Flow)); + memset(&ssn, 0, sizeof(TcpSession)); + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + + FLOW_INITIALIZE(&f); + f.alproto = ALPROTO_MODBUS; + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + f.flags |= FLOW_IPV4; + + p->flow = &f; + p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST; + p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED; + + StreamTcpInitConfig(TRUE); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any " + "(msg:\"Modbus Data mismatch\"; " + "app-layer-event: " + "modbus.value_mismatch; " + "sid:1;)"); + if (s == NULL) + goto end; + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, + readWriteMultipleRegistersReq, sizeof(readWriteMultipleRegistersReq)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + ModbusState *modbus_state = f.alstate; + if (modbus_state == NULL) { + printf("no modbus state: "); + goto end; + } + + ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + + if ((tx->function != 23) || (tx->read.address != 0x03) || (tx->read.quantity != 6) || + (tx->write.address != 0x0E) || (tx->write.quantity != 3) || (tx->write.count != 6) || + (tx->data[0] != 0x1234) || (tx->data[1] != 0x5678) || (tx->data[2] != 0x9ABC)) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 23, tx->function); + printf("expected read address %" PRIu8 ", got %" PRIu8 ": ", 0x03, tx->read.address); + printf("expected read quantity %" PRIu8 ", got %" PRIu8 ": ", 6, tx->read.quantity); + printf("expected write address %" PRIu8 ", got %" PRIu8 ": ", 0x0E, tx->write.address); + printf("expected write quantity %" PRIu8 ", got %" PRIu8 ": ", 3, tx->write.quantity); + printf("expected write count %" PRIu8 ", got %" PRIu8 ": ", 6, tx->write.count); + printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x1234, tx->data[0]); + printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x5678, tx->data[1]); + printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x9ABC, tx->data[2]); + goto end; + } + + SCMutexLock(&f.m); + r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT, + readWriteMultipleRegistersRsp, sizeof(readWriteMultipleRegistersRsp)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + if (modbus_state->transaction_max !=1) { + printf("expected transaction_max %" PRIu8 ", got %" PRIu64 ": ", 1, modbus_state->transaction_max); + goto end; + } + + /* do detect */ + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + + if (!PacketAlertCheck(p, 1)) { + printf("sid 1 didn't match. Should have matched: "); + goto end; + } + + result = 1; +end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + UTHFreePackets(&p, 1); + return result; +} + +/** \test Send Modbus Force Listen Only Mode request. */ +static int ModbusParserTest04(void) { + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + Flow f; + TcpSession ssn; + + int result = 0; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, + forceListenOnlyMode, sizeof(forceListenOnlyMode)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + ModbusState *modbus_state = f.alstate; + if (modbus_state == NULL) { + printf("no modbus state: "); + goto end; + } + + ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + + if ((tx->function != 8) || (tx->subFunction != 4)) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 8, tx->function); + printf("expected sub-function %" PRIu8 ", got %" PRIu8 ": ", 0x04, tx->subFunction); + goto end; + } + + result = 1; +end: + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + return result; +} + +/** \test Send Modbus invalid Protocol version in request. */ +static int ModbusParserTest05(void) { + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + DetectEngineThreadCtx *det_ctx = NULL; + Flow f; + Packet *p = NULL; + Signature *s = NULL; + TcpSession ssn; + ThreadVars tv; + + int result = 0; + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&f, 0, sizeof(Flow)); + memset(&ssn, 0, sizeof(TcpSession)); + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + + FLOW_INITIALIZE(&f); + f.alproto = ALPROTO_MODBUS; + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + f.flags |= FLOW_IPV4; + + p->flow = &f; + p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST; + p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED; + + StreamTcpInitConfig(TRUE); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any " + "(msg:\"Modbus invalid Protocol version\"; " + "app-layer-event: " + "modbus.invalid_protocol_id; " + "sid:1;)"); + if (s == NULL) + goto end; + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, + invalidProtocolIdReq, sizeof(invalidProtocolIdReq)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + ModbusState *modbus_state = f.alstate; + if (modbus_state == NULL) { + printf("no modbus state: "); + goto end; + } + + /* do detect */ + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + + if (!PacketAlertCheck(p, 1)) { + printf("sid 1 didn't match. Should have matched: "); + goto end; + } + + result = 1; +end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + UTHFreePackets(&p, 1); + return result; +} + +/** \test Send Modbus unsolicited response. */ +static int ModbusParserTest06(void) { + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + DetectEngineThreadCtx *det_ctx = NULL; + Flow f; + Packet *p = NULL; + Signature *s = NULL; + TcpSession ssn; + ThreadVars tv; + + int result = 0; + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&f, 0, sizeof(Flow)); + memset(&ssn, 0, sizeof(TcpSession)); + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + + FLOW_INITIALIZE(&f); + f.alproto = ALPROTO_MODBUS; + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + f.flags |= FLOW_IPV4; + + p->flow = &f; + p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST; + p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED; + + StreamTcpInitConfig(TRUE); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any " + "(msg:\"Modbus unsolicited response\"; " + "app-layer-event: " + "modbus.unsolicited_response; " + "sid:1;)"); + if (s == NULL) + goto end; + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT, + readCoilsRsp, sizeof(readCoilsRsp)); + if (r != 0) { + printf("toclient chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + ModbusState *modbus_state = f.alstate; + if (modbus_state == NULL) { + printf("no modbus state: "); + goto end; + } + + /* do detect */ + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + + if (!PacketAlertCheck(p, 1)) { + printf("sid 1 didn't match. Should have matched: "); + goto end; + } + + result = 1; +end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + UTHFreePackets(&p, 1); + return result; +} + +/** \test Send Modbus invalid Length request. */ +static int ModbusParserTest07(void) { + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + DetectEngineThreadCtx *det_ctx = NULL; + Flow f; + Packet *p = NULL; + Signature *s = NULL; + TcpSession ssn; + ThreadVars tv; + + int result = 0; + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&f, 0, sizeof(Flow)); + memset(&ssn, 0, sizeof(TcpSession)); + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + + FLOW_INITIALIZE(&f); + f.alproto = ALPROTO_MODBUS; + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + f.flags |= FLOW_IPV4; + + p->flow = &f; + p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST; + p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED; + + StreamTcpInitConfig(TRUE); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any " + "(msg:\"Modbus invalid Length\"; " + "app-layer-event: " + "modbus.invalid_length; " + "sid:1;)"); + if (s == NULL) + goto end; + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, + invalidLengthWriteMultipleRegistersReq, + sizeof(invalidLengthWriteMultipleRegistersReq)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + ModbusState *modbus_state = f.alstate; + if (modbus_state == NULL) { + printf("no modbus state: "); + goto end; + } + + /* do detect */ + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + + if (!PacketAlertCheck(p, 1)) { + printf("sid 1 didn't match. Should have matched: "); + goto end; + } + + result = 1; +end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + UTHFreePackets(&p, 1); + return result; +} + +/** \test Send Modbus Read Coils request and error response with Exception code invalid. */ +static int ModbusParserTest08(void) { + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + DetectEngineThreadCtx *det_ctx = NULL; + Flow f; + Packet *p = NULL; + Signature *s = NULL; + TcpSession ssn; + ThreadVars tv; + + int result = 0; + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&f, 0, sizeof(Flow)); + memset(&ssn, 0, sizeof(TcpSession)); + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + + FLOW_INITIALIZE(&f); + f.alproto = ALPROTO_MODBUS; + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + f.flags |= FLOW_IPV4; + + p->flow = &f; + p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST; + p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED; + + StreamTcpInitConfig(TRUE); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any " + "(msg:\"Modbus Exception code invalid\"; " + "app-layer-event: " + "modbus.invalid_exception_code; " + "sid:1;)"); + if (s == NULL) + goto end; + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, + readCoilsReq, sizeof(readCoilsReq)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + ModbusState *modbus_state = f.alstate; + if (modbus_state == NULL) { + printf("no modbus state: "); + goto end; + } + + ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + + if ((tx->function != 1) || (tx->read.address != 0x7890) || (tx->read.quantity != 19)) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 1, tx->function); + printf("expected address %" PRIu8 ", got %" PRIu8 ": ", 0x7890, tx->read.address); + printf("expected quantity %" PRIu8 ", got %" PRIu8 ": ", 19, tx->read.quantity); + goto end; + } + + SCMutexLock(&f.m); + r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT, + readCoilsErrorRsp, sizeof(readCoilsErrorRsp)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + if (modbus_state->transaction_max !=1) { + printf("expected transaction_max %" PRIu8 ", got %" PRIu64 ": ", 1, modbus_state->transaction_max); + goto end; + } + + /* do detect */ + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + + if (!PacketAlertCheck(p, 1)) { + printf("sid 1 didn't match. Should have matched: "); + goto end; + } + + result = 1; +end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + UTHFreePackets(&p, 1); + return result; +} + +/** \test Modbus fragmentation - 1 ADU over 2 TCP packets. */ +static int ModbusParserTest09(void) { + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + Flow f; + TcpSession ssn; + + uint32_t input_len = sizeof(readCoilsReq), part2_len = 3; + uint8_t *input = readCoilsReq; + + int result = 0; + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, + input, input_len - part2_len); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + + r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, + input, input_len); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + ModbusState *modbus_state = f.alstate; + if (modbus_state == NULL) { + printf("no modbus state: "); + goto end; + } + + ModbusTransaction *tx = ModbusGetTx(modbus_state, 0); + + if ((tx->function != 1) || (tx->read.address != 0x7890) || (tx->read.quantity != 19)) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 1, tx->function); + printf("expected address %" PRIu8 ", got %" PRIu8 ": ", 0x7890, tx->read.address); + printf("expected quantity %" PRIu8 ", got %" PRIu8 ": ", 19, tx->read.quantity); + goto end; + } + + input_len = sizeof(readCoilsRsp); + part2_len = 10; + input = readCoilsRsp; + + SCMutexLock(&f.m); + r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT, + input, input_len - part2_len); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + + r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT, + input, input_len); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + if (modbus_state->transaction_max !=1) { + printf("expected transaction_max %" PRIu8 ", got %" PRIu64 ": ", 1, modbus_state->transaction_max); + goto end; + } + + result = 1; +end: + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + return result; +} + +/** \test Modbus fragmentation - 2 ADU in 1 TCP packet. */ +static int ModbusParserTest10(void) { + uint32_t input_len = sizeof(readCoilsReq) + sizeof(writeMultipleRegistersReq); + uint8_t *input, *ptr; + + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + Flow f; + TcpSession ssn; + + int result = 0; + + input = (uint8_t *) SCMalloc (input_len * sizeof(uint8_t)); + if (unlikely(input == NULL)) + goto end; + + memcpy(input, readCoilsReq, sizeof(readCoilsReq)); + memcpy(input + sizeof(readCoilsReq), writeMultipleRegistersReq, sizeof(writeMultipleRegistersReq)); + + memset(&f, 0, sizeof(f)); + memset(&ssn, 0, sizeof(ssn)); + + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + + StreamTcpInitConfig(TRUE); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, + input, input_len); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + ModbusState *modbus_state = f.alstate; + if (modbus_state == NULL) { + printf("no modbus state: "); + goto end; + } + + if (modbus_state->transaction_max !=2) { + printf("expected transaction_max %" PRIu8 ", got %" PRIu64 ": ", 2, modbus_state->transaction_max); + goto end; + } + + ModbusTransaction *tx = ModbusGetTx(modbus_state, 1); + + if ((tx->function != 16) || (tx->write.address != 0x01) || (tx->write.quantity != 2) || + (tx->write.count != 4) || (tx->data[0] != 0x000A) || (tx->data[1] != 0x0102)) { + printf("expected function %" PRIu8 ", got %" PRIu8 ": ", 16, tx->function); + printf("expected write address %" PRIu8 ", got %" PRIu8 ": ", 0x01, tx->write.address); + printf("expected write quantity %" PRIu8 ", got %" PRIu8 ": ", 2, tx->write.quantity); + printf("expected write count %" PRIu8 ", got %" PRIu8 ": ", 4, tx->write.count); + printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x000A, tx->data[0]); + printf("expected data %" PRIu8 ", got %" PRIu8 ": ", 0x0102, tx->data[1]); + goto end; + } + + input_len = sizeof(readCoilsRsp) + sizeof(writeMultipleRegistersRsp); + + ptr = (uint8_t *) SCRealloc (input, input_len * sizeof(uint8_t)); + if (unlikely(ptr == NULL)) + goto end; + input = ptr; + + memcpy(input, readCoilsRsp, sizeof(readCoilsRsp)); + memcpy(input + sizeof(readCoilsRsp), writeMultipleRegistersRsp, sizeof(writeMultipleRegistersRsp)); + + SCMutexLock(&f.m); + r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT, + input, sizeof(input_len)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + result = 1; +end: + if (input != NULL) + SCFree(input); + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + return result; +} + +/** \test Send Modbus exceed Length request. */ +static int ModbusParserTest11(void) { + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + DetectEngineThreadCtx *det_ctx = NULL; + Flow f; + Packet *p = NULL; + Signature *s = NULL; + TcpSession ssn; + ThreadVars tv; + + int result = 0; + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&f, 0, sizeof(Flow)); + memset(&ssn, 0, sizeof(TcpSession)); + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + + FLOW_INITIALIZE(&f); + f.alproto = ALPROTO_MODBUS; + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + f.flags |= FLOW_IPV4; + + p->flow = &f; + p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST; + p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED; + + StreamTcpInitConfig(TRUE); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any " + "(msg:\"Modbus invalid Length\"; " + "app-layer-event: " + "modbus.invalid_length; " + "sid:1;)"); + if (s == NULL) + goto end; + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, + exceededLengthWriteMultipleRegistersReq, + sizeof(exceededLengthWriteMultipleRegistersReq) + 65523 /* header.length - 7 */ * sizeof(uint8_t)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + ModbusState *modbus_state = f.alstate; + if (modbus_state == NULL) { + printf("no modbus state: "); + goto end; + } + + /* do detect */ + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + + if (!PacketAlertCheck(p, 1)) { + printf("sid 1 didn't match. Should have matched: "); + goto end; + } + + result = 1; +end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + UTHFreePackets(&p, 1); + return result; +} + +/** \test Send Modbus invalid PDU Length. */ +static int ModbusParserTest12(void) { + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + DetectEngineThreadCtx *det_ctx = NULL; + Flow f; + Packet *p = NULL; + Signature *s = NULL; + TcpSession ssn; + ThreadVars tv; + + int result = 0; + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&f, 0, sizeof(Flow)); + memset(&ssn, 0, sizeof(TcpSession)); + + p = UTHBuildPacket(NULL, 0, IPPROTO_TCP); + + FLOW_INITIALIZE(&f); + f.alproto = ALPROTO_MODBUS; + f.protoctx = (void *)&ssn; + f.proto = IPPROTO_TCP; + f.flags |= FLOW_IPV4; + + p->flow = &f; + p->flags |= PKT_HAS_FLOW | PKT_STREAM_EST; + p->flowflags |= FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED; + + StreamTcpInitConfig(TRUE); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) + goto end; + + de_ctx->flags |= DE_QUIET; + s = DetectEngineAppendSig(de_ctx, "alert modbus any any -> any any " + "(msg:\"Modbus invalid Length\"; " + "app-layer-event: " + "modbus.invalid_length; " + "sid:1;)"); + if (s == NULL) + goto end; + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + SCMutexLock(&f.m); + int r = AppLayerParserParse(alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER, + invalidLengthPDUWriteMultipleRegistersReq, + sizeof(invalidLengthPDUWriteMultipleRegistersReq)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + SCMutexUnlock(&f.m); + goto end; + } + SCMutexUnlock(&f.m); + + ModbusState *modbus_state = f.alstate; + if (modbus_state == NULL) { + printf("no modbus state: "); + goto end; + } + + /* do detect */ + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + + if (!PacketAlertCheck(p, 1)) { + printf("sid 1 didn't match. Should have matched: "); + goto end; + } + + result = 1; +end: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&tv, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + FLOW_DESTROY(&f); + UTHFreePackets(&p, 1); + return result; +} +#endif /* UNITTESTS */ + +void ModbusParserRegisterTests(void) { +#ifdef UNITTESTS + UtRegisterTest("ModbusParserTest01 - Modbus Read Coils request", ModbusParserTest01, 1); + UtRegisterTest("ModbusParserTest02 - Modbus Write Multiple registers request", ModbusParserTest02, 1); + UtRegisterTest("ModbusParserTest03 - Modbus Read/Write Multiple registers request", ModbusParserTest03, 1); + UtRegisterTest("ModbusParserTest04 - Modbus Force Listen Only Mode request", ModbusParserTest04, 1); + UtRegisterTest("ModbusParserTest05 - Modbus invalid Protocol version", ModbusParserTest05, 1); + UtRegisterTest("ModbusParserTest06 - Modbus unsolicited response", ModbusParserTest06, 1); + UtRegisterTest("ModbusParserTest07 - Modbus invalid Length request", ModbusParserTest07, 1); + UtRegisterTest("ModbusParserTest08 - Modbus Exception code invalid", ModbusParserTest08, 1); + UtRegisterTest("ModbusParserTest09 - Modbus fragmentation - 1 ADU in 2 TCP packets", ModbusParserTest09, 1); + UtRegisterTest("ModbusParserTest10 - Modbus fragmentation - 2 ADU in 1 TCP packet", ModbusParserTest10, 1); + UtRegisterTest("ModbusParserTest11 - Modbus exceeded Length request", ModbusParserTest11, 1); + UtRegisterTest("ModbusParserTest12 - Modbus invalid PDU Length", ModbusParserTest12, 1); +#endif /* UNITTESTS */ +} |