{"action":"created","comment":{"url":"https://api.github.com/repos/inliniac/suricata/pulls/comments/31670911","id":31670911,"diff_hunk":"@@ -0,0 +1,2616 @@\n+/* Copyright (C) 2015 Open Information Security Foundation\n+ *\n+ * You can copy, redistribute or modify this Program under the terms of\n+ * the GNU General Public License version 2 as published by the Free\n+ * Software Foundation.\n+ *\n+ * This program is distributed in the hope that it will be useful,\n+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ * GNU General Public License for more details.\n+ *\n+ * You should have received a copy of the GNU General Public License\n+ * version 2 along with this program; if not, write to the Free Software\n+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\n+ * 02110-1301, USA.\n+ */\n+\n+#include \"suricata-common.h\"\n+#include \"stream.h\"\n+#include \"util-byte.h\"\n+#include \"util-unittest.h\"\n+#include \"util-hashlist.h\"\n+\n+#include \"util-print.h\"\n+\n+#include \"app-layer-protos.h\"\n+#include \"app-layer-parser.h\"\n+#include \"app-layer-detect-proto.h\"\n+\n+#include \"app-layer-dnp3.h\"\n+\n+/** Default number of unreplied requests to be considered a flood. */\n+#define DNP3_DEFAULT_REQ_FLOOD_COUNT 500\n+\n+#define DNP3_DEFAULT_PORT \"20000\"\n+#define DNP3_START_BYTE0  0x05\n+#define DNP3_START_BYTE1  0x64\n+#define DNP3_MIN_LEN      5\n+#define DNP3_CRC_LEN      2\n+#define DNP3_BLOCK_SIZE   16\n+\n+#define DNP3_MAX_TRAN_SEQNO 64\n+#define DNP3_MAX_APP_SEQNO  16\n+\n+/* The number of bytes in the header that are counted as part of the\n+ * header length field. */\n+#define DNP3_LINK_HDR_LEN 5\n+\n+/* Link control function codes. */\n+#define DNP3_LINK_FC_CONFIRMED_USER_DATA   3\n+#define DNP3_LINK_FC_UNCONFIRMED_USER_DATA 4\n+\n+/* Reserved addresses. */\n+#define DNP3_RESERVED_ADDR_MIN 0xfff0\n+#define DNP3_RESERVED_ADDR_MAX 0xfffb\n+\n+/* Source addresses must be < 0xfff0. */\n+#define DNP3_SRC_ADDR_MAX 0xfff0\n+\n+/* DNP3 application request function codes. */\n+#define DNP3_APP_FC_CONFIRM                0x00\n+#define DNP3_APP_FC_READ                   0x01\n+#define DNP3_APP_FC_WRITE                  0x02\n+#define DNP3_APP_FC_SELECT                 0x03\n+#define DNP3_APP_FC_OPERATE                0x04\n+#define DNP3_APP_FC_DIR_OPERATE            0x05\n+#define DNP3_APP_FC_DIR_OPERATE_NR         0x06\n+#define DNP3_APP_FC_FREEZE                 0x07\n+#define DNP3_APP_FC_FREEZE_NR              0x08\n+#define DNP3_APP_FC_FREEZE_CLEAR           0x09\n+#define DNP3_APP_FC_FREEZE_CLEAR_NR        0x0a\n+#define DNP3_APP_FC_FREEZE_AT_TIME         0x0b\n+#define DNP3_APP_FC_FREEZE_AT_TIME_NR      0x0c\n+#define DNP3_APP_FC_COLD_RESTART           0x0d\n+#define DNP3_APP_FC_WARM_RESTART           0x0e\n+#define DNP3_APP_FC_INITIALIZE_DATA        0x0f\n+#define DNP3_APP_FC_INITIALIZE_APPLICATION 0x10\n+#define DNP3_APP_FC_START_APPLICATION      0x11\n+#define DNP3_APP_FC_STOP_APPLICATION       0x12\n+#define DNP3_APP_FC_SAVE_CONFIGURATION     0x13\n+#define DNP3_APP_FC_ENABLE_UNSOLICITED     0x14\n+#define DNP3_APP_FC_DISABLE_UNSOLICTED     0x15\n+#define DNP3_APP_FC_ASSIGN_CLASS           0x16\n+#define DNP3_APP_FC_DELAY_MEASUREMENT      0x17\n+#define DNP3_APP_FC_RECORD_CURRENT_TIME    0x18\n+#define DNP3_APP_FC_OPEN_TIME              0x19\n+#define DNP3_APP_FC_CLOSE_FILE             0x1a\n+#define DNP3_APP_FC_DELETE_FILE            0x1b\n+#define DNP3_APP_FC_GET_FILE_INFO          0x1c\n+#define DNP3_APP_FC_AUTHENTICATE_FILE      0x1d\n+#define DNP3_APP_FC_ABORT_FILE             0x1e\n+#define DNP3_APP_FC_ACTIVATE_CONFIG        0x1f\n+#define DNP3_APP_FC_AUTH_REQ               0x20\n+#define DNP3_APP_FC_AUTH_REQ_NR            0x21\n+\n+/* DNP3 application response function codes. */\n+#define DNP3_APP_FC_RESPONSE               0x81\n+#define DNP3_APP_FC_UNSOLICITED_RESP       0x82\n+#define DNP3_APP_FC_AUTH_RESP              0x83\n+\n+#define DNP3_OBJ_GROUP_MAX 256\n+#define DNP3_OBJ_VAR_MAX 256\n+\n+#define DNP3_OBJ_TIME_SIZE   6  /* AKA UINT48. */\n+#define DNP3_OBJ_G12_V1_SIZE 11\n+#define DNP3_OBJ_G12_V2_SIZE 11\n+#define DNP3_OBJ_G12_V3_SIZE 1\n+\n+/* Extract the prefix code from the object qualifier. */\n+#define DNP3_OBJ_PREFIX(x) ((x >> 4) & 0x3)\n+\n+/* Extract the range code from the object qualifier. */\n+#define DNP3_OBJ_RANGE(x)  (x & 0xf)\n+\n+typedef struct DNP3LinkHeader_ {\n+    uint8_t  start_byte0;\n+    uint8_t  start_byte1;\n+    uint8_t  len;\n+    uint8_t  control;\n+    uint16_t dst;\n+    uint16_t src;\n+    uint16_t crc;\n+} __attribute__((__packed__)) DNP3LinkHeader;\n+\n+typedef struct DNP3ApplicationHeader_ {\n+    uint8_t control;\n+    uint8_t function_code;\n+} __attribute__((__packed__)) DNP3ApplicationHeader;\n+\n+typedef struct DNP3InternalInd_ {\n+    uint8_t iin1;\n+    uint8_t iin2;\n+} __attribute__((__packed__)) DNP3InternalInd;\n+\n+/**\n+ * DNP3 application object header.\n+ */\n+typedef struct DNP3ObjHeader_ {\n+    uint8_t group;\n+    uint8_t variation;\n+    uint8_t qualifier;\n+} __attribute__((packed)) DNP3ObjHeader;\n+\n+/**\n+ * A table to hold object sizes for all object groups and their\n+ * variations.\n+ *\n+ * Sizes are in bits.\n+ */\n+static int GROUP_VAR_SIZE_MAP[DNP3_OBJ_GROUP_MAX][DNP3_OBJ_VAR_MAX];\n+\n+#define BSTRn    1\n+#define BSTR1    1\n+#define BSTR6    6\n+#define BSTR8    8\n+#define FLT32    32\n+#define FLT64    64\n+#define INT16    16\n+#define INT32    32\n+#define UINT2    2\n+#define UINT4    4\n+#define UINT7    7\n+#define UINT8    8\n+#define UINT16   16\n+#define UINT32   32\n+#define DNP3TIME (DNP3_OBJ_TIME_SIZE * 8)\n+\n+/**\n+ * \\brief Initialize the group/variation object size table for objects\n+ *     that have a static size.\n+ */\n+static void DNP3InitGroupVarSizeMap(void)\n+{\n+    /* Initialize all values to -1 to denote unset/unknown. */\n+    for (int i = 0; i < DNP3_OBJ_GROUP_MAX; i++) {\n+        for (int j = 0; j < DNP3_OBJ_VAR_MAX; j++) {\n+            GROUP_VAR_SIZE_MAP[i][j] = -1;\n+        }\n+    }\n+\n+    GROUP_VAR_SIZE_MAP[1][1] = BSTRn;\n+    GROUP_VAR_SIZE_MAP[1][2] = BSTR8;\n+\n+    GROUP_VAR_SIZE_MAP[2][1] = BSTR8;\n+    GROUP_VAR_SIZE_MAP[2][2] = BSTR8 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[2][3] = BSTR8 + UINT16;\n+\n+    GROUP_VAR_SIZE_MAP[3][1] = UINT2;\n+    GROUP_VAR_SIZE_MAP[3][2] = BSTR6 + UINT2;\n+\n+    GROUP_VAR_SIZE_MAP[4][1] = BSTR6 + UINT2;\n+    GROUP_VAR_SIZE_MAP[4][2] = BSTR6 + UINT2 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[4][3] = BSTR6 + UINT2 + UINT16;\n+\n+    GROUP_VAR_SIZE_MAP[10][1] = BSTRn;\n+    GROUP_VAR_SIZE_MAP[10][2] = BSTR8;\n+\n+    GROUP_VAR_SIZE_MAP[11][1] = BSTR8;\n+    GROUP_VAR_SIZE_MAP[11][2] = BSTR8 + DNP3TIME;\n+\n+    GROUP_VAR_SIZE_MAP[12][1] = UINT4 + BSTR1 + BSTR1 + UINT2 + UINT8 +\n+        UINT32 + UINT32 + UINT7 + BSTR1;\n+    GROUP_VAR_SIZE_MAP[12][2] = GROUP_VAR_SIZE_MAP[12][1];\n+    GROUP_VAR_SIZE_MAP[12][3] = BSTRn;\n+\n+    GROUP_VAR_SIZE_MAP[13][1] = UINT7 + BSTR1;\n+    GROUP_VAR_SIZE_MAP[13][2] = UINT7 + BSTR1 + DNP3TIME;\n+\n+    GROUP_VAR_SIZE_MAP[20][1] = BSTR8 + UINT32;\n+    GROUP_VAR_SIZE_MAP[20][2] = BSTR8 + UINT16;\n+    GROUP_VAR_SIZE_MAP[20][3] = BSTR8 + UINT32;\n+    GROUP_VAR_SIZE_MAP[20][4] = BSTR8 + UINT16;\n+    GROUP_VAR_SIZE_MAP[20][5] = UINT32;\n+    GROUP_VAR_SIZE_MAP[20][6] = UINT16;\n+    GROUP_VAR_SIZE_MAP[20][7] = UINT32;\n+    GROUP_VAR_SIZE_MAP[20][8] = UINT16;\n+\n+    GROUP_VAR_SIZE_MAP[21][1] = BSTR8 + UINT32;\n+    GROUP_VAR_SIZE_MAP[21][2] = BSTR8 + UINT16;\n+    GROUP_VAR_SIZE_MAP[21][3] = BSTR8 + UINT32;\n+    GROUP_VAR_SIZE_MAP[21][4] = BSTR8 + UINT16;\n+    GROUP_VAR_SIZE_MAP[21][5] = BSTR8 + UINT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[21][6] = BSTR8 + UINT16 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[21][7] = BSTR8 + UINT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[21][8] = BSTR8 + UINT16 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[21][9] = UINT32;\n+    GROUP_VAR_SIZE_MAP[21][10] = UINT16;\n+    GROUP_VAR_SIZE_MAP[21][11] = UINT32;\n+    GROUP_VAR_SIZE_MAP[21][12] = UINT16;\n+\n+    GROUP_VAR_SIZE_MAP[22][1] = BSTR8 + UINT32;\n+    GROUP_VAR_SIZE_MAP[22][2] = BSTR8 + UINT16;\n+    GROUP_VAR_SIZE_MAP[22][3] = BSTR8 + UINT32;\n+    GROUP_VAR_SIZE_MAP[22][4] = BSTR8 + UINT16;\n+    GROUP_VAR_SIZE_MAP[22][5] = BSTR8 + UINT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[22][6] = BSTR8 + UINT16 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[22][7] = BSTR8 + UINT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[22][8] = BSTR8 + UINT16 + DNP3TIME;\n+\n+    GROUP_VAR_SIZE_MAP[23][1] = BSTR8 + UINT32;\n+    GROUP_VAR_SIZE_MAP[23][2] = BSTR8 + UINT16;\n+    GROUP_VAR_SIZE_MAP[23][3] = BSTR8 + UINT32;\n+    GROUP_VAR_SIZE_MAP[23][4] = BSTR8 + UINT16;\n+    GROUP_VAR_SIZE_MAP[23][5] = BSTR8 + UINT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[23][6] = BSTR8 + UINT16 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[23][7] = BSTR8 + UINT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[23][8] = BSTR8 + UINT16 + DNP3TIME;\n+\n+    GROUP_VAR_SIZE_MAP[30][1] = BSTR8 + INT32;\n+    GROUP_VAR_SIZE_MAP[30][2] = BSTR8 + INT16;\n+    GROUP_VAR_SIZE_MAP[30][3] = INT32;\n+    GROUP_VAR_SIZE_MAP[30][4] = INT16;\n+    GROUP_VAR_SIZE_MAP[30][5] = BSTR8 + FLT32;\n+    GROUP_VAR_SIZE_MAP[30][6] = BSTR8 + FLT64;\n+\n+    GROUP_VAR_SIZE_MAP[31][1] = BSTR8 + INT32;\n+    GROUP_VAR_SIZE_MAP[31][2] = BSTR8 + INT16;\n+    GROUP_VAR_SIZE_MAP[31][3] = BSTR8 + INT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[31][4] = BSTR8 + INT16 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[31][5] = INT32;\n+    GROUP_VAR_SIZE_MAP[31][6] = INT16;\n+    GROUP_VAR_SIZE_MAP[31][7] = BSTR8 + FLT32;\n+    GROUP_VAR_SIZE_MAP[31][8] = BSTR8 + FLT64;\n+\n+    GROUP_VAR_SIZE_MAP[32][1] = BSTR8 + INT32;\n+    GROUP_VAR_SIZE_MAP[32][2] = BSTR8 + INT16;\n+    GROUP_VAR_SIZE_MAP[32][3] = BSTR8 + INT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[32][4] = BSTR8 + INT16 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[32][5] = BSTR8 + FLT32;\n+    GROUP_VAR_SIZE_MAP[32][6] = BSTR8 + FLT64;\n+    GROUP_VAR_SIZE_MAP[32][7] = BSTR8 + FLT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[32][8] = BSTR8 + FLT32 + DNP3TIME;\n+\n+    GROUP_VAR_SIZE_MAP[33][1] = BSTR8 + INT32;\n+    GROUP_VAR_SIZE_MAP[33][2] = BSTR8 + INT16;\n+    GROUP_VAR_SIZE_MAP[33][3] = BSTR8 + INT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[33][4] = BSTR8 + INT16 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[33][5] = BSTR8 + FLT32;\n+    GROUP_VAR_SIZE_MAP[33][6] = BSTR8 + FLT64;\n+    GROUP_VAR_SIZE_MAP[33][7] = BSTR8 + FLT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[33][8] = BSTR8 + FLT32 + DNP3TIME;\n+\n+    GROUP_VAR_SIZE_MAP[34][1] = UINT16;\n+    GROUP_VAR_SIZE_MAP[34][2] = UINT32;\n+    GROUP_VAR_SIZE_MAP[34][3] = FLT32;\n+\n+    GROUP_VAR_SIZE_MAP[40][1] = BSTR8 + INT32;\n+    GROUP_VAR_SIZE_MAP[40][2] = BSTR8 + INT16;\n+    GROUP_VAR_SIZE_MAP[40][3] = BSTR8 + FLT32;\n+    GROUP_VAR_SIZE_MAP[40][4] = BSTR8 + FLT64;\n+\n+    GROUP_VAR_SIZE_MAP[41][1] = INT32 + UINT8;\n+    GROUP_VAR_SIZE_MAP[41][2] = INT16 + UINT8;\n+    GROUP_VAR_SIZE_MAP[41][3] = FLT32 + UINT8;\n+    GROUP_VAR_SIZE_MAP[41][4] = FLT64 + UINT8;\n+\n+    GROUP_VAR_SIZE_MAP[42][1] = BSTR8 + INT32;\n+    GROUP_VAR_SIZE_MAP[42][2] = BSTR8 + INT16;\n+    GROUP_VAR_SIZE_MAP[42][3] = BSTR8 + INT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[42][4] = BSTR8 + INT16 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[42][5] = BSTR8 + FLT32;\n+    GROUP_VAR_SIZE_MAP[42][6] = BSTR8 + FLT64;\n+    GROUP_VAR_SIZE_MAP[42][7] = BSTR8 + FLT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[42][8] = BSTR8 + FLT64 + DNP3TIME;\n+\n+    GROUP_VAR_SIZE_MAP[43][1] = UINT7 + BSTR1 + INT32;\n+    GROUP_VAR_SIZE_MAP[43][2] = UINT7 + BSTR1 + INT16;\n+    GROUP_VAR_SIZE_MAP[43][3] = UINT7 + BSTR1 + INT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[43][4] = UINT7 + BSTR1 + INT16 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[43][5] = UINT7 + BSTR1 + FLT32;\n+    GROUP_VAR_SIZE_MAP[43][6] = UINT7 + BSTR1 + FLT64;\n+    GROUP_VAR_SIZE_MAP[43][7] = UINT7 + BSTR1 + FLT32 + DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[43][8] = UINT7 + BSTR1 + FLT64 + DNP3TIME;\n+\n+    GROUP_VAR_SIZE_MAP[50][1] = DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[50][2] = DNP3TIME + UINT32;\n+    GROUP_VAR_SIZE_MAP[50][3] = DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[50][4] = DNP3TIME + UINT32 + UINT8;\n+\n+    GROUP_VAR_SIZE_MAP[51][1] = DNP3TIME;\n+    GROUP_VAR_SIZE_MAP[51][2] = DNP3TIME;\n+\n+    GROUP_VAR_SIZE_MAP[52][1] = UINT16;\n+    GROUP_VAR_SIZE_MAP[52][1] = UINT16;\n+\n+    GROUP_VAR_SIZE_MAP[60][1] = 0;\n+    GROUP_VAR_SIZE_MAP[60][2] = 0;\n+    GROUP_VAR_SIZE_MAP[60][3] = 0;\n+    GROUP_VAR_SIZE_MAP[60][4] = 0;\n+\n+    GROUP_VAR_SIZE_MAP[80][1] = BSTRn;\n+\n+    GROUP_VAR_SIZE_MAP[81][1] = UINT7 + BSTR1 + UINT8 + UINT8;\n+\n+    GROUP_VAR_SIZE_MAP[102][1] = UINT8;\n+\n+    GROUP_VAR_SIZE_MAP[121][1] = BSTR8 + UINT16 + UINT32;\n+\n+    GROUP_VAR_SIZE_MAP[122][1] = BSTR8 + UINT16 + UINT32;\n+    GROUP_VAR_SIZE_MAP[122][2] = BSTR8 + UINT16 + UINT32 + DNP3TIME;\n+}\n+\n+/* Map DNP3 object prefix codes to their size.\n+ * Table 4-4, IEEE 1815-2012. */\n+static int DNP3_OBJ_PREFIX_CODE_MAP[] = {\n+    0,                          /* 0. No prefix. */\n+    1,                          /* 1. 1 octet. */\n+    2,                          /* 2. 2 octet. */\n+    4,                          /* 3. 4 octet. */\n+    1,                          /* 4. 1 octet. */\n+    2,                          /* 5. 2 octet. */\n+    4,                          /* 6. 4 octet. */\n+    -1,                         /* 7. Reserved. */\n+};\n+\n+/* Decoder event map. */\n+SCEnumCharMap dnp3_decoder_event_table[] = {\n+    {\"FLOODED\",           DNP3_DECODER_EVENT_FLOODED},\n+    {\"LEN_TOO_SMALL\",     DNP3_DECODER_EVENT_LEN_TOO_SMALL},\n+    {\"BAD_LINK_CRC\",      DNP3_DECODER_EVENT_BAD_LINK_CRC},\n+    {\"BAD_TRANSPORT_CRC\", DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC},\n+    {NULL, -1},\n+};\n+\n+/* Some DNP3 servers start with a banner. */\n+static const char banner[] = \"DNP3\";\n+\n+/* DNP3 values are stored in little endian on the wire, so swapping will be\n+ * needed on big endian architectures. */\n+#if __BYTE_ORDER == __BIG_ENDIAN\n+#define dnp3_swap16(x) SCByteSwap16(x)\n+#define dnp3_swap32(x) SCByteSwap32(x)\n+#elif __BYTE_ORDER == __LITTLE_ENDIAN\n+#define dnp3_swap16(x) x\n+#define dnp3_swap32(x) x\n+#endif\n+\n+/* Calculate the next transport sequence number. */\n+#define NEXT_TRAN_SEQNO(current) ((current + 1) % DNP3_MAX_TRAN_SEQNO)\n+\n+/* Calculate the next application sequence number. */\n+#define NEXT_APP_SEQNO(current)  ((current + 1) % DNP3_MAX_APP_SEQNO)\n+\n+/* CRC table generated by pycrc - http://github.com/tpircher/pycrc.\n+ * - Polynomial: 0x3d65.\n+ */\n+static const uint16_t crc_table[256] = {\n+    0x0000, 0x365e, 0x6cbc, 0x5ae2, 0xd978, 0xef26, 0xb5c4, 0x839a,\n+    0xff89, 0xc9d7, 0x9335, 0xa56b, 0x26f1, 0x10af, 0x4a4d, 0x7c13,\n+    0xb26b, 0x8435, 0xded7, 0xe889, 0x6b13, 0x5d4d, 0x07af, 0x31f1,\n+    0x4de2, 0x7bbc, 0x215e, 0x1700, 0x949a, 0xa2c4, 0xf826, 0xce78,\n+    0x29af, 0x1ff1, 0x4513, 0x734d, 0xf0d7, 0xc689, 0x9c6b, 0xaa35,\n+    0xd626, 0xe078, 0xba9a, 0x8cc4, 0x0f5e, 0x3900, 0x63e2, 0x55bc,\n+    0x9bc4, 0xad9a, 0xf778, 0xc126, 0x42bc, 0x74e2, 0x2e00, 0x185e,\n+    0x644d, 0x5213, 0x08f1, 0x3eaf, 0xbd35, 0x8b6b, 0xd189, 0xe7d7,\n+    0x535e, 0x6500, 0x3fe2, 0x09bc, 0x8a26, 0xbc78, 0xe69a, 0xd0c4,\n+    0xacd7, 0x9a89, 0xc06b, 0xf635, 0x75af, 0x43f1, 0x1913, 0x2f4d,\n+    0xe135, 0xd76b, 0x8d89, 0xbbd7, 0x384d, 0x0e13, 0x54f1, 0x62af,\n+    0x1ebc, 0x28e2, 0x7200, 0x445e, 0xc7c4, 0xf19a, 0xab78, 0x9d26,\n+    0x7af1, 0x4caf, 0x164d, 0x2013, 0xa389, 0x95d7, 0xcf35, 0xf96b,\n+    0x8578, 0xb326, 0xe9c4, 0xdf9a, 0x5c00, 0x6a5e, 0x30bc, 0x06e2,\n+    0xc89a, 0xfec4, 0xa426, 0x9278, 0x11e2, 0x27bc, 0x7d5e, 0x4b00,\n+    0x3713, 0x014d, 0x5baf, 0x6df1, 0xee6b, 0xd835, 0x82d7, 0xb489,\n+    0xa6bc, 0x90e2, 0xca00, 0xfc5e, 0x7fc4, 0x499a, 0x1378, 0x2526,\n+    0x5935, 0x6f6b, 0x3589, 0x03d7, 0x804d, 0xb613, 0xecf1, 0xdaaf,\n+    0x14d7, 0x2289, 0x786b, 0x4e35, 0xcdaf, 0xfbf1, 0xa113, 0x974d,\n+    0xeb5e, 0xdd00, 0x87e2, 0xb1bc, 0x3226, 0x0478, 0x5e9a, 0x68c4,\n+    0x8f13, 0xb94d, 0xe3af, 0xd5f1, 0x566b, 0x6035, 0x3ad7, 0x0c89,\n+    0x709a, 0x46c4, 0x1c26, 0x2a78, 0xa9e2, 0x9fbc, 0xc55e, 0xf300,\n+    0x3d78, 0x0b26, 0x51c4, 0x679a, 0xe400, 0xd25e, 0x88bc, 0xbee2,\n+    0xc2f1, 0xf4af, 0xae4d, 0x9813, 0x1b89, 0x2dd7, 0x7735, 0x416b,\n+    0xf5e2, 0xc3bc, 0x995e, 0xaf00, 0x2c9a, 0x1ac4, 0x4026, 0x7678,\n+    0x0a6b, 0x3c35, 0x66d7, 0x5089, 0xd313, 0xe54d, 0xbfaf, 0x89f1,\n+    0x4789, 0x71d7, 0x2b35, 0x1d6b, 0x9ef1, 0xa8af, 0xf24d, 0xc413,\n+    0xb800, 0x8e5e, 0xd4bc, 0xe2e2, 0x6178, 0x5726, 0x0dc4, 0x3b9a,\n+    0xdc4d, 0xea13, 0xb0f1, 0x86af, 0x0535, 0x336b, 0x6989, 0x5fd7,\n+    0x23c4, 0x159a, 0x4f78, 0x7926, 0xfabc, 0xcce2, 0x9600, 0xa05e,\n+    0x6e26, 0x5878, 0x029a, 0x34c4, 0xb75e, 0x8100, 0xdbe2, 0xedbc,\n+    0x91af, 0xa7f1, 0xfd13, 0xcb4d, 0x48d7, 0x7e89, 0x246b, 0x1235\n+};\n+\n+static uint16_t DNP3ComputeCRC(uint8_t *buf, uint32_t len)\n+{\n+    uint16_t crc = 0;\n+    uint8_t *byte = buf;\n+    int idx;\n+\n+    while (len--) {\n+        idx = (crc ^ *byte) & 0xff;\n+        crc = (crc_table[idx] ^ (crc >> 8)) & 0xffff;\n+        byte++;\n+    }\n+\n+    return ~crc & 0xffff;\n+}\n+\n+/**\n+ * \\brief Check the CRC of a block.\n+ *\n+ * \\param block The block of data with CRC to be checked.\n+ * \\param len The size of the data block.\n+ *\n+ * \\retval 1 if CRC is OK, otherwise 0.\n+ */\n+static int DNP3CheckCRC(uint8_t *block, uint32_t len)\n+{\n+    uint32_t crc_offset = len - DNP3_CRC_LEN;\n+    uint16_t crc = DNP3ComputeCRC(block, len - DNP3_CRC_LEN);\n+    if (((crc & 0xff) == block[crc_offset]) &&\n+        ((crc >> 8) == block[crc_offset + 1])) {\n+        return 1;\n+    }\n+\n+    return 0;\n+}\n+\n+static int DNP3CheckLinkHeaderCRC(DNP3LinkHeader *header)\n+{\n+    return DNP3CheckCRC((uint8_t *)header, sizeof(DNP3LinkHeader));\n+}\n+\n+/**\n+ * \\brief Check user data CRCs.\n+ *\n+ * \\param data Pointer to user data.\n+ * \\param len Length of user data.\n+ *\n+ * \\retval 1 if CRCs are OK, otherwise 0.\n+ */\n+static int DNP3CheckUserDataCRCs(uint8_t *data, uint32_t len)\n+{\n+    uint32_t offset = 0;\n+    uint32_t block_size;\n+\n+    while (offset < len) {\n+        if (len - offset >= DNP3_BLOCK_SIZE + DNP3_CRC_LEN) {\n+            block_size = DNP3_BLOCK_SIZE + DNP3_CRC_LEN;\n+        }\n+        else {\n+            block_size = len - offset;\n+        }\n+\n+        if (!DNP3CheckCRC(data + offset, block_size)) {\n+            /* Once failed, may as well return immediately. */\n+            return 0;\n+        }\n+\n+        offset += block_size;\n+    }\n+\n+    return 1;\n+}\n+\n+/**\n+ * \\brief Allocate a DNP3 session.\n+ */\n+static DNP3Session *DNP3SessionAlloc(DNP3LinkHeader *header)\n+{\n+    SCEnter();\n+    DNP3Session *session = SCCalloc(1, sizeof(DNP3Session));\n+    if (unlikely(session == NULL)) {\n+        SCReturnPtr(NULL, \"DNP3Session\");\n+    }\n+    if (DNP3_LINK_DIR(header->control)) {\n+        session->master = header->src;\n+        session->slave = header->dst;\n+    }\n+    else {\n+        session->master = header->dst;\n+        session->slave = header->src;\n+    }\n+    SCReturnPtr(session, \"DNP3Session\");\n+}\n+\n+/**\n+ * \\brief Free a DNPSession.\n+ *\n+ * Note: Argument is void so this function can be provided to the hash\n+ *   table implemention.\n+ */\n+static void DNP3SessionFree(void *data)\n+{\n+    if (data != NULL) {\n+        SCFree(data);\n+    }\n+}\n+\n+/**\n+ * \\brief Get a DNP3 session, allocating if necessary.\n+ */\n+static DNP3Session *DNP3SessionGet(DNP3State *dnp3, DNP3LinkHeader *header)\n+{\n+    DNP3Session *session = NULL;\n+\n+    session = HashListTableLookup(dnp3->sessions, header,\n+        sizeof(DNP3LinkHeader));\n+    if (session == NULL) {\n+        session = DNP3SessionAlloc(header);\n+        if (session != NULL) {\n+            HashListTableAdd(dnp3->sessions, session, sizeof(DNP3Session));\n+        }\n+    }\n+\n+    return session;\n+}\n+\n+/**\n+ * \\brief Check the DNP3 frame start bytes.\n+ *\n+ * \\retval 1 if valid, 0 if not.\n+ */\n+static inline int DNP3CheckStartBytes(DNP3LinkHeader *header)\n+{\n+    return header->start_byte0 == DNP3_START_BYTE0 &&\n+        header->start_byte1 == DNP3_START_BYTE1;\n+}\n+\n+/**\n+ * \\brief Check if a frame contains a banner.\n+ *\n+ * Some servers (outstations) appear to send back a banner that fails\n+ * the normal frame checks.  So first check for a banner.\n+ *\n+ * \\retval 1 if a banner is found, 0 if not.\n+ */\n+static int DNP3ContainsBanner(uint8_t *input, uint32_t len)\n+{\n+    return memmem(input, len, banner, strlen(banner)) != NULL;\n+}\n+\n+static uint16_t DNP3ProbingParser(uint8_t *input, uint32_t len,\n+    uint32_t *offset)\n+{\n+    DNP3LinkHeader *hdr = (DNP3LinkHeader *)input;\n+\n+    /* Check that we have the minimum amount of bytes. */\n+    if (len < sizeof(DNP3LinkHeader)) {\n+        SCLogDebug(\"Length too small to be a DNP3 header.\");\n+        return ALPROTO_UNKNOWN;\n+    }\n+\n+    /* May be a banner. */\n+    if (DNP3ContainsBanner(input, len)) {\n+        SCLogDebug(\"Packet contains a DNP3 banner.\");\n+        goto end;\n+    }\n+\n+    /* Verify start value (from AN2013-004b). */\n+    if (!DNP3CheckStartBytes(hdr)) {\n+        SCLogDebug(\"Invalid start bytes.\");\n+        return ALPROTO_FAILED;\n+    }\n+\n+    /* Verify minimum length. */\n+    if (hdr->len < DNP3_MIN_LEN) {\n+        SCLogDebug(\"Packet too small to be a valid DNP3 fragment.\");\n+        return ALPROTO_FAILED;\n+    }\n+\n+end:\n+    SCLogDebug(\"Detected DNP3.\");\n+    return ALPROTO_DNP3;\n+}\n+\n+/**\n+ * \\brief Calculate a DNP3 session hash for the session hash table.\n+ */\n+static uint32_t DNP3SessionHash(HashListTable *ht, void *buf, uint16_t buflen)\n+{\n+    uint16_t a = 0, b = 0;\n+    uint32_t hash;\n+\n+    if (buflen == sizeof(DNP3Session)) {\n+        DNP3Session *session = buf;\n+        a = session->master;\n+        b = session->slave;\n+    }\n+    else if (buflen == sizeof(DNP3LinkHeader)) {\n+        DNP3LinkHeader *header = buf;\n+        a = header->src;\n+        b = header->dst;\n+    }\n+\n+    hash = (a * b) % ht->array_size;\n+    return hash;\n+}\n+\n+/**\n+ * \\brief Compare DNP3 session hash - for the session hash table.\n+ */\n+static char DNP3SessionHashCompare(void *a, uint16_t alen, void *b,\n+    uint16_t blen)\n+{\n+    uint16_t a1 = 0, a2 = 0;\n+    uint16_t b1 = 0, b2 = 0;\n+\n+    if (alen == sizeof(DNP3Session)) {\n+        DNP3Session *session = a;\n+        a1 = session->master;\n+        a2 = session->slave;\n+    }\n+    else if (alen == sizeof(DNP3LinkHeader)) {\n+        DNP3LinkHeader *header = a;\n+        a1 = header->src;\n+        a2 = header->dst;\n+    }\n+\n+    if (blen == sizeof(DNP3Session)) {\n+        DNP3Session *session = b;\n+        b1 = session->master;\n+        b2 = session->slave;\n+    }\n+    else if (blen == sizeof(DNP3LinkHeader)) {\n+        DNP3LinkHeader *header = b;\n+        b1 = header->src;\n+        b2 = header->dst;\n+    }\n+\n+    if (a1 == b1 && a2 == b2) {\n+        return 1;\n+    }\n+    if (a1 == b2 && a2 == b1) {\n+        return 1;\n+    }\n+\n+    return 0;\n+}\n+\n+/**\n+ * \\brief Reassemble the application layer by stripping the CRCs.\n+ *\n+ * \\param input Input buffer starting at the transport header (which\n+ *    will be removed from the output).\n+ * \\param input_len Length of the input buffer.\n+ * \\param output Pointer to output buffer (may be realloc'd).\n+ * \\param output_len Pointer to output length.\n+ *\n+ * \\retval Pointer to output buffer if successful, otherwise NULL.\n+ */\n+static uint8_t *DNP3ReassembleApplicationLayer(uint8_t *input,\n+    uint32_t input_len, uint8_t **output, uint32_t *output_len)\n+{\n+    int len = (input_len / (DNP3_BLOCK_SIZE + DNP3_CRC_LEN)) * DNP3_BLOCK_SIZE;\n+    int rem = input_len % (DNP3_BLOCK_SIZE + DNP3_CRC_LEN);\n+\n+    if (rem) {\n+        /* The remainder must be at least one byte plus a CRC. */\n+        if (rem < 3) {\n+            return NULL;\n+        }\n+        len += rem + DNP3_CRC_LEN;\n+    }\n+\n+    /* Remove one byte for the transport header which won't be\n+     * included in the output. */\n+    len--;\n+\n+    if (*output == NULL) {\n+        *output = SCCalloc(1, len);\n+        if (unlikely(*output == NULL)) {\n+            return NULL;\n+        }\n+    }\n+    else {\n+        uint8_t *ptr = SCRealloc(*output, (size_t)(*output_len + len));\n+        if (unlikely(ptr == NULL)) {\n+            SCFree(*output);\n+            return NULL;\n+        }\n+        *output = ptr;\n+    }\n+\n+    int offset = 0, block_size;\n+    while ((uint32_t)offset < input_len) {\n+        if (input_len - offset > DNP3_BLOCK_SIZE + DNP3_CRC_LEN) {\n+            block_size = DNP3_BLOCK_SIZE + DNP3_CRC_LEN;\n+        }\n+        else {\n+            block_size = input_len - offset;\n+        }\n+\n+        /* For the first block we trim the leading transport header. */\n+        if (offset == 0) {\n+            memcpy(*output + *output_len, input + offset + 1,\n+                block_size - DNP3_CRC_LEN - 1);\n+            *output_len += block_size - DNP3_CRC_LEN - 1;\n+        }\n+        else {\n+            memcpy(*output + *output_len, input + offset,\n+                block_size - DNP3_CRC_LEN);\n+            *output_len += block_size - DNP3_CRC_LEN;\n+        }\n+        offset += block_size;\n+    }\n+\n+    return *output;\n+}\n+\n+static void *DNP3StateAlloc(void)\n+{\n+    SCEnter();\n+    DNP3State *dnp3;\n+\n+    dnp3 = (DNP3State *)SCCalloc(1, sizeof(DNP3State));\n+    if (unlikely(dnp3 == NULL)) {\n+        return NULL;\n+    }\n+    TAILQ_INIT(&dnp3->tx_list);\n+\n+    dnp3->sessions = HashListTableInit(256, DNP3SessionHash,\n+        DNP3SessionHashCompare, DNP3SessionFree);\n+    if (unlikely(dnp3->sessions == NULL)) {\n+        goto fail;\n+    }\n+\n+    SCReturnPtr(dnp3, \"void\");\n+fail:\n+    if (dnp3 != NULL)\n+        SCFree(dnp3);\n+    SCReturnPtr(NULL, \"void\");\n+}\n+\n+static void DNP3SetEvent(DNP3State *dnp3, uint8_t event)\n+{\n+    if (dnp3 && dnp3->curr) {\n+        AppLayerDecoderEventsSetEventRaw(&dnp3->curr->decoder_events, event);\n+        dnp3->events++;\n+    }\n+    else {\n+        SCLogWarning(SC_ERR_ALPARSER,\n+            \"Fail set set event, state or txn was NULL.\");\n+    }\n+}\n+\n+static DNP3Transaction *DNP3TxAlloc(DNP3State *dnp3)\n+{\n+    DNP3Transaction *tx = SCCalloc(1, sizeof(DNP3Transaction));\n+    if (unlikely(tx == NULL)) {\n+        return NULL;\n+    }\n+    dnp3->unreplied++;\n+    dnp3->curr = tx;\n+    tx->dnp3 = dnp3;\n+    tx->tx_num = ++dnp3->transaction_max;\n+    TAILQ_INIT(&tx->request_objects);\n+    TAILQ_INIT(&tx->response_objects);\n+    TAILQ_INSERT_TAIL(&dnp3->tx_list, tx, next);\n+\n+    /* Check for flood state. */\n+    if (dnp3->unreplied > DNP3_DEFAULT_REQ_FLOOD_COUNT) {\n+        DNP3SetEvent(dnp3, DNP3_DECODER_EVENT_FLOODED);\n+        dnp3->flooded = 1;\n+    }\n+\n+    return tx;\n+}\n+\n+static DNP3Transaction *DNP3FindTransaction(DNP3State *dnp3,\n+    DNP3Session *session, uint8_t seqno)\n+{\n+    if (dnp3->curr == NULL) {\n+        return NULL;\n+    }\n+\n+    if (dnp3->curr->session == session && dnp3->curr->app_seqno == seqno) {\n+        return dnp3->curr;\n+    }\n+\n+    DNP3Transaction *tx;\n+    TAILQ_FOREACH(tx, &dnp3->tx_list, next) {\n+        if (tx->session == session && tx->app_seqno == seqno) {\n+            return tx;\n+        }\n+    }\n+\n+    return NULL;\n+}\n+\n+/**\n+ * \\brief Calculate the length of a frame with CRCs added.\n+ */\n+static uint32_t DNP3CalculateUnassembledLength(uint8_t length)\n+{\n+    uint32_t frame_len = 0;\n+    int rem;\n+\n+    /* Subtract the 5 bytes of the header that are included in the\n+     * length. */\n+    length -= DNP3_LINK_HDR_LEN;\n+\n+    rem = length % DNP3_BLOCK_SIZE;\n+    frame_len = (length / DNP3_BLOCK_SIZE) * (DNP3_BLOCK_SIZE + DNP3_CRC_LEN);\n+    if (rem) {\n+        frame_len += rem + DNP3_CRC_LEN;\n+    }\n+\n+    return frame_len + sizeof(DNP3LinkHeader);\n+}\n+\n+/**\n+ * \\brief Check if the link function code specifies user data.\n+ */\n+static int DNP3IsUserData(DNP3LinkHeader *header)\n+{\n+    switch (DNP3_LINK_FC(header->control)) {\n+        case DNP3_LINK_FC_CONFIRMED_USER_DATA:\n+        case DNP3_LINK_FC_UNCONFIRMED_USER_DATA:\n+            return 1;\n+        default:\n+            return 0;\n+    }\n+}\n+\n+/**\n+ * \\brief Check if the length of the header is long enough for the\n+ *     frame to contain user data.\n+ */\n+static int DNP3HasUserData(DNP3LinkHeader *header)\n+{\n+    if (DNP3_LINK_DIR(header->control)) {\n+        return header->len >= DNP3_LINK_HDR_LEN + sizeof(DNP3TransportHeader) +\n+            sizeof(DNP3ApplicationHeader);\n+    }\n+    else {\n+        return header->len >= DNP3_LINK_HDR_LEN + sizeof(DNP3TransportHeader) +\n+            sizeof(DNP3ApplicationHeader) + sizeof(DNP3InternalInd);\n+    }\n+}\n+\n+/**\n+ * DNP3 buffer helper function - reset the buffer.\n+ */\n+static void DNP3BufferReset(DNP3Buffer *buffer)\n+{\n+    buffer->offset = 0;\n+    buffer->len = 0;\n+}\n+\n+/**\n+ * DNP3 buffer helper function - add data to the buffer, making the buffer\n+ * larger if needed.\n+ */\n+static int DNP3BufferAdd(DNP3Buffer *buffer, uint8_t *buf, uint32_t len)\n+{\n+    if (buffer->size == 0) {\n+        buffer->buffer = SCCalloc(1, len);\n+        if (unlikely(buffer->buffer == NULL)) {\n+            return 0;\n+        }\n+        buffer->size = len;\n+    }\n+    else if (buffer->len + len > buffer->size) {\n+        uint8_t *tmp = SCRealloc(buffer->buffer, buffer->len + len);\n+        if (unlikely(tmp == NULL)) {\n+            return 0;\n+        }\n+        buffer->buffer = tmp;\n+        buffer->size = buffer->len + len;\n+    }\n+    memcpy(buffer->buffer + buffer->len, buf, len);\n+    buffer->len += len;\n+\n+    return 1;\n+}\n+\n+/**\n+ * DNP3 buffer helper function - pulls up the data to the beginning of the\n+ * buffer to make room at the end for more data.\n+ */\n+static void DNP3BufferTrim(DNP3Buffer *buffer)\n+{\n+    if (buffer->offset == buffer->len) {\n+        DNP3BufferReset(buffer);\n+    }\n+    else if (buffer->offset > 0) {\n+        memmove(buffer->buffer, buffer->buffer + buffer->offset,\n+            buffer->len - buffer->offset);\n+        buffer->len = buffer->len - buffer->offset;\n+        buffer->offset = 0;\n+    }\n+}\n+\n+/**\n+ * \\brief Decode DNP3 application objects.\n+ *\n+ * This function decoded known DNP3 application objects. As the\n+ * protocol isn't self describing, we can only decode the buffer while\n+ * the application objects are known.  As soon as an unknown\n+ * group/variation is hit, we must stop processing.\n+ *\n+ * \\param buf the input buffer\n+ * \\param len length of the input buffer\n+ * \\param objects pointer to list where decoded objects will be stored.\n+ *\n+ * \\retval 1 if all objects decoded, 0 if all objects could not be decoded (\n+ *    unknown group/variations)\n+ */\n+static int DNP3DecodeApplicationObjects(uint8_t *buf, uint32_t len,\n+    DNP3ObjectList *objects)\n+{\n+    int retval = 0;\n+\n+    if (buf == NULL || len == 0) {\n+        return 1;\n+    }\n+\n+    while (len) {\n+        uint32_t offset = 0;\n+\n+        if (len < sizeof(DNP3ObjHeader)) {\n+            SCLogInfo(\n+                \"Insufficient data: not enough data for object header (%d).\",\n+                len);\n+            SCLogInfo(\"%02x\", buf[2]);\n+            goto done;\n+        }\n+        DNP3ObjHeader *header = (DNP3ObjHeader *)buf;\n+        offset += sizeof(DNP3ObjHeader);\n+\n+        DNP3Object *object = SCCalloc(1, sizeof(*object));\n+        if (object != NULL) {\n+            object->group = header->group;\n+            object->variation = header->variation;\n+            TAILQ_INSERT_TAIL(objects, object, next);\n+        }\n+\n+        SCLogDebug(\"Group: 0x%02x (%d); Variation: 0x%02x (%d); \"\n+            \"Qualifier: 0x%02x (%d)\",\n+            header->group, header->group,\n+            header->variation, header->variation,\n+            header->qualifier, header->qualifier);\n+        uint8_t prefix_code = DNP3_OBJ_PREFIX(header->qualifier);\n+        uint8_t range_code = DNP3_OBJ_RANGE(header->qualifier);\n+        object->prefix = DNP3_OBJ_PREFIX(header->qualifier);\n+        object->range = DNP3_OBJ_RANGE(header->qualifier);\n+\n+        int prefix_len = DNP3_OBJ_PREFIX_CODE_MAP[prefix_code];\n+        if (prefix_len < 0) {\n+            goto unknown;\n+        }\n+\n+        uint32_t count = 0;\n+\n+        /* IEEE 1815-2012, Table 4-5. */\n+        switch (range_code) {\n+            case 0x00: {\n+                uint8_t start = buf[offset++];\n+                uint8_t stop = buf[offset++];\n+                count = stop - start + 1;\n+                break;\n+            }\n+            case 0x01: {\n+                uint16_t start = dnp3_swap16((uint16_t)(*buf));\n+                buf = buf + sizeof(uint16_t);\n+                uint16_t stop = dnp3_swap16((uint16_t)(*buf));\n+                count = stop - start + 1;\n+                break;\n+            }\n+            case 0x02: {\n+                uint32_t start = dnp3_swap32((uint32_t)(*buf));\n+                buf = buf + sizeof(uint32_t);\n+                uint32_t stop = dnp3_swap32((uint32_t)(*buf));\n+                count = stop - start + 1;\n+                break;\n+            }\n+            case 0x06:\n+                /* No range field. */\n+                count = 0;\n+                break;\n+            case 0x07:\n+                /* 1 octet count of objects. */\n+                count = buf[offset++];\n+                break;\n+            case 0x08: {\n+                /* 2 octet count of objects. */\n+                count = *(uint16_t *)(buf + offset);\n+                offset += sizeof(uint16_t);\n+                break;\n+            }\n+            case 0x03:\n+            case 0x04:\n+            case 0x05:\n+            case 0x09:\n+            case 0x0b:\n+                SCLogInfo(\"Range code 0x%02x not yet implemented.\", range_code);\n+                goto done;\n+            default:\n+                SCLogInfo(\"Range code 0x%02x is reserved.\", range_code);\n+                goto done;\n+        }\n+\n+        if (count == 0) {\n+            goto next;\n+        }\n+\n+        /* Get the size of the object - in bits. */\n+        int object_size = GROUP_VAR_SIZE_MAP[header->group][header->variation];\n+        if (object_size < 0) {\n+            goto unknown;\n+        }\n+\n+        /* Calculate the full length of the object:\n+         * (prefix + object * size) * (number of items)\n+         * then round up to the nearest byte.\n+         */\n+        int object_len_bits = ((prefix_len * 8) + object_size) * count;\n+        int object_len = object_len_bits / 8;\n+        if (object_len_bits % 8 != 0) {\n+            object_len++;\n+        }\n+        if (offset + object_len > len) {\n+            SCLogInfo(\"Insufficient data to decode object %d:%d\", header->group,\n+                header->variation);\n+            goto not_enough_data;\n+        }\n+\n+        offset += object_len;\n+    next:\n+\n+        object->data = SCCalloc(1, offset);\n+        if (object->data != NULL) {\n+            memcpy(object->data, buf, offset);\n+            object->len = offset;\n+        }\n+\n+        buf += offset;\n+        len -= offset;\n+\n+        continue;\n+\n+        /* Can't continue on an unknown group or variation as we don't\n+         * know how far to advance the buffer. */\n+\n+    unknown:\n+        SCLogInfo(\"Unknown variation 0x%02x (%d) for group 0x%02x (%d)\",\n+            header->variation, header->variation, header->group, header->group);\n+        goto done;\n+    }\n+\n+    /* All objects were decoded. */\n+    retval = 1;\n+\n+not_enough_data:\n+done:\n+    return retval;\n+}\n+\n+static int DNP3HandleRequestTransportLayer(DNP3State *dnp3, uint8_t *input,\n+    uint32_t input_len)\n+{\n+    SCEnter();\n+    DNP3LinkHeader *header;\n+    DNP3Session *session;\n+    DNP3TransportHeader th;\n+    DNP3Transaction *tx;\n+    DNP3ApplicationHeader *ah;\n+    uint32_t offset = 0;\n+    uint32_t th_offset = 0;\n+\n+    header = (DNP3LinkHeader *)input;\n+    offset += sizeof(DNP3LinkHeader);\n+\n+    th = input[offset];\n+    th_offset = offset++;\n+\n+    session = DNP3SessionGet(dnp3, header);\n+    if (unlikely(session == NULL)) {\n+        goto error;\n+    }\n+\n+    if (!DNP3_TRANSPORT_FIR(th)) {\n+        /* Need to look further into multi-segment fragments from the master. */\n+        SCLogInfo(\"Unexpected multi-frame fragment from master.\");\n+        goto done;\n+    }\n+\n+    ah = (DNP3ApplicationHeader *)(input + offset);\n+    offset += sizeof(DNP3ApplicationHeader);\n+\n+    tx = DNP3TxAlloc(dnp3);\n+    if (unlikely(tx == NULL)) {\n+        goto error;\n+    }\n+    tx->session = session;\n+    session->last_tx = tx;\n+    tx->app_seqno = DNP3_APP_SEQ(ah->control);\n+    tx->app_function_code = ah->function_code;\n+    tx->request_ll_control = header->control;\n+    tx->request_th = th;\n+    tx->request_al_control = ah->control;\n+    tx->request_al_fc = ah->function_code;\n+\n+    /* Some function codes do not expect a reply. */\n+    switch (ah->function_code) {\n+        case DNP3_APP_FC_CONFIRM:\n+        case DNP3_APP_FC_DIR_OPERATE_NR:\n+        case DNP3_APP_FC_FREEZE_NR:\n+        case DNP3_APP_FC_FREEZE_CLEAR_NR:\n+        case DNP3_APP_FC_FREEZE_AT_TIME_NR:\n+        case DNP3_APP_FC_AUTH_REQ_NR:\n+            tx->response_done = 1;\n+        default:\n+            break;\n+    }\n+\n+    /* Reassemble the request buffer. */\n+    tx->request_buffer = DNP3ReassembleApplicationLayer(\n+        input + th_offset, // + sizeof(DNP3TransportHeader),\n+            input_len - th_offset, // - sizeof(DNP3TransportHeader),\n+            &tx->request_buffer, &tx->request_buffer_len);\n+    tx->request_done = 1;\n+","path":"src/app-layer-dnp3.c","position":1152,"original_position":1152,"commit_id":"a75cb9a724896a49f5f61b1780eec872eaa9cf5f","original_commit_id":"a75cb9a724896a49f5f61b1780eec872eaa9cf5f","user":{"login":"jasonish","id":644115,"avatar_url":"https://avatars.githubusercontent.com/u/644115?v=3","gravatar_id":"","url":"https://api.github.com/users/jasonish","html_url":"https://github.com/jasonish","followers_url":"https://api.github.com/users/jasonish/followers","following_url":"https://api.github.com/users/jasonish/following{/other_user}","gists_url":"https://api.github.com/users/jasonish/gists{/gist_id}","starred_url":"https://api.github.com/users/jasonish/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jasonish/subscriptions","organizations_url":"https://api.github.com/users/jasonish/orgs","repos_url":"https://api.github.com/users/jasonish/repos","events_url":"https://api.github.com/users/jasonish/events{/privacy}","received_events_url":"https://api.github.com/users/jasonish/received_events","type":"User","site_admin":false},"body":"Yes, I think this is the ideal candidate for a malformed frame event.  We should only attempt to re-assemble \"user data\" frames, and a \"user data\" frame without data is not valid.","created_at":"2015-06-03T21:15:36Z","updated_at":"2015-06-03T21:15:36Z","html_url":"https://github.com/inliniac/suricata/pull/1510#discussion_r31670911","pull_request_url":"https://api.github.com/repos/inliniac/suricata/pulls/1510","_links":{"self":{"href":"https://api.github.com/repos/inliniac/suricata/pulls/comments/31670911"},"html":{"href":"https://github.com/inliniac/suricata/pull/1510#discussion_r31670911"},"pull_request":{"href":"https://api.github.com/repos/inliniac/suricata/pulls/1510"}}},"pull_request":{"url":"https://api.github.com/repos/inliniac/suricata/pulls/1510","id":36321827,"html_url":"https://github.com/inliniac/suricata/pull/1510","diff_url":"https://github.com/inliniac/suricata/pull/1510.diff","patch_url":"https://github.com/inliniac/suricata/pull/1510.patch","issue_url":"https://api.github.com/repos/inliniac/suricata/issues/1510","number":1510,"state":"open","locked":false,"title":"DNP3 Support - v5","user":{"login":"jasonish","id":644115,"avatar_url":"https://avatars.githubusercontent.com/u/644115?v=3","gravatar_id":"","url":"https://api.github.com/users/jasonish","html_url":"https://github.com/jasonish","followers_url":"https://api.github.com/users/jasonish/followers","following_url":"https://api.github.com/users/jasonish/following{/other_user}","gists_url":"https://api.github.com/users/jasonish/gists{/gist_id}","starred_url":"https://api.github.com/users/jasonish/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jasonish/subscriptions","organizations_url":"https://api.github.com/users/jasonish/orgs","repos_url":"https://api.github.com/users/jasonish/repos","events_url":"https://api.github.com/users/jasonish/events{/privacy}","received_events_url":"https://api.github.com/users/jasonish/received_events","type":"User","site_admin":false},"body":"Replaces #1488.\r\n\r\nUpdates from previous PR:\r\n- All static size objects decoded.\r\n- Drop payload logging for now.\r\n\r\nDNP3 support includes:\r\n- Application layer decoder.\r\n- Snort compatible keywords:\r\n  https://redmine.openinfosecfoundation.org/projects/suricata/wiki/DNP3_keyword\r\n- Request / response logging.\r\n\r\nBuildbot output:\r\n- PR jasonish-pcap: https://buildbot.openinfosecfoundation.org/builders/jasonish-pcap/builds/98\r\n- PR jasonish: https://buildbot.openinfosecfoundation.org/builders/jasonish/builds/99\r\n","created_at":"2015-05-27T16:21:23Z","updated_at":"2015-06-03T21:15:36Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7e3aeaf5130cef1239c5ff58d1b6dd6063415c3b","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/inliniac/suricata/pulls/1510/commits","review_comments_url":"https://api.github.com/repos/inliniac/suricata/pulls/1510/comments","review_comment_url":"https://api.github.com/repos/inliniac/suricata/pulls/comments{/number}","comments_url":"https://api.github.com/repos/inliniac/suricata/issues/1510/comments","statuses_url":"https://api.github.com/repos/inliniac/suricata/statuses/a75cb9a724896a49f5f61b1780eec872eaa9cf5f","head":{"label":"jasonish:ish-dnp3-v5","ref":"ish-dnp3-v5","sha":"a75cb9a724896a49f5f61b1780eec872eaa9cf5f","user":{"login":"jasonish","id":644115,"avatar_url":"https://avatars.githubusercontent.com/u/644115?v=3","gravatar_id":"","url":"https://api.github.com/users/jasonish","html_url":"https://github.com/jasonish","followers_url":"https://api.github.com/users/jasonish/followers","following_url":"https://api.github.com/users/jasonish/following{/other_user}","gists_url":"https://api.github.com/users/jasonish/gists{/gist_id}","starred_url":"https://api.github.com/users/jasonish/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jasonish/subscriptions","organizations_url":"https://api.github.com/users/jasonish/orgs","repos_url":"https://api.github.com/users/jasonish/repos","events_url":"https://api.github.com/users/jasonish/events{/privacy}","received_events_url":"https://api.github.com/users/jasonish/received_events","type":"User","site_admin":false},"repo":{"id":5414542,"name":"suricata","full_name":"jasonish/suricata","owner":{"login":"jasonish","id":644115,"avatar_url":"https://avatars.githubusercontent.com/u/644115?v=3","gravatar_id":"","url":"https://api.github.com/users/jasonish","html_url":"https://github.com/jasonish","followers_url":"https://api.github.com/users/jasonish/followers","following_url":"https://api.github.com/users/jasonish/following{/other_user}","gists_url":"https://api.github.com/users/jasonish/gists{/gist_id}","starred_url":"https://api.github.com/users/jasonish/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jasonish/subscriptions","organizations_url":"https://api.github.com/users/jasonish/orgs","repos_url":"https://api.github.com/users/jasonish/repos","events_url":"https://api.github.com/users/jasonish/events{/privacy}","received_events_url":"https://api.github.com/users/jasonish/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jasonish/suricata","description":"Mirror of the official OISF Suricata git repository","fork":true,"url":"https://api.github.com/repos/jasonish/suricata","forks_url":"https://api.github.com/repos/jasonish/suricata/forks","keys_url":"https://api.github.com/repos/jasonish/suricata/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jasonish/suricata/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jasonish/suricata/teams","hooks_url":"https://api.github.com/repos/jasonish/suricata/hooks","issue_events_url":"https://api.github.com/repos/jasonish/suricata/issues/events{/number}","events_url":"https://api.github.com/repos/jasonish/suricata/events","assignees_url":"https://api.github.com/repos/jasonish/suricata/assignees{/user}","branches_url":"https://api.github.com/repos/jasonish/suricata/branches{/branch}","tags_url":"https://api.github.com/repos/jasonish/suricata/tags","blobs_url":"https://api.github.com/repos/jasonish/suricata/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jasonish/suricata/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jasonish/suricata/git/refs{/sha}","trees_url":"https://api.github.com/repos/jasonish/suricata/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jasonish/suricata/statuses/{sha}","languages_url":"https://api.github.com/repos/jasonish/suricata/languages","stargazers_url":"https://api.github.com/repos/jasonish/suricata/stargazers","contributors_url":"https://api.github.com/repos/jasonish/suricata/contributors","subscribers_url":"https://api.github.com/repos/jasonish/suricata/subscribers","subscription_url":"https://api.github.com/repos/jasonish/suricata/subscription","commits_url":"https://api.github.com/repos/jasonish/suricata/commits{/sha}","git_commits_url":"https://api.github.com/repos/jasonish/suricata/git/commits{/sha}","comments_url":"https://api.github.com/repos/jasonish/suricata/comments{/number}","issue_comment_url":"https://api.github.com/repos/jasonish/suricata/issues/comments{/number}","contents_url":"https://api.github.com/repos/jasonish/suricata/contents/{+path}","compare_url":"https://api.github.com/repos/jasonish/suricata/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jasonish/suricata/merges","archive_url":"https://api.github.com/repos/jasonish/suricata/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jasonish/suricata/downloads","issues_url":"https://api.github.com/repos/jasonish/suricata/issues{/number}","pulls_url":"https://api.github.com/repos/jasonish/suricata/pulls{/number}","milestones_url":"https://api.github.com/repos/jasonish/suricata/milestones{/number}","notifications_url":"https://api.github.com/repos/jasonish/suricata/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jasonish/suricata/labels{/name}","releases_url":"https://api.github.com/repos/jasonish/suricata/releases{/id}","created_at":"2012-08-14T15:13:38Z","updated_at":"2015-05-08T15:33:21Z","pushed_at":"2015-06-01T19:04:53Z","git_url":"git://github.com/jasonish/suricata.git","ssh_url":"git@github.com:jasonish/suricata.git","clone_url":"https://github.com/jasonish/suricata.git","svn_url":"https://github.com/jasonish/suricata","homepage":"http://www.openinfosecfoundation.org","size":41841,"stargazers_count":0,"watchers_count":0,"language":"C","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"inliniac:master","ref":"master","sha":"07efec550dd249f3c4a1a1fb926cbbe4298245cc","user":{"login":"inliniac","id":719366,"avatar_url":"https://avatars.githubusercontent.com/u/719366?v=3","gravatar_id":"","url":"https://api.github.com/users/inliniac","html_url":"https://github.com/inliniac","followers_url":"https://api.github.com/users/inliniac/followers","following_url":"https://api.github.com/users/inliniac/following{/other_user}","gists_url":"https://api.github.com/users/inliniac/gists{/gist_id}","starred_url":"https://api.github.com/users/inliniac/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/inliniac/subscriptions","organizations_url":"https://api.github.com/users/inliniac/orgs","repos_url":"https://api.github.com/users/inliniac/repos","events_url":"https://api.github.com/users/inliniac/events{/privacy}","received_events_url":"https://api.github.com/users/inliniac/received_events","type":"User","site_admin":false},"repo":{"id":5410234,"name":"suricata","full_name":"inliniac/suricata","owner":{"login":"inliniac","id":719366,"avatar_url":"https://avatars.githubusercontent.com/u/719366?v=3","gravatar_id":"","url":"https://api.github.com/users/inliniac","html_url":"https://github.com/inliniac","followers_url":"https://api.github.com/users/inliniac/followers","following_url":"https://api.github.com/users/inliniac/following{/other_user}","gists_url":"https://api.github.com/users/inliniac/gists{/gist_id}","starred_url":"https://api.github.com/users/inliniac/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/inliniac/subscriptions","organizations_url":"https://api.github.com/users/inliniac/orgs","repos_url":"https://api.github.com/users/inliniac/repos","events_url":"https://api.github.com/users/inliniac/events{/privacy}","received_events_url":"https://api.github.com/users/inliniac/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/inliniac/suricata","description":"Mirror of the official OISF Suricata git repository","fork":false,"url":"https://api.github.com/repos/inliniac/suricata","forks_url":"https://api.github.com/repos/inliniac/suricata/forks","keys_url":"https://api.github.com/repos/inliniac/suricata/keys{/key_id}","collaborators_url":"https://api.github.com/repos/inliniac/suricata/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/inliniac/suricata/teams","hooks_url":"https://api.github.com/repos/inliniac/suricata/hooks","issue_events_url":"https://api.github.com/repos/inliniac/suricata/issues/events{/number}","events_url":"https://api.github.com/repos/inliniac/suricata/events","assignees_url":"https://api.github.com/repos/inliniac/suricata/assignees{/user}","branches_url":"https://api.github.com/repos/inliniac/suricata/branches{/branch}","tags_url":"https://api.github.com/repos/inliniac/suricata/tags","blobs_url":"https://api.github.com/repos/inliniac/suricata/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/inliniac/suricata/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/inliniac/suricata/git/refs{/sha}","trees_url":"https://api.github.com/repos/inliniac/suricata/git/trees{/sha}","statuses_url":"https://api.github.com/repos/inliniac/suricata/statuses/{sha}","languages_url":"https://api.github.com/repos/inliniac/suricata/languages","stargazers_url":"https://api.github.com/repos/inliniac/suricata/stargazers","contributors_url":"https://api.github.com/repos/inliniac/suricata/contributors","subscribers_url":"https://api.github.com/repos/inliniac/suricata/subscribers","subscription_url":"https://api.github.com/repos/inliniac/suricata/subscription","commits_url":"https://api.github.com/repos/inliniac/suricata/commits{/sha}","git_commits_url":"https://api.github.com/repos/inliniac/suricata/git/commits{/sha}","comments_url":"https://api.github.com/repos/inliniac/suricata/comments{/number}","issue_comment_url":"https://api.github.com/repos/inliniac/suricata/issues/comments{/number}","contents_url":"https://api.github.com/repos/inliniac/suricata/contents/{+path}","compare_url":"https://api.github.com/repos/inliniac/suricata/compare/{base}...{head}","merges_url":"https://api.github.com/repos/inliniac/suricata/merges","archive_url":"https://api.github.com/repos/inliniac/suricata/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/inliniac/suricata/downloads","issues_url":"https://api.github.com/repos/inliniac/suricata/issues{/number}","pulls_url":"https://api.github.com/repos/inliniac/suricata/pulls{/number}","milestones_url":"https://api.github.com/repos/inliniac/suricata/milestones{/number}","notifications_url":"https://api.github.com/repos/inliniac/suricata/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/inliniac/suricata/labels{/name}","releases_url":"https://api.github.com/repos/inliniac/suricata/releases{/id}","created_at":"2012-08-14T08:30:06Z","updated_at":"2015-06-01T08:00:23Z","pushed_at":"2015-06-03T16:25:08Z","git_url":"git://github.com/inliniac/suricata.git","ssh_url":"git@github.com:inliniac/suricata.git","clone_url":"https://github.com/inliniac/suricata.git","svn_url":"https://github.com/inliniac/suricata","homepage":"http://suricata-ids.org/","size":63494,"stargazers_count":176,"watchers_count":176,"language":"C","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":164,"mirror_url":null,"open_issues_count":23,"forks":164,"open_issues":23,"watchers":176,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/inliniac/suricata/pulls/1510"},"html":{"href":"https://github.com/inliniac/suricata/pull/1510"},"issue":{"href":"https://api.github.com/repos/inliniac/suricata/issues/1510"},"comments":{"href":"https://api.github.com/repos/inliniac/suricata/issues/1510/comments"},"review_comments":{"href":"https://api.github.com/repos/inliniac/suricata/pulls/1510/comments"},"review_comment":{"href":"https://api.github.com/repos/inliniac/suricata/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/inliniac/suricata/pulls/1510/commits"},"statuses":{"href":"https://api.github.com/repos/inliniac/suricata/statuses/a75cb9a724896a49f5f61b1780eec872eaa9cf5f"}}}}