]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
App-layer: Add Modbus protocol parser
authorDIALLO David <diallo@et.esiea.fr>
Wed, 23 Jul 2014 09:12:59 +0000 (11:12 +0200)
committerVictor Julien <victor@inliniac.net>
Thu, 6 Nov 2014 10:56:38 +0000 (11:56 +0100)
Decode Modbus request and response messages, and extracts
MODBUS Application Protocol header and the code function.

In case of read/write function, extracts message contents
(read/write address, quantity, count, data to write).

Links request and response messages in a transaction according to
Transaction Identifier (transaction management based on DNS source code).

MODBUS Messaging on TCP/IP Implementation Guide V1.0b
(http://www.modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf)
MODBUS Application Protocol Specification V1.1b3
(http://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf)

Based on DNS source code.

Signed-off-by: David DIALLO <diallo@et.esia.fr>
Makefile.am
rules/Makefile.am
rules/modbus-events.rules [new file with mode: 0644]
src/Makefile.am
src/app-layer-modbus.c [new file with mode: 0644]
src/app-layer-modbus.h [new file with mode: 0644]
src/app-layer-parser.c
src/app-layer-protos.h
src/util-error.c
src/util-error.h
suricata.yaml.in

index 5e534ee07bc798eaee51aa85223b0514a141b216..d6c72ef211537cd0ce6a660f36be26cdc34c5d51 100644 (file)
@@ -41,6 +41,7 @@ endif
        @test -e "$(e_sysconfrulesdir)smtp-events.rules" || install -m 600 "$(top_srcdir)/rules/smtp-events.rules" "$(e_sysconfrulesdir)"
        @test -e "$(e_sysconfrulesdir)http-events.rules" || install -m 600 "$(top_srcdir)/rules/http-events.rules" "$(e_sysconfrulesdir)"
        @test -e "$(e_sysconfrulesdir)dns-events.rules" || install -m 600 "$(top_srcdir)/rules/dns-events.rules" "$(e_sysconfrulesdir)"
+       @test -e "$(e_sysconfrulesdir)modbus-events.rules" || install -m 600 "$(top_srcdir)/rules/modbus-events.rules" "$(e_sysconfrulesdir)"
        @echo ""
        @echo "You can now start suricata by running as root something like '$(bindir)/suricata -c $(e_sysconfdir)/suricata.yaml -i eth0'."
        @echo ""
index bb11b919f1866629ec6652f18ebe1e67f8611224..cd3404336817856e10411b8062b7c3df632b6e86 100644 (file)
@@ -5,4 +5,5 @@ smtp-events.rules \
 http-events.rules \
 dns-events.rules \
 tls-events.rules \
+modbus-events.rules \
 files.rules
diff --git a/rules/modbus-events.rules b/rules/modbus-events.rules
new file mode 100644 (file)
index 0000000..f879971
--- /dev/null
@@ -0,0 +1,18 @@
+# Modbus Protocol version field is incorrect (Modbus version = 0)
+alert modbus any any -> any any (msg:"SURICATA Modbus invalid Protocol version"; app-layer-event:modbus.invalid_protocol_id; sid:2250001; rev:1;)
+# Response (answer) we didn't see a Request for. Could be packet loss.
+alert modbus any any -> any any (msg:"SURICATA Modbus unsolicited response"; app-layer-event:modbus.unsolicited_response; sid:2250002; rev:1;)
+# Malformed request or response. Malformed means length field is wrong
+alert modbus any any -> any any (msg:"SURICATA Modbus invalid Length"; app-layer-event:modbus.invalid_length; sid:2250003; rev:1;)
+# Unit identifier field is incorrect
+alert modbus any any -> any any (msg:"SURICATA Modbus invalid Unit Identifier"; app-layer-event:modbus.invalid_unit_identifier; sid:2250004; rev:1;)
+# Modbus Function code is incorrect
+alert modbus any any -> any any (msg:"SURICATA Modbus invalid Function code"; app-layer-event:modbus.invalid_function_code; sid:2250005; rev:1;)
+# Modbus Request/Response value field is incorrect
+alert modbus any any -> any any (msg:"SURICATA Modbus invalid Value"; app-layer-event:modbus.invalid_value; sid:2250006; rev:1;)
+# Modbus Expception code is incorrect
+alert modbus any any -> any any (msg:"SURICATA Modbus Exception code invalid"; flow:to_client; app-layer-event:modbus.invalid_exception_code; sid:2250007; rev:1;)
+# Value field in Modbus Response does not match with Modbus Request
+alert modbus any any -> any any (msg:"SURICATA Modbus Data mismatch"; flow:to_client; app-layer-event:modbus.value_mismatch; sid:2250008; rev:1;)
+# Request Flood Detected
+alert modbus any any -> any any (msg:"SURICATA Modbus Request flood detected"; flow:to_server; app-layer-event:modbus.flooded; sid:2250009; rev:1;)
index 938c6acfa45c4b1fbff2c2438dc95bb57b4d0087..5b2b3cc88769d6cc141c0e349792a7f063d85bbd 100644 (file)
@@ -26,6 +26,7 @@ app-layer-htp.c app-layer-htp.h \
 app-layer-htp-file.c app-layer-htp-file.h \
 app-layer-htp-libhtp.c app-layer-htp-libhtp.h \
 app-layer-htp-mem.c app-layer-htp-mem.h \
+app-layer-modbus.c app-layer-modbus.h \
 app-layer-parser.c app-layer-parser.h \
 app-layer-protos.c app-layer-protos.h \
 app-layer-smb2.c app-layer-smb2.h \
diff --git a/src/app-layer-modbus.c b/src/app-layer-modbus.c
new file mode 100644 (file)
index 0000000..7d23bca
--- /dev/null
@@ -0,0 +1,2401 @@
+/*
+ * 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);
+
+    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  offset  Offset of the received input data pointer
+ */
+static void ModbusExtractUint8(uint8_t  *res,
+                               uint8_t  *input,
+                               uint16_t *offset) {
+    SCEnter();
+    *res     = *(input + *offset);
+    *offset += sizeof(uint8_t);
+    SCReturn;
+}
+
+/** \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  offset  Offset of the received input data pointer
+ */
+static void ModbusExtractUint16(uint16_t    *res,
+                                uint8_t     *input,
+                                uint16_t    *offset) {
+    SCEnter();
+    ByteExtractUint16(res, BYTE_BIG_ENDIAN, sizeof(uint16_t), (const uint8_t *) (input + *offset));
+    *offset += sizeof(uint16_t);
+    SCReturn;
+}
+
+/** \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->length > MODBUS_MIN_INVALID_UNIT_ID)   &&
+        (header->length < 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  offset  Offset of the received input data pointer
+ */
+static void ModbusExceptionResponse(ModbusTransaction   *tx,
+                                    ModbusState         *modbus,
+                                    uint8_t             *input,
+                                    uint16_t            *offset)
+{
+    SCEnter();
+    uint8_t exception;
+
+    /* Exception code (1 byte) */
+    ModbusExtractUint8(&exception, input, offset);
+
+    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  offset  Offset of the received input data pointer
+ */
+static void ModbusParseReadRequest(ModbusTransaction   *tx,
+                                   ModbusState         *modbus,
+                                   uint8_t             *input,
+                                   uint16_t            *offset)
+{
+    SCEnter();
+    uint16_t    quantity;
+    uint8_t     type = tx->type;
+
+    /* Starting Address (2 bytes) */
+    ModbusExtractUint16(&(tx->read.address), input, offset);
+
+    /* Quantity (2 bytes) */
+    ModbusExtractUint16(&(tx->read.quantity), input, offset);
+    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  offset  Offset of the received input data pointer
+ */
+static void ModbusParseReadResponse(ModbusTransaction   *tx,
+                                    ModbusState         *modbus,
+                                    uint8_t             *input,
+                                    uint16_t            *offset)
+{
+    SCEnter();
+    uint8_t count;
+
+    /* Count (1 bytes) */
+    ModbusExtractUint8(&count, input, offset);
+
+    /* 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  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,
+                                   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) */
+    ModbusExtractUint16(&(tx->write.address), input, offset);
+
+    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) */
+        ModbusExtractUint16(&quantity, input, offset);
+        tx->write.quantity = quantity;
+
+        /* Count (1 bytes) */
+        ModbusExtractUint8(&count, input, offset);
+        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) */
+            ModbusExtractUint16(&word, input, offset);
+            tx->data[i] = word;
+
+            if ((word != 0x00) && (word != 0xFF00))
+                goto error;
+        } else {
+            for (i = 0; i < count; i++) {
+                /* Outputs value (1 byte) */
+                ModbusExtractUint8(&byte, input, offset);
+                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) */
+            ModbusExtractUint16(&word, input, offset);
+            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  offset  Offset of the received input data pointer
+ */
+static void ModbusParseWriteResponse(ModbusTransaction   *tx,
+                                     ModbusState         *modbus,
+                                     uint8_t             *input,
+                                     uint16_t            *offset)
+{
+    SCEnter();
+    uint16_t    address, quantity, word;
+    uint8_t     type = tx->type;
+
+    /* Starting Address (2 bytes) */
+    ModbusExtractUint16(&address, input, offset);
+
+    if (address != tx->write.address)
+        goto error;
+
+    if (type & MODBUS_TYP_SINGLE) {
+        /* Outputs/Registers value (2 bytes) */
+        ModbusExtractUint16(&word, input, offset);
+
+        /* Check with Outputs/Registers from request */
+        if (word != tx->data[0])
+            goto error;
+    } else if (type & MODBUS_TYP_MULTIPLE) {
+        /* Quantity (2 bytes) */
+        ModbusExtractUint16(&quantity, input, offset);
+
+        /* 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) */
+        ModbusExtractUint16(&word, input, offset);
+
+        /* Check And_Mask value according to the request */
+        if (word != tx->data[0])
+            goto error;
+
+        /* And_Or_Mask value (2 bytes) */
+        ModbusExtractUint16(&word, input, 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  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,
+                                        uint16_t            *offset)
+{
+    SCEnter();
+    uint16_t data;
+
+    /* Sub-function (2 bytes) */
+    ModbusExtractUint16(&(tx->subFunction), input, offset);
+
+    /* Data (2 bytes) */
+    ModbusExtractUint16(&data, input, offset);
+
+    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  offset      Offset of the received input data pointer
+ */
+static void ModbusParseRequestPDU(ModbusTransaction *tx,
+                                  ModbusState       *modbus,
+                                  uint8_t           *input,
+                                  uint16_t          *offset)
+{
+    SCEnter();
+    uint8_t count;
+
+    int i = 0;
+
+    /* Standard function codes used on MODBUS application layer protocol (1 byte) */
+    ModbusExtractUint8(&(tx->function), input, offset);
+
+    /* 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) */
+            ModbusExtractUint8(&count, input, offset);
+
+            /* 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, 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) */
+            ModbusExtractUint8(&(tx->mei), input, offset);
+
+            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, offset);
+
+    if (tx->type & MODBUS_TYP_WRITE)
+        ModbusParseWriteRequest(tx, modbus, input, 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  offset  Offset of the received input data pointer
+ */
+static void ModbusParseResponsePDU(ModbusTransaction    *tx,
+                                   ModbusState          *modbus,
+                                   uint8_t              *input,
+                                   uint16_t             *offset)
+{
+    SCEnter();
+    uint8_t count, error = FALSE, function, mei;
+
+    /* Standard function codes used on MODBUS application layer protocol (1 byte) */
+    ModbusExtractUint8(&function, input, offset);
+
+    /* 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, 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) */
+                    ModbusExtractUint8(&count, input, offset);
+
+                    /* 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) */
+                    ModbusExtractUint8(&mei, input, offset);
+
+                    if (mei != tx->mei)
+                        ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH);
+                    goto end;
+            }
+
+            if (tx->type & MODBUS_TYP_READ)
+                ModbusParseReadResponse(tx, modbus, input, offset);
+            /* Read/Write response contents none write response part */
+            else if (tx->type & MODBUS_TYP_WRITE)
+                ModbusParseWriteResponse(tx, modbus, input, 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
+ *  \param  offset  Offset of the received input data pointer
+ */
+static void ModbusParseHeader(ModbusHeader  *header,
+                              uint8_t       *input,
+                              uint16_t      *offset)
+{
+    SCEnter();
+    /* Transaction Identifier (2 bytes) */
+    ModbusExtractUint16(&(header->transactionId), input, offset);
+
+    /* Protocol Identifier (2 bytes) */
+    ModbusExtractUint16(&(header->protocolId), input, offset);
+
+    /* Length (2 bytes) */
+    ModbusExtractUint16(&(header->length), input, offset);
+
+    /* Unit Identifier (1 byte) */
+    ModbusExtractUint8(&(header->unitId), input, offset);
+
+    SCReturn;
+}
+
+/** \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;
+
+    while (input_len > 0) {
+        uint16_t    adu_len, offset = 0;
+        uint8_t     *ptr = input;
+
+        /* Modbus header is 7 bytes long */
+        if (input_len < sizeof(ModbusHeader))
+            SCReturnInt(0);
+
+        /* Extract MODBUS Header */
+        ModbusParseHeader(&header, ptr, &offset);
+
+        /* Compute ADU length. */
+        adu_len = sizeof(ModbusHeader) + 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, ptr, &offset);
+
+        /* 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;
+
+    while (input_len > 0) {
+        uint16_t    adu_len, offset = 0;
+        uint8_t     *ptr = input;
+
+        /* Modbus header is 7 bytes long */
+        if (input_len < sizeof(ModbusHeader))
+            SCReturnInt(0);
+
+        /* Extract MODBUS Header */
+        ModbusParseHeader(&header, ptr, &offset);
+
+        /* Compute ADU length */
+        adu_len = sizeof(ModbusHeader) + 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, ptr, &offset);
+        }
+
+        /* 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;
+}
+
+/**
+ * \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);
+
+        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};
+
+/** \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;
+}
+#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);
+#endif /* UNITTESTS */
+}
diff --git a/src/app-layer-modbus.h b/src/app-layer-modbus.h
new file mode 100644 (file)
index 0000000..fba43db
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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>
+ */
+
+#ifndef __APP_LAYER_MODBUS_H__
+#define __APP_LAYER_MODBUS_H__
+
+#include "decode.h"
+
+#include "queue.h"
+
+/* Modbus Application Data Unit (ADU)
+ * and Protocol Data Unit (PDU) messages */
+enum {
+    MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID,
+    MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE,
+    MODBUS_DECODER_EVENT_INVALID_LENGTH,
+    MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER,
+    MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE,
+    MODBUS_DECODER_EVENT_INVALID_VALUE,
+    MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE,
+    MODBUS_DECODER_EVENT_VALUE_MISMATCH,
+    MODBUS_DECODER_EVENT_FLOODED,
+};
+
+/* Modbus Function Code Categories. */
+#define MODBUS_CAT_NONE                 0x0
+#define MODBUS_CAT_PUBLIC_ASSIGNED      (1<<0)
+#define MODBUS_CAT_PUBLIC_UNASSIGNED    (1<<1)
+#define MODBUS_CAT_USER_DEFINED         (1<<2)
+#define MODBUS_CAT_RESERVED             (1<<3)
+#define MODBUS_CAT_ALL                  0xFF
+
+/* Modbus Read/Write function and Access Types. */
+#define MODBUS_TYP_NONE                 0x0
+#define MODBUS_TYP_ACCESS_MASK          0x03
+#define MODBUS_TYP_READ                 (1<<0)
+#define MODBUS_TYP_WRITE                (1<<1)
+#define MODBUS_TYP_ACCESS_FUNCTION_MASK 0x3C
+#define MODBUS_TYP_BIT_ACCESS_MASK      0x0C
+#define MODBUS_TYP_DISCRETES            (1<<2)
+#define MODBUS_TYP_COILS                (1<<3)
+#define MODBUS_TYP_WORD_ACCESS_MASK     0x30
+#define MODBUS_TYP_INPUT                (1<<4)
+#define MODBUS_TYP_HOLDING              (1<<5)
+#define MODBUS_TYP_SINGLE               (1<<6)
+#define MODBUS_TYP_MULTIPLE             (1<<7)
+#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)
+
+/* Modbus Transaction Structure, request/response. */
+typedef struct ModbusTransaction_ {
+    struct ModbusState_ *modbus;
+
+    uint64_t    tx_num;         /**< internal: id */
+    uint16_t    transactionId;
+    uint16_t    length;
+    uint8_t     function;
+    uint8_t     category;
+    uint8_t     type;
+    union {
+        uint16_t    subFunction;
+        uint8_t     mei;
+        struct {
+            struct {
+                uint16_t    address;
+                uint16_t    quantity;
+            } read;
+            struct {
+                uint16_t    address;
+                uint16_t    quantity;
+                uint8_t     count;
+            } write;
+        };
+    };
+    uint16_t    *data;  /**< to store data to write, bit is converted in 16bits. */
+
+    AppLayerDecoderEvents *decoder_events;  /**< per tx events */
+
+    uint8_t     replied;                    /**< bool indicating request is replied to. */
+
+    TAILQ_ENTRY(ModbusTransaction_) next;
+} ModbusTransaction;
+
+/* Modbus State Structure. */
+typedef struct ModbusState_ {
+    TAILQ_HEAD(, ModbusTransaction_)    tx_list;    /**< transaction list */
+    ModbusTransaction                   *curr;      /**< ptr to current tx */
+    uint64_t                            transaction_max;
+    uint32_t                            unreplied_cnt;  /**< number of unreplied requests */
+    uint16_t                            events;
+    uint8_t                             givenup;    /**< bool indicating flood. */
+} ModbusState;
+
+void RegisterModbusParsers(void);
+void ModbusParserRegisterTests(void);
+
+#endif /* __APP_LAYER_MODBUS_H__ */
index e665cceed6a19951e07c95f60e7400a693d46b29..ec45b0caf8a6c9047d3252d06e341f4bb68a86e4 100644 (file)
@@ -57,6 +57,7 @@
 #include "app-layer-smtp.h"
 #include "app-layer-dns-udp.h"
 #include "app-layer-dns-tcp.h"
+#include "app-layer-modbus.h"
 
 #include "conf.h"
 #include "util-spm.h"
@@ -1033,6 +1034,7 @@ void AppLayerParserRegisterProtocolParsers(void)
     RegisterSMTPParsers();
     RegisterDNSUDPParsers();
     RegisterDNSTCPParsers();
+    RegisterModbusParsers();
 
     /** IMAP */
     AppLayerProtoDetectRegisterProtocol(ALPROTO_IMAP, "imap");
index 39c729c41a33d857248f5d27550f5c00a97ca34b..ecfc4569e84b935a47752a4de5cbcb160cc3f787 100644 (file)
@@ -41,6 +41,7 @@ enum {
     ALPROTO_IRC,
 
     ALPROTO_DNS,
+    ALPROTO_MODBUS,
 
     /* used by the probing parser when alproto detection fails
      * permanently for that particular stream */
index b0d67508524ad1c2c898fbef8c3ab6cea1a4baf5..8da681ac3dc5de47e77e3b66ff7dd190a0cea9b0 100644 (file)
@@ -293,6 +293,7 @@ const char * SCErrorToString(SCError err)
         CASE_CODE (SC_WARN_XFF_INVALID_HEADER);
         CASE_CODE (SC_ERR_THRESHOLD_SETUP);
         CASE_CODE (SC_ERR_DNS_CONFIG);
+        CASE_CODE (SC_ERR_MODBUS_CONFIG);
         CASE_CODE (SC_ERR_CONF_YAML_ERROR);
         CASE_CODE (SC_ERR_CONF_NAME_TOO_LONG);
         CASE_CODE (SC_ERR_APP_LAYER_PROTOCOL_DETECTION);
index ada12d53c96446e2ba1046bf02d52c633b65f44f..7d715529051378a1a3b026e012e4f3b49fb8d8b8 100644 (file)
@@ -269,6 +269,7 @@ typedef enum {
     SC_WARN_XFF_INVALID_HEADER,
     SC_ERR_THRESHOLD_SETUP,
     SC_ERR_DNS_CONFIG,
+    SC_ERR_MODBUS_CONFIG,
     SC_ERR_CONF_YAML_ERROR,
     SC_ERR_CONF_NAME_TOO_LONG,
     SC_ERR_APP_LAYER_PROTOCOL_DETECTION,
index 29dacf4db0a005991cdb251c067f31d4feb7318c..70be178badd6c503ede913c1a674b7352dd6e993 100644 (file)
@@ -1028,6 +1028,7 @@ rule-files:
  - smtp-events.rules    # available in suricata sources under rules dir
  - dns-events.rules     # available in suricata sources under rules dir
  - tls-events.rules     # available in suricata sources under rules dir
+ - modbus-events.rules  # available in suricata sources under rules dir
 
 classification-file: @e_sysconfdir@classification.config
 reference-config-file: @e_sysconfdir@reference.config
@@ -1081,6 +1082,8 @@ vars:
 
     DNP3_PORTS: 20000
 
+    MODBUS_PORTS: 502
+
 # Set the order of alerts bassed on actions
 # The default order is pass, drop, reject, alert
 # action-order:
@@ -1186,6 +1189,19 @@ app-layer:
       enabled: yes
       detection-ports:
         dp: 139
+    # Note: Modbus probe parser is minimalist due to the poor significant field
+    # Only Modbus message length (greater than Modbus header length)
+    # And Protocol ID (equal to 0) are checked in probing parser
+    # It is important to enable detection port and define Modbus port
+    # to avoid false positive
+    modbus:
+      # How many unreplied Modbus requests are considered a flood.
+      # If the limit is reached, app-layer-event:modbus.flooded; will match.
+      #request-flood: 500
+
+      enabled: yes
+      detection-ports:
+        dp: 502
     # smb2 detection is disabled internally inside the engine.
     #smb2:
     #  enabled: yes