]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #2343 in SNORT/snort3 from ~MDAGON/snort3:translate to master
authorMike Stepanek (mstepane) <mstepane@cisco.com>
Tue, 28 Jul 2020 17:15:33 +0000 (17:15 +0000)
committerMike Stepanek (mstepane) <mstepane@cisco.com>
Tue, 28 Jul 2020 17:15:33 +0000 (17:15 +0000)
Squashed commit of the following:

commit 0c98ff023f2575ab3a36c8b09c6fa62de234913f
Author: mdagon <mdagon@cisco.com>
Date:   Fri Jul 10 11:25:18 2020 -0400

    payload_injector: add HTTP page translation

src/payload_injector/CMakeLists.txt
src/payload_injector/dev_notes.txt
src/payload_injector/payload_injector_module.cc
src/payload_injector/payload_injector_module.h
src/payload_injector/payload_injector_translate_page.cc [new file with mode: 0644]
src/payload_injector/test/CMakeLists.txt
src/payload_injector/test/payload_injector_translate_test.cc [new file with mode: 0644]

index b3d82db5efb1f3d961b694aeb0fbf840a7cc6688..cf9c91772d4af39b88c2899580e0ae529591fd3c 100644 (file)
@@ -5,6 +5,7 @@ set (PAYLOAD_INJECTOR_INCLUDES
 add_library ( payload_injector OBJECT
     ${PAYLOAD_INJECTOR_INCLUDES}
     payload_injector_module.cc
+    payload_injector_translate_page.cc
 )
 
 install(FILES ${PAYLOAD_INJECTOR_INCLUDES}
index 8a16a8c1a5af32da3fa235b9ca751a2a24bb7300..0c99cf1d19a52421c64e4ca58a39024808dccc50 100644 (file)
@@ -6,4 +6,9 @@ It coordinates with the Active component of Snort to perform these functions.
 
 Currently it is being used for HTTP/1 injections. HTTP/2 support is in development.
 
+get_http2_payload supports translation of HTTP block/redirect page to HTTP2.
+Current implementation is limited, the constraints are specified in
+payload_injector_translate_page.cc.
+
+
 
index 919df8ffc3085a58c899bc297dec462e37449898..8a1d33a645bce40d55bcf247c8854f24a6d3d966 100644 (file)
@@ -62,7 +62,7 @@ bool PayloadInjectorModule::end(const char*, int, SnortConfig*)
 }
 
 InjectionReturnStatus PayloadInjectorModule::inject_http_payload(Packet* p,
-    InjectionControl& control)
+    const InjectionControl& control)
 {
     InjectionReturnStatus status = INJECTION_SUCCESS;
 
index 67717ede3b723895566837ca9875dd0cc0267fa4..7bd6eb241b33d955778fec3b71f9edc7f1041fed 100644 (file)
@@ -43,6 +43,7 @@ enum InjectionReturnStatus : int8_t
     ERR_STREAM_NOT_ESTABLISHED = -2,
     ERR_HTTP2_STREAM_ID_0 = -3,
     ERR_UNIDENTIFIED_PROTOCOL = -4,
+    ERR_PAGE_TRANSLATION = -4,
 };
 
 struct InjectionControl
@@ -64,7 +65,7 @@ public:
 
     bool end(const char*, int, snort::SnortConfig*) override;
 
-    static InjectionReturnStatus inject_http_payload(snort::Packet* p, InjectionControl& control);
+    static InjectionReturnStatus inject_http_payload(snort::Packet* p, const InjectionControl& control);
 
 #ifdef UNIT_TEST
     void set_configured(bool val) { configured = val; }
@@ -72,6 +73,11 @@ public:
 
 private:
     static bool configured;
+
+#ifdef UNIT_TEST
+public:
+#endif
+    static InjectionReturnStatus get_http2_payload(InjectionControl control, uint8_t *& http2_payload, uint32_t & payload_len);
 };
 
 #endif
diff --git a/src/payload_injector/payload_injector_translate_page.cc b/src/payload_injector/payload_injector_translate_page.cc
new file mode 100644 (file)
index 0000000..c0179af
--- /dev/null
@@ -0,0 +1,247 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// payload_injector_translate_page.cc author Maya Dagon <mdagon@cisco.com>
+
+// Translates HTTP 1.1 block/redirect page to HTTP2.
+// 1. Headers are separated by /r/n
+// 2. Headers end with /r/n
+// 3. Must have headers and body
+// 4. Translated header length <= 2000
+// 5. Supported: HTTP/1.1 403, HTTP/1.1 307, Connection: close,
+//     Content-Length: , Content-Type: , Set-Cookie: , Location:
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "payload_injector_module.h"
+
+#include "service_inspectors/http2_inspect/http2_enum.h"
+#include "utils/util.h"
+
+static const char status_403[] = "HTTP/1.1 403";
+static uint8_t status_403_h2[] = { 0, 7, ':', 's', 't', 'a', 't', 'u', 's', 3, '4', '0', '3' };
+static const char status_307[] = "HTTP/1.1 307";
+static uint8_t status_307_h2[] = { 0, 7, ':', 's', 't', 'a', 't', 'u', 's', 3, '3', '0', '7' };
+static const char connection[] = "Connection: close";
+static const uint8_t connection_h2[] = { 0, 10, 'c','o','n','n','e','c','t','i','o','n',
+                                         5, 'c', 'l', 'o', 's', 'e' };
+static const char content_length[] = "Content-Length: ";
+static const char content_type[] = "Content-Type: ";
+static const char cookie[] = "Set-Cookie: ";
+static const char location[] = "Location: ";
+
+static const uint32_t max_hdr_size = 2000;
+
+// Write literal header field
+static InjectionReturnStatus write_indexed(char* hdr, uint32_t len, uint8_t*& out,
+    uint32_t& out_free_space, const uint8_t* ind, uint8_t ind_size)
+{
+    const char* sep = (char*)memchr(hdr,':',len);
+    assert(sep != nullptr);
+    const uint32_t skip_len = strlen(": ");
+    assert((sep - hdr) >= skip_len);
+    const uint32_t val_len = len - (sep - hdr) - skip_len;
+    const uint8_t max_val_len = (1<<7) - 1; // FIXIT-E bigger than this will have to be 7 bit
+                                            // prefix
+                                            // encoded - currently not supported
+    if ((val_len == 0) || (val_len > max_val_len))
+        return ERR_PAGE_TRANSLATION;
+
+    if (out_free_space < (val_len + 1 + ind_size))
+    {
+#ifndef UNIT_TEST
+        assert(false);  // increase max_hdr_size
+#endif
+        return ERR_PAGE_TRANSLATION;
+    }
+
+    memcpy(out, ind, ind_size);
+    out += ind_size;
+    out[0] = val_len;
+    memcpy(out + 1, sep + skip_len, val_len);
+    out += 1 + val_len;
+    out_free_space -= val_len + 1 + ind_size;
+
+    return INJECTION_SUCCESS;
+}
+
+// Write fixed translation
+static InjectionReturnStatus write_translation(uint8_t*& out, uint32_t& out_free_space,
+    const uint8_t* translation, uint8_t size)
+{
+    if (out_free_space < size)
+    {
+#ifndef UNIT_TEST
+        assert(false);  // increase max_hdr_size
+#endif
+        return ERR_PAGE_TRANSLATION;
+    }
+
+    memcpy(out, translation, size);
+    out += size;
+    out_free_space -= size;
+
+    return INJECTION_SUCCESS;
+}
+
+static InjectionReturnStatus translate_hdr_field(char* hdr, uint32_t len, uint8_t*& out,
+    uint32_t& out_free_space)
+{
+    if (len > strlen(status_403) && memcmp(hdr, status_403, strlen(status_403)) == 0)
+    {
+        return write_translation(out, out_free_space,status_403_h2, sizeof(status_403_h2));
+    }
+    else if (len > strlen(status_307) && memcmp(hdr, status_307, strlen(status_307)) == 0)
+    {
+        return write_translation(out, out_free_space,status_307_h2, sizeof(status_307_h2));
+    }
+    else if (len == strlen(connection) && memcmp(hdr, connection, strlen(connection))==0)
+    {
+        return write_translation(out, out_free_space, connection_h2, sizeof(connection_h2));
+    }
+    // The following use literal header field without indexing.
+    // The header field name index to the static table is represented using 4-bit prefix.
+    else if (len > strlen(content_length) && memcmp(hdr, content_length, strlen(content_length))==
+        0)
+    {
+        const uint8_t ind_rep[] = { 0xf, 0xd }; // 0000 + 28 in 4 bit prefix
+        return write_indexed(hdr, len, out, out_free_space, ind_rep, sizeof(ind_rep));
+    }
+    else if (len > strlen(content_type) && memcmp(hdr, content_type, strlen(content_type))==0)
+    {
+        const uint8_t ind_rep[] = { 0xf, 0x10 }; // 0000 + 31 in 4 bit prefix
+        return write_indexed(hdr, len, out, out_free_space, ind_rep, sizeof(ind_rep));
+    }
+    else if (len > strlen(cookie) && memcmp(hdr, cookie, strlen(cookie))==0)
+    {
+        const uint8_t ind_rep[] = { 0xf, 0x28 }; // 0000 + 55 in 4 bit prefix
+        return write_indexed(hdr, len, out, out_free_space, ind_rep, sizeof(ind_rep));
+    }
+    else if (len > strlen(location) && memcmp(hdr, location, strlen(location))==0)
+    {
+        const uint8_t ind_rep[] = { 0xf, 0x1f }; // 0000 + 46 in 4 bit prefix
+        return write_indexed(hdr, len, out, out_free_space, ind_rep, sizeof(ind_rep));
+    }
+    else
+        return ERR_PAGE_TRANSLATION;
+}
+
+static InjectionReturnStatus get_http2_hdr(char* http_page, uint32_t len,
+    uint8_t* http2_hdr, uint32_t& hdr_len, uint32_t& body_offset)
+{
+    InjectionReturnStatus status = ERR_PAGE_TRANSLATION;
+    body_offset = 0;
+
+    uint32_t hdr_free_space = max_hdr_size;
+    char* page_cur = http_page;
+    uint8_t* hdr_cur = http2_hdr;
+    while ((page_cur - http_page) < len)
+    {
+        char* cr_newline = strstr(page_cur, "\r\n");
+        if (cr_newline != nullptr)
+        {
+            if (cr_newline == page_cur)
+            {
+                // reached end of headers
+                if ((page_cur - http_page + 2) < len)
+                    body_offset = page_cur - http_page + 2;
+                break;
+            }
+            status = translate_hdr_field(page_cur, cr_newline-page_cur, hdr_cur, hdr_free_space);
+            if (status != INJECTION_SUCCESS)
+                break;
+            page_cur = cr_newline + 2;
+        }
+        else
+            break;
+    }
+
+    if (status == ERR_PAGE_TRANSLATION || body_offset == 0)
+        return ERR_PAGE_TRANSLATION;
+
+    hdr_len = hdr_cur - http2_hdr;
+
+    return INJECTION_SUCCESS;
+}
+
+static void write_3_bytes_of_int(uint8_t* out, uint32_t val)
+{
+#ifdef WORDS_BIGENDIAN
+    out[2] = (val & (0xff000000)) >> 24;
+    out[1] = (val & (0xff0000)) >> 16;
+    out[0] = (val & (0xff00)) >> 8;
+#else
+    out[2] = val & 0xff;
+    out[1] = (val & (0xff00)) >> 8;
+    out[0] = (val & (0xff0000)) >> 16;
+#endif
+}
+
+static void write_frame_hdr(uint8_t*& out, uint32_t len, uint8_t type, uint8_t flags, uint32_t
+    stream_id)
+{
+    write_3_bytes_of_int(out, len);
+    out[3] = type;
+    out[4] = flags;
+    stream_id = htonl(stream_id);
+    memcpy(out+5, &stream_id, 4);
+    out += Http2Enums::FRAME_HEADER_LENGTH;
+}
+
+InjectionReturnStatus PayloadInjectorModule::get_http2_payload(InjectionControl control,
+    uint8_t*& http2_payload, uint32_t& payload_len)
+{
+    if (control.http_page == nullptr || control.http_page_len == 0)
+        return ERR_PAGE_TRANSLATION;
+
+    // create a string version to run with strstr
+    char* page_string = (char*)snort_alloc(control.http_page_len + 1);
+    memcpy(page_string, control.http_page, control.http_page_len);
+    page_string[control.http_page_len] = '\0';
+
+    uint8_t http2_hdr[max_hdr_size];
+    uint32_t hdr_len, body_offset;
+    InjectionReturnStatus status = get_http2_hdr(page_string, control.http_page_len, http2_hdr,
+        hdr_len, body_offset);
+
+    snort_free(page_string);
+
+    if (status == ERR_PAGE_TRANSLATION)
+        return status;
+
+    const uint32_t body_len = control.http_page_len - body_offset;
+    // FIXIT-E support larger body size
+    if (body_len > 1<<14)
+        return ERR_PAGE_TRANSLATION;
+
+    payload_len = 2*Http2Enums::FRAME_HEADER_LENGTH + hdr_len + body_len;
+    http2_payload = (uint8_t*)snort_alloc(payload_len);
+
+    uint8_t* http2_payload_cur = http2_payload;
+    // FIXIT-E update flags
+    write_frame_hdr(http2_payload_cur, hdr_len, Http2Enums::FT_HEADERS, 0, control.stream_id);
+    memcpy(http2_payload_cur, http2_hdr, hdr_len);
+    http2_payload_cur += hdr_len;
+    write_frame_hdr(http2_payload_cur, body_len, Http2Enums::FT_DATA, 0, control.stream_id);
+    memcpy(http2_payload_cur, control.http_page + body_offset, body_len);
+
+    return INJECTION_SUCCESS;
+}
+
index 693bcc56f8709d499e685758b35563b99dba1a3c..5312339746c4694c3874ba41f964bf258c11d4d6 100644 (file)
@@ -3,3 +3,8 @@ add_cpputest (payload_injector_test
       ../payload_injector_module.cc
       ../../framework/module.cc
 )
+
+add_cpputest (payload_injector_translate_test
+  SOURCES
+      ../payload_injector_translate_page.cc
+)
diff --git a/src/payload_injector/test/payload_injector_translate_test.cc b/src/payload_injector/test/payload_injector_translate_test.cc
new file mode 100644 (file)
index 0000000..f60ec1f
--- /dev/null
@@ -0,0 +1,392 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+
+// payload_injector_translate_test.cc author Maya Dagon <mdagon@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "payload_injector/payload_injector_module.h"
+
+#include "utils/util.h"
+
+#include <CppUTest/CommandLineTestRunner.h>
+#include <CppUTest/TestHarness.h>
+
+TEST_GROUP(payload_injector_translate_test)
+{
+    uint8_t* http2_payload;
+    uint32_t payload_len;
+    InjectionReturnStatus status;
+};
+
+TEST(payload_injector_translate_test, basic_hdr_translation)
+{
+    char http_page[] = "HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: "
+        "504\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n<!DOCTYPE html>\n<html><head>\n<meta"
+        "http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />\n";
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = (uint8_t*)http_page;
+    control.http_page_len = strlen(http_page);
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+    CHECK(status == INJECTION_SUCCESS);
+
+    uint8_t out[] = { 0x0, 0x0, 0x40, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x7, 0x3a, 0x73, 0x74,
+                      0x61, 0x74, 0x75, 0x73, 0x3, 0x34, 0x30, 0x33, 0x0, 0xa, 0x63, 0x6f, 0x6e,
+                      0x6e, 0x65,
+                      0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0xf, 0xd,
+                      0x3, 0x35,
+                      0x30, 0x34, 0xf, 0x10, 0x18, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d,
+                      0x6c,
+                      0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x55, 0x54, 0x46,
+                      0x2d,
+                      0x38, 0x0, 0x0, 0x62, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3c, 0x21, 0x44, 0x4f,
+                      0x43, 0x54,
+                      0x59, 0x50, 0x45, 0x20, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0xa, 0x3c, 0x68, 0x74,
+                      0x6d,
+                      0x6c, 0x3e, 0x3c, 0x68, 0x65, 0x61, 0x64, 0x3e, 0xa, 0x3c, 0x6d, 0x65, 0x74,
+                      0x61,
+                      0x68, 0x74, 0x74, 0x70, 0x2d, 0x65, 0x71, 0x75, 0x69, 0x76, 0x3d, 0x22, 0x63,
+                      0x6f,
+                      0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x22, 0x20, 0x63,
+                      0x6f,
+                      0x6e, 0x74, 0x65, 0x6e, 0x74, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x68,
+                      0x74,
+                      0x6d, 0x6c, 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x55,
+                      0x54,
+                      0x46, 0x2d, 0x38, 0x22, 0x20, 0x2f, 0x3e, 0xa };
+    CHECK(payload_len == sizeof(out));
+    CHECK(memcmp(http2_payload, out, payload_len) == 0);
+
+    snort_free(http2_payload);
+}
+
+TEST(payload_injector_translate_test, basic_hdr_translation2)
+{
+    char http_page[] =
+        "HTTP/1.1 307 Proxy Redirect\r\nLocation: https://\r\nSet-Cookie: 04f2; Max-Age: 600; path=/;\r\n\r\nBody\n";
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = (uint8_t*)http_page;
+    control.http_page_len = strlen(http_page);
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == INJECTION_SUCCESS);
+
+    uint8_t out[] = { 0x0, 0x0, 0x36, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x7, 0x3a, 0x73, 0x74,
+                      0x61, 0x74, 0x75, 0x73, 0x3, 0x33, 0x30, 0x37, 0xf, 0x1f, 0x8, 0x68, 0x74,
+                      0x74, 0x70,
+                      0x73, 0x3a, 0x2f, 0x2f, 0xf, 0x28, 0x1b, 0x30, 0x34, 0x66, 0x32, 0x3b, 0x20,
+                      0x4d,
+                      0x61, 0x78, 0x2d, 0x41, 0x67, 0x65, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x3b, 0x20,
+                      0x70,
+                      0x61, 0x74, 0x68, 0x3d, 0x2f, 0x3b, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0,
+                      0x1, 0x42,
+                      0x6f, 0x64, 0x79, 0xa };
+    CHECK(payload_len == sizeof(out));
+    CHECK(memcmp(http2_payload, out, payload_len) == 0);
+
+    snort_free(http2_payload);
+}
+
+TEST(payload_injector_translate_test, only_body)
+{
+    char http_page[] = "\r\nbody";
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = (uint8_t*)http_page;
+    control.http_page_len = strlen(http_page);
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+TEST(payload_injector_translate_test, no_body)
+{
+    char http_page[] =
+        "HTTP/1.1 307 Proxy Redirect\r\nLocation: https://\r\nSet-Cookie: 04f2; Max-Age: 600; path=/;\r\n\r\n";
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = (uint8_t*)http_page;
+    control.http_page_len = strlen(http_page);
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+TEST(payload_injector_translate_test, missing_last_rn)
+{
+    char http_page[] =
+        "HTTP/1.1 307 Proxy Redirect\r\nLocation: https://\r\nSet-Cookie: 04f2; Max-Age: 600; path=/;\r\nBody\n";
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = (uint8_t*)http_page;
+    control.http_page_len = strlen(http_page);
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+TEST(payload_injector_translate_test, missing_space_after_colon)
+{
+    char http_page[] =
+        "HTTP/1.1 307 Proxy Redirect\r\nLocation:https://\r\nSet-Cookie: 04f2; Max-Age: 600; path=/;\r\n\r\nBody\n";
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = (uint8_t*)http_page;
+    control.http_page_len = strlen(http_page);
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+TEST(payload_injector_translate_test, extra_space_before_colon)
+{
+    char http_page[] =
+        "HTTP/1.1 307 Proxy Redirect\r\nLocation :https://\r\nSet-Cookie: 04f2; Max-Age: 600; path=/;\r\n\r\nBody\n";
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = (uint8_t*)http_page;
+    control.http_page_len = strlen(http_page);
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+TEST(payload_injector_translate_test, unsuporrted_status)
+{
+    char http_page[] =
+        "HTTP/1.1 200 OK\r\nLocation: https://\r\nSet-Cookie: 04f2; Max-Age: 600; path=/;\r\n\r\nBody\n";
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = (uint8_t*)http_page;
+    control.http_page_len = strlen(http_page);
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+TEST(payload_injector_translate_test, unsupported_hdr)
+{
+    char http_page[] =
+        "HTTP/1.1 307 Proxy Redirect\r\nLocation:https://\r\nRetry-after: 120\r\n\r\nBody\n";
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = (uint8_t*)http_page;
+    control.http_page_len = strlen(http_page);
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+TEST(payload_injector_translate_test, hdr_ends_wo_value)
+{
+    char http_page[] = "HTTP/1.1 307 Proxy Redirect\r\nLocation: ";
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = (uint8_t*)http_page;
+    control.http_page_len = strlen(http_page);
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+TEST(payload_injector_translate_test, missing_value)
+{
+    char http_page[] = "HTTP/1.1 307 Proxy Redirect\r\nLocation: \r\n\r\nBody\n";
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = (uint8_t*)http_page;
+    control.http_page_len = strlen(http_page);
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+// Header value has maximum supported length + 1
+TEST(payload_injector_translate_test, val_len_too_big)
+{
+    const uint32_t size = strlen("Location: ") + 128 + strlen("\r\n\r\nbody");
+    uint8_t http_page[size];
+    memset(http_page, 'a', size);
+    memcpy(http_page, "Location: ", strlen("Location: "));
+    memcpy(http_page+128+strlen("Location: "), "\r\n\r\nbody", strlen("\r\n\r\nbody"));
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = http_page;
+    control.http_page_len = size;
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+// Header value has maximum supported length 127
+TEST(payload_injector_translate_test, max_val)
+{
+    const uint32_t size = strlen("Location: ") + 127 + strlen("\r\n\r\nbody");
+    uint8_t http_page[size];
+    memset(http_page, 'a', size);
+    memcpy(http_page, "Location: ", strlen("Location: "));
+    memcpy(http_page+127+strlen("Location: "), "\r\n\r\nbody", strlen("\r\n\r\nbody"));
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = http_page;
+    control.http_page_len = size;
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == INJECTION_SUCCESS);
+    snort_free(http2_payload);
+}
+
+// Translated header is exactly 2000.
+// Verify correct frame length when length is more than 1 byte.
+// Verify correct behavior for body length is 1.
+TEST(payload_injector_translate_test, http2_hdr_is_max)
+{
+    const uint32_t size = strlen("Connection: close\r\n") * 110 + strlen("Location: ") +
+        strlen("\r\n\r\nb") + 17;
+    uint8_t http_page[size];
+
+    memset(http_page, 'a', size);
+    uint8_t* cur_pos = http_page;
+    for (int i=0; i < 110; i++)
+    {
+        memcpy(cur_pos, "Connection: close\r\n", strlen("Connection: close\r\n"));
+        cur_pos += strlen("Connection: close\r\n");
+    }
+    memcpy(cur_pos, "Location: ", strlen("Location: "));
+    memcpy(http_page+size-strlen("\r\n\r\nb"), "\r\n\r\nb", strlen("\r\n\r\nb"));
+
+    InjectionControl control;
+    control.http_page = http_page;
+    control.http_page_len = size;
+    control.stream_id = 0xf000;
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == INJECTION_SUCCESS);
+    CHECK(payload_len == 2019);
+    uint8_t hdr[] = { 0x0, 0x7, 0xd0, 0x1, 0x0, 0x0, 0x0, 0xf0, 0x0 };
+    CHECK(memcmp(http2_payload, hdr, sizeof(hdr))==0);
+    uint8_t body[] = { 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0xf0, 0x0, 'b' };
+    CHECK(memcmp(http2_payload + 2009, body, sizeof(body))==0);
+    snort_free(http2_payload);
+}
+
+// Translated header is 2001. Goes through write_indexed code path.
+TEST(payload_injector_translate_test, http2_hdr_too_big)
+{
+    const uint32_t size = strlen("Connection: close\r\n") * 110 + strlen("Location: ") +
+        strlen("\r\n\r\nbody") + 18;
+    uint8_t http_page[size];
+
+    memset(http_page, 'a', size);
+    uint8_t* cur_pos = http_page;
+    for (int i=0; i < 110; i++)
+    {
+        memcpy(cur_pos, "Connection: close\r\n", strlen("Connection: close\r\n"));
+        cur_pos += strlen("Connection: close\r\n");
+    }
+    memcpy(cur_pos, "Location: ", strlen("Location: "));
+    memcpy(http_page+size-strlen("\r\n\r\nbody"), "\r\n\r\nbody", strlen("\r\n\r\nbody"));
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = http_page;
+    control.http_page_len = size;
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+// Translated header > 2000. Goes through write_translation code path.
+TEST(payload_injector_translate_test, http2_hdr_too_big2)
+{
+    const uint32_t size = strlen("Connection: close\r\n") * 112 + strlen("\r\nbody");
+    uint8_t http_page[size];
+
+    uint8_t* cur_pos = http_page;
+    for (int i=0; i < 112; i++)
+    {
+        memcpy(cur_pos, "Connection: close\r\n", strlen("Connection: close\r\n"));
+        cur_pos += strlen("Connection: close\r\n");
+    }
+    memcpy(http_page+size-strlen("\r\nbody"), "\r\nbody", strlen("\r\nbody"));
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = http_page;
+    control.http_page_len = size;
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+TEST(payload_injector_translate_test, payload_body_larger_than_max)
+{
+    static const uint32_t size = (1<<14) + 1 + strlen("HTTP/1.1 403 Forbidden\r\n\r\n");
+    uint8_t http_page[size];
+    memset(http_page,'a',size);
+    memcpy(http_page,"HTTP/1.1 403 Forbidden\r\n\r\n", strlen("HTTP/1.1 403 Forbidden\r\n\r\n"));
+
+    InjectionControl control;
+    control.stream_id = 1;
+    control.http_page = http_page;
+    control.http_page_len = size;
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+TEST(payload_injector_translate_test, http_page_is_nullptr)
+{
+    InjectionControl control;
+    control.http_page = nullptr;
+    control.http_page_len = 1;
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+TEST(payload_injector_translate_test, http_page_is_0_length)
+{
+    uint8_t http_page[] = {1};
+
+    InjectionControl control;
+    control.http_page = http_page;
+    control.http_page_len = 0;
+    status = PayloadInjectorModule::get_http2_payload(control, http2_payload, payload_len);
+    CHECK(status == ERR_PAGE_TRANSLATION);
+}
+
+int main(int argc, char** argv)
+{
+    return CommandLineTestRunner::RunAllTests(argc, argv);
+}
+