]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
http2: initial support
authorPhilippe Antoine <contact@catenacyber.fr>
Mon, 13 Jul 2020 15:07:20 +0000 (17:07 +0200)
committerVictor Julien <victor@inliniac.net>
Thu, 6 Aug 2020 14:31:05 +0000 (16:31 +0200)
35 files changed:
rules/http2-events.rules [new file with mode: 0644]
rust/src/filecontainer.rs
rust/src/filetracker.rs
rust/src/http2/detect.rs [new file with mode: 0644]
rust/src/http2/files.rs [new file with mode: 0644]
rust/src/http2/http2.rs [new file with mode: 0644]
rust/src/http2/huffman.rs [new file with mode: 0644]
rust/src/http2/logger.rs [new file with mode: 0644]
rust/src/http2/mod.rs [new file with mode: 0644]
rust/src/http2/parser.rs [new file with mode: 0644]
rust/src/lib.rs
src/Makefile.am
src/alert-prelude.c
src/app-layer-http2.c [new file with mode: 0644]
src/app-layer-http2.h [new file with mode: 0644]
src/app-layer-parser.c
src/app-layer-protos.c
src/app-layer-protos.h
src/detect-engine-register.c
src/detect-engine-register.h
src/detect-file-data.c
src/detect-filemagic.c
src/detect-http2.c [new file with mode: 0644]
src/detect-http2.h [new file with mode: 0644]
src/output-json-alert.c
src/output-json-file.c
src/output-json-http2.c [new file with mode: 0644]
src/output-json-http2.h [new file with mode: 0644]
src/output.c
src/suricata-common.h
src/tests/detect-http2.c [new file with mode: 0644]
src/util-error.c
src/util-error.h
src/util-profiling.c
suricata.yaml.in

diff --git a/rules/http2-events.rules b/rules/http2-events.rules
new file mode 100644 (file)
index 0000000..9429edb
--- /dev/null
@@ -0,0 +1,15 @@
+# HTTP2 app layer event rules
+#
+# SID's fall in the 2290000+ range. See https://redmine.openinfosecfoundation.org/projects/suricata/wiki/AppLayer
+#
+# These sigs fire at most once per connection.
+#
+
+alert http2 any any -> any any (msg:"SURICATA HTTP2 invalid frame header"; flow:established; app-layer-event:http2.invalid_frame_header; classtype:protocol-command-decode; sid:2290000; rev:1;)
+alert http2 any any -> any any (msg:"SURICATA HTTP2 invalid client magic"; flow:established; app-layer-event:http2.invalid_client_magic; classtype:protocol-command-decode; sid:2290001; rev:1;)
+alert http2 any any -> any any (msg:"SURICATA HTTP2 invalid frame data"; flow:established; app-layer-event:http2.invalid_frame_data; classtype:protocol-command-decode; sid:2290002; rev:1;)
+alert http2 any any -> any any (msg:"SURICATA HTTP2 invalid header"; flow:established; app-layer-event:http2.invalid_header; classtype:protocol-command-decode; sid:2290003; rev:1;)
+alert http2 any any -> any any (msg:"SURICATA HTTP2 invalid frame length"; flow:established; app-layer-event:http2.invalid_frame_length; classtype:protocol-command-decode; sid:2290004; rev:1;)
+alert http2 any any -> any any (msg:"SURICATA HTTP2 header frame with extra data"; flow:established; app-layer-event:http2.extra_header_data; classtype:protocol-command-decode; sid:2290005; rev:1;)
+alert http2 any any -> any any (msg:"SURICATA HTTP2 too long frame data"; flow:established; app-layer-event:http2.long_frame_data; classtype:protocol-command-decode; sid:2290006; rev:1;)
+alert http2 any any -> any any (msg:"SURICATA HTTP2 stream identifier reuse"; flow:established; app-layer-event:http2.stream_id_reuse; classtype:protocol-command-decode; sid:2290007; rev:1;)
index 38520cfa9ea12fbd0c977bc0a32509d163f62ded..6f67d88b94953e3136f54e275e5f5f4a6f8b80ba 100644 (file)
@@ -21,6 +21,11 @@ use std::os::raw::{c_void};
 use crate::log::*;
 use crate::core::*;
 
+// Defined in util-file.h
+extern {
+    pub fn FileFlowToFlags(flow: *const Flow, flags: u8) -> u16;
+}
+
 pub struct File;
 #[repr(C)]
 #[derive(Debug)]
index ad1402f66c2bd36f18d9f134146dfc786fc0791c..1f6534196f100e75a04b6097cfa6d4794155e4db 100644 (file)
@@ -52,7 +52,7 @@ impl FileChunk {
 #[derive(Debug)]
 pub struct FileTransferTracker {
     file_size: u64,
-    tracked: u64,
+    pub tracked: u64,
     cur_ooo: u64,   // how many bytes do we have queued from ooo chunks
     track_id: u32,
     chunk_left: u32,
diff --git a/rust/src/http2/detect.rs b/rust/src/http2/detect.rs
new file mode 100644 (file)
index 0000000..28f100d
--- /dev/null
@@ -0,0 +1,542 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use super::http2::{HTTP2FrameTypeData, HTTP2Transaction};
+use super::parser;
+use crate::core::STREAM_TOSERVER;
+use std::ffi::CStr;
+use std::mem::transmute;
+use std::str::FromStr;
+
+fn http2_tx_has_frametype(
+    tx: &mut HTTP2Transaction, direction: u8, value: u8,
+) -> std::os::raw::c_int {
+    if direction & STREAM_TOSERVER != 0 {
+        for i in 0..tx.frames_ts.len() {
+            if tx.frames_ts[i].header.ftype as u8 == value {
+                return 1;
+            }
+        }
+    } else {
+        for i in 0..tx.frames_tc.len() {
+            if tx.frames_tc[i].header.ftype as u8 == value {
+                return 1;
+            }
+        }
+    }
+    return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_tx_has_frametype(
+    tx: *mut std::os::raw::c_void, direction: u8, value: u8,
+) -> std::os::raw::c_int {
+    let tx = cast_pointer!(tx, HTTP2Transaction);
+    return http2_tx_has_frametype(tx, direction, value);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_parse_frametype(
+    str: *const std::os::raw::c_char,
+) -> std::os::raw::c_int {
+    let ft_name: &CStr = CStr::from_ptr(str); //unsafe
+    if let Ok(s) = ft_name.to_str() {
+        if let Ok(x) = parser::HTTP2FrameType::from_str(s) {
+            return x as i32;
+        }
+    }
+    return -1;
+}
+
+fn http2_tx_has_errorcode(
+    tx: &mut HTTP2Transaction, direction: u8, code: u32,
+) -> std::os::raw::c_int {
+    if direction & STREAM_TOSERVER != 0 {
+        for i in 0..tx.frames_ts.len() {
+            match tx.frames_ts[i].data {
+                HTTP2FrameTypeData::GOAWAY(goaway) => {
+                    if goaway.errorcode == code {
+                        return 1;
+                    }
+                }
+                HTTP2FrameTypeData::RSTSTREAM(rst) => {
+                    if rst.errorcode == code {
+                        return 1;
+                    }
+                }
+                _ => {}
+            }
+        }
+    } else {
+        for i in 0..tx.frames_tc.len() {
+            match tx.frames_tc[i].data {
+                HTTP2FrameTypeData::GOAWAY(goaway) => {
+                    if goaway.errorcode as u32 == code {
+                        return 1;
+                    }
+                }
+                HTTP2FrameTypeData::RSTSTREAM(rst) => {
+                    if rst.errorcode as u32 == code {
+                        return 1;
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+    return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_tx_has_errorcode(
+    tx: *mut std::os::raw::c_void, direction: u8, code: u32,
+) -> std::os::raw::c_int {
+    let tx = cast_pointer!(tx, HTTP2Transaction);
+    return http2_tx_has_errorcode(tx, direction, code);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_parse_errorcode(
+    str: *const std::os::raw::c_char,
+) -> std::os::raw::c_int {
+    let ft_name: &CStr = CStr::from_ptr(str); //unsafe
+    if let Ok(s) = ft_name.to_str() {
+        if let Ok(x) = parser::HTTP2ErrorCode::from_str(s) {
+            return x as i32;
+        }
+    }
+    return -1;
+}
+
+fn http2_tx_get_next_priority(
+    tx: &mut HTTP2Transaction, direction: u8, nb: u32,
+) -> std::os::raw::c_int {
+    let mut pos = 0 as u32;
+    if direction & STREAM_TOSERVER != 0 {
+        for i in 0..tx.frames_ts.len() {
+            match &tx.frames_ts[i].data {
+                HTTP2FrameTypeData::PRIORITY(prio) => {
+                    if pos == nb {
+                        return prio.weight as i32;
+                    } else {
+                        pos = pos + 1;
+                    }
+                }
+                HTTP2FrameTypeData::HEADERS(hd) => {
+                    if let Some(prio) = hd.priority {
+                        if pos == nb {
+                            return prio.weight as i32;
+                        } else {
+                            pos = pos + 1;
+                        }
+                    }
+                }
+                _ => {}
+            }
+        }
+    } else {
+        for i in 0..tx.frames_tc.len() {
+            match &tx.frames_tc[i].data {
+                HTTP2FrameTypeData::PRIORITY(prio) => {
+                    if pos == nb {
+                        return prio.weight as i32;
+                    } else {
+                        pos = pos + 1;
+                    }
+                }
+                HTTP2FrameTypeData::HEADERS(hd) => {
+                    if let Some(prio) = hd.priority {
+                        if pos == nb {
+                            return prio.weight as i32;
+                        } else {
+                            pos = pos + 1;
+                        }
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+    return -1;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_tx_get_next_priority(
+    tx: *mut std::os::raw::c_void, direction: u8, nb: u32,
+) -> std::os::raw::c_int {
+    let tx = cast_pointer!(tx, HTTP2Transaction);
+    return http2_tx_get_next_priority(tx, direction, nb);
+}
+
+fn http2_tx_get_next_window(
+    tx: &mut HTTP2Transaction, direction: u8, nb: u32,
+) -> std::os::raw::c_int {
+    let mut pos = 0 as u32;
+    if direction & STREAM_TOSERVER != 0 {
+        for i in 0..tx.frames_ts.len() {
+            match tx.frames_ts[i].data {
+                HTTP2FrameTypeData::WINDOWUPDATE(wu) => {
+                    if pos == nb {
+                        return wu.sizeinc as i32;
+                    } else {
+                        pos = pos + 1;
+                    }
+                }
+                _ => {}
+            }
+        }
+    } else {
+        for i in 0..tx.frames_tc.len() {
+            match tx.frames_tc[i].data {
+                HTTP2FrameTypeData::WINDOWUPDATE(wu) => {
+                    if pos == nb {
+                        return wu.sizeinc as i32;
+                    } else {
+                        pos = pos + 1;
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+    return -1;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_tx_get_next_window(
+    tx: *mut std::os::raw::c_void, direction: u8, nb: u32,
+) -> std::os::raw::c_int {
+    let tx = cast_pointer!(tx, HTTP2Transaction);
+    return http2_tx_get_next_window(tx, direction, nb);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_parse_settingsid(
+    str: *const std::os::raw::c_char,
+) -> std::os::raw::c_int {
+    let ft_name: &CStr = CStr::from_ptr(str); //unsafe
+    if let Ok(s) = ft_name.to_str() {
+        if let Ok(x) = parser::HTTP2SettingsId::from_str(s) {
+            return x as i32;
+        }
+    }
+    return -1;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_detect_settingsctx_parse(
+    str: *const std::os::raw::c_char,
+) -> *mut std::os::raw::c_void {
+    let ft_name: &CStr = CStr::from_ptr(str); //unsafe
+    if let Ok(s) = ft_name.to_str() {
+        if let Ok((_, ctx)) = parser::http2_parse_settingsctx(s) {
+            let boxed = Box::new(ctx);
+            return transmute(boxed); //unsafe
+        }
+    }
+    return std::ptr::null_mut();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_detect_settingsctx_free(ctx: *mut std::os::raw::c_void) {
+    // Just unbox...
+    let _ctx: Box<parser::DetectHTTP2settingsSigCtx> = transmute(ctx);
+}
+
+fn http2_detect_settings_match(
+    set: &Vec<parser::HTTP2FrameSettings>, ctx: &parser::DetectHTTP2settingsSigCtx,
+) -> std::os::raw::c_int {
+    for i in 0..set.len() {
+        if set[i].id == ctx.id {
+            match &ctx.value {
+                None => {
+                    return 1;
+                }
+                Some(x) => match x.mode {
+                    parser::DetectUintMode::DetectUintModeEqual => {
+                        if set[i].value == x.value {
+                            return 1;
+                        }
+                    }
+                    parser::DetectUintMode::DetectUintModeLt => {
+                        if set[i].value <= x.value {
+                            return 1;
+                        }
+                    }
+                    parser::DetectUintMode::DetectUintModeGt => {
+                        if set[i].value >= x.value {
+                            return 1;
+                        }
+                    }
+                    parser::DetectUintMode::DetectUintModeRange => {
+                        if set[i].value <= x.value && set[i].value >= x.valrange {
+                            return 1;
+                        }
+                    }
+                },
+            }
+        }
+    }
+    return 0;
+}
+
+fn http2_detect_settingsctx_match(
+    ctx: &mut parser::DetectHTTP2settingsSigCtx, tx: &mut HTTP2Transaction, direction: u8,
+) -> std::os::raw::c_int {
+    if direction & STREAM_TOSERVER != 0 {
+        for i in 0..tx.frames_ts.len() {
+            match &tx.frames_ts[i].data {
+                HTTP2FrameTypeData::SETTINGS(set) => {
+                    if http2_detect_settings_match(&set, ctx) != 0 {
+                        return 1;
+                    }
+                }
+                _ => {}
+            }
+        }
+    } else {
+        for i in 0..tx.frames_tc.len() {
+            match &tx.frames_tc[i].data {
+                HTTP2FrameTypeData::SETTINGS(set) => {
+                    if http2_detect_settings_match(&set, ctx) != 0 {
+                        return 1;
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+    return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_detect_settingsctx_match(
+    ctx: *const std::os::raw::c_void, tx: *mut std::os::raw::c_void, direction: u8,
+) -> std::os::raw::c_int {
+    let ctx = cast_pointer!(ctx, parser::DetectHTTP2settingsSigCtx);
+    let tx = cast_pointer!(tx, HTTP2Transaction);
+    return http2_detect_settingsctx_match(ctx, tx, direction);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_detect_u64_parse(
+    str: *const std::os::raw::c_char,
+) -> *mut std::os::raw::c_void {
+    let ft_name: &CStr = CStr::from_ptr(str); //unsafe
+    if let Ok(s) = ft_name.to_str() {
+        if let Ok((_, ctx)) = parser::detect_parse_u64(s) {
+            let boxed = Box::new(ctx);
+            return transmute(boxed); //unsafe
+        }
+    }
+    return std::ptr::null_mut();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_detect_u64_free(ctx: *mut std::os::raw::c_void) {
+    // Just unbox...
+    let _ctx: Box<parser::DetectU64Data> = transmute(ctx);
+}
+
+fn http2_detect_sizeupdate_match(
+    hd: &parser::HTTP2FrameHeaders, ctx: &parser::DetectU64Data,
+) -> std::os::raw::c_int {
+    for i in 0..hd.blocks.len() {
+        match ctx.mode {
+            parser::DetectUintMode::DetectUintModeEqual => {
+                if hd.blocks[i].sizeupdate == ctx.value
+                    && hd.blocks[i].error
+                        == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate
+                {
+                    return 1;
+                }
+            }
+            parser::DetectUintMode::DetectUintModeLt => {
+                if hd.blocks[i].sizeupdate <= ctx.value
+                    && hd.blocks[i].error
+                        == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate
+                {
+                    return 1;
+                }
+            }
+            parser::DetectUintMode::DetectUintModeGt => {
+                if hd.blocks[i].sizeupdate >= ctx.value
+                    && hd.blocks[i].error
+                        == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate
+                {
+                    return 1;
+                }
+            }
+            parser::DetectUintMode::DetectUintModeRange => {
+                if hd.blocks[i].sizeupdate <= ctx.value
+                    && hd.blocks[i].sizeupdate >= ctx.valrange
+                    && hd.blocks[i].error
+                        == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate
+                {
+                    return 1;
+                }
+            }
+        }
+    }
+    return 0;
+}
+
+fn http2_detect_sizeupdatectx_match(
+    ctx: &mut parser::DetectU64Data, tx: &mut HTTP2Transaction, direction: u8,
+) -> std::os::raw::c_int {
+    if direction & STREAM_TOSERVER != 0 {
+        for i in 0..tx.frames_ts.len() {
+            match &tx.frames_ts[i].data {
+                HTTP2FrameTypeData::HEADERS(hd) => {
+                    if http2_detect_sizeupdate_match(&hd, ctx) != 0 {
+                        return 1;
+                    }
+                }
+                _ => {}
+            }
+        }
+    } else {
+        for i in 0..tx.frames_tc.len() {
+            match &tx.frames_tc[i].data {
+                HTTP2FrameTypeData::HEADERS(hd) => {
+                    if http2_detect_sizeupdate_match(&hd, ctx) != 0 {
+                        return 1;
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+    return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_detect_sizeupdatectx_match(
+    ctx: *const std::os::raw::c_void, tx: *mut std::os::raw::c_void, direction: u8,
+) -> std::os::raw::c_int {
+    let ctx = cast_pointer!(ctx, parser::DetectU64Data);
+    let tx = cast_pointer!(tx, HTTP2Transaction);
+    return http2_detect_sizeupdatectx_match(ctx, tx, direction);
+}
+
+//TODOask better syntax between rs_http2_tx_get_header_name in argument
+// and rs_http2_detect_sizeupdatectx_match explicitly casting
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_header_name(
+    tx: &mut HTTP2Transaction, direction: u8, nb: u32, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+    let mut pos = 0 as u32;
+    if direction & STREAM_TOSERVER != 0 {
+        for i in 0..tx.frames_ts.len() {
+            match &tx.frames_ts[i].data {
+                HTTP2FrameTypeData::HEADERS(hd) => {
+                    if nb < pos + hd.blocks.len() as u32 {
+                        let value = &hd.blocks[(nb - pos) as usize].name;
+                        *buffer = value.as_ptr(); //unsafe
+                        *buffer_len = value.len() as u32;
+                        return 1;
+                    } else {
+                        pos = pos + hd.blocks.len() as u32;
+                    }
+                }
+                _ => {}
+            }
+        }
+    } else {
+        for i in 0..tx.frames_tc.len() {
+            match &tx.frames_tc[i].data {
+                HTTP2FrameTypeData::HEADERS(hd) => {
+                    if nb < pos + hd.blocks.len() as u32 {
+                        let value = &hd.blocks[(nb - pos) as usize].name;
+                        *buffer = value.as_ptr(); //unsafe
+                        *buffer_len = value.len() as u32;
+                        return 1;
+                    } else {
+                        pos = pos + hd.blocks.len() as u32;
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+    return 0;
+}
+
+fn http2_escape_header(hd: &parser::HTTP2FrameHeaders, i: u32) -> Vec<u8> {
+    //minimum size + 2 for escapes
+    let normalsize = hd.blocks[i as usize].value.len() + 2 + hd.blocks[i as usize].name.len() + 2;
+    let mut vec = Vec::with_capacity(normalsize);
+    for j in 0..hd.blocks[i as usize].name.len() {
+        vec.push(hd.blocks[i as usize].name[j]);
+        if hd.blocks[i as usize].name[j] == ':' as u8 {
+            vec.push(':' as u8);
+        }
+    }
+    vec.push(':' as u8);
+    vec.push(' ' as u8);
+    for j in 0..hd.blocks[i as usize].value.len() {
+        vec.push(hd.blocks[i as usize].value[j]);
+        if hd.blocks[i as usize].value[j] == ':' as u8 {
+            vec.push(':' as u8);
+        }
+    }
+    return vec;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_tx_get_header(
+    tx: &mut HTTP2Transaction, direction: u8, nb: u32, buffer: *mut *const u8, buffer_len: *mut u32,
+) -> u8 {
+    let mut pos = 0 as u32;
+    if direction & STREAM_TOSERVER != 0 {
+        for i in 0..tx.frames_ts.len() {
+            match &tx.frames_ts[i].data {
+                HTTP2FrameTypeData::HEADERS(hd) => {
+                    if nb < pos + hd.blocks.len() as u32 {
+                        tx.escaped_tmp = http2_escape_header(&hd, nb - pos);
+                        let value = &tx.escaped_tmp;
+                        *buffer = value.as_ptr(); //unsafe
+                        *buffer_len = value.len() as u32;
+                        return 1;
+                    } else {
+                        pos = pos + hd.blocks.len() as u32;
+                    }
+                }
+                _ => {}
+            }
+        }
+    } else {
+        for i in 0..tx.frames_tc.len() {
+            match &tx.frames_tc[i].data {
+                HTTP2FrameTypeData::HEADERS(hd) => {
+                    if nb < pos + hd.blocks.len() as u32 {
+                        tx.escaped_tmp = http2_escape_header(&hd, nb - pos);
+                        let value = &tx.escaped_tmp;
+                        *buffer = value.as_ptr(); //unsafe
+                        *buffer_len = value.len() as u32;
+                        return 1;
+                    } else {
+                        pos = pos + hd.blocks.len() as u32;
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+
+    return 0;
+}
diff --git a/rust/src/http2/files.rs b/rust/src/http2/files.rs
new file mode 100644 (file)
index 0000000..4063604
--- /dev/null
@@ -0,0 +1,51 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use crate::core::*;
+use crate::filecontainer::*;
+
+/// Wrapper around Suricata's internal file container logic.
+#[derive(Debug)]
+pub struct HTTP2Files {
+    pub files_ts: FileContainer,
+    pub files_tc: FileContainer,
+    pub flags_ts: u16,
+    pub flags_tc: u16,
+}
+
+impl HTTP2Files {
+    pub fn new() -> HTTP2Files {
+        HTTP2Files {
+            files_ts: FileContainer::default(),
+            files_tc: FileContainer::default(),
+            flags_ts: 0,
+            flags_tc: 0,
+        }
+    }
+    pub fn free(&mut self) {
+        self.files_ts.free();
+        self.files_tc.free();
+    }
+
+    pub fn get(&mut self, direction: u8) -> (&mut FileContainer, u16) {
+        if direction == STREAM_TOSERVER {
+            (&mut self.files_ts, self.flags_ts)
+        } else {
+            (&mut self.files_tc, self.flags_tc)
+        }
+    }
+}
diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs
new file mode 100644 (file)
index 0000000..3bf4893
--- /dev/null
@@ -0,0 +1,1100 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use super::files::*;
+use super::parser;
+use crate::applayer::{self, *};
+use crate::core::{
+    self, AppProto, Flow, SuricataFileContext, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP,
+    STREAM_TOCLIENT, STREAM_TOSERVER,
+};
+use crate::filecontainer::*;
+use crate::filetracker::*;
+use crate::log::*;
+use nom;
+use std;
+use std::ffi::{CStr, CString};
+use std::fmt;
+use std::mem::transmute;
+
+static mut ALPROTO_HTTP2: AppProto = ALPROTO_UNKNOWN;
+
+const HTTP2_DEFAULT_MAX_FRAME_SIZE: u32 = 16384;
+const HTTP2_MAX_HANDLED_FRAME_SIZE: usize = 65536;
+const HTTP2_MIN_HANDLED_FRAME_SIZE: usize = 256;
+
+pub static mut SURICATA_HTTP2_FILE_CONFIG: Option<&'static SuricataFileContext> = None;
+
+#[no_mangle]
+pub extern "C" fn rs_http2_init(context: &'static mut SuricataFileContext) {
+    unsafe {
+        SURICATA_HTTP2_FILE_CONFIG = Some(context);
+    }
+}
+
+#[repr(u8)]
+#[derive(Copy, Clone, PartialOrd, PartialEq)]
+pub enum HTTP2ConnectionState {
+    Http2StateInit = 0,
+    Http2StateMagicDone = 1,
+}
+
+const HTTP2_FRAME_HEADER_LEN: usize = 9;
+const HTTP2_MAGIC_LEN: usize = 24;
+const HTTP2_FRAME_GOAWAY_LEN: usize = 4;
+const HTTP2_FRAME_RSTSTREAM_LEN: usize = 4;
+const HTTP2_FRAME_PRIORITY_LEN: usize = 1;
+const HTTP2_FRAME_WINDOWUPDATE_LEN: usize = 4;
+
+#[repr(u8)]
+#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
+pub enum HTTP2FrameUnhandledReason {
+    UnknownType = 0,
+    TooLong = 1,
+    ParsingError = 2,
+    Incomplete = 3,
+}
+
+impl fmt::Display for HTTP2FrameUnhandledReason {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
+#[derive(Debug)]
+pub struct HTTP2FrameUnhandled {
+    pub reason: HTTP2FrameUnhandledReason,
+}
+
+pub enum HTTP2FrameTypeData {
+    PRIORITY(parser::HTTP2FramePriority),
+    GOAWAY(parser::HTTP2FrameGoAway),
+    RSTSTREAM(parser::HTTP2FrameRstStream),
+    SETTINGS(Vec<parser::HTTP2FrameSettings>),
+    WINDOWUPDATE(parser::HTTP2FrameWindowUpdate),
+    HEADERS(parser::HTTP2FrameHeaders),
+    PUSHPROMISE(parser::HTTP2FramePushPromise),
+    CONTINUATION(parser::HTTP2FrameContinuation),
+    PING,
+    DATA,
+    //not a defined frame
+    UNHANDLED(HTTP2FrameUnhandled),
+}
+
+#[repr(u8)]
+#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
+pub enum HTTP2TransactionState {
+    HTTP2StateIdle = 0,
+    HTTP2StateOpen = 1,
+    HTTP2StateReserved = 2,
+    HTTP2StateDataClient = 3,
+    HTTP2StateDataServer = 4,
+    HTTP2StateHalfClosedClient = 5,
+    HTTP2StateHalfClosedServer = 6,
+    HTTP2StateClosed = 7,
+    //not a RFC-defined state, used for stream 0 frames appyling to the global connection
+    HTTP2StateGlobal = 8,
+}
+
+pub struct HTTP2Frame {
+    pub header: parser::HTTP2FrameHeader,
+    pub data: HTTP2FrameTypeData,
+}
+
+pub struct HTTP2Transaction {
+    tx_id: u64,
+    pub stream_id: u32,
+    state: HTTP2TransactionState,
+    child_stream_id: u32,
+
+    pub frames_tc: Vec<HTTP2Frame>,
+    pub frames_ts: Vec<HTTP2Frame>,
+
+    de_state: Option<*mut core::DetectEngineState>,
+    events: *mut core::AppLayerDecoderEvents,
+    tx_data: AppLayerTxData,
+    ft: FileTransferTracker,
+
+    //temporary escaped header for detection
+    //must be attached to transaction for memory management (be freed at the right time)
+    pub escaped_tmp: Vec<u8>,
+}
+
+impl HTTP2Transaction {
+    pub fn new() -> HTTP2Transaction {
+        HTTP2Transaction {
+            tx_id: 0,
+            stream_id: 0,
+            child_stream_id: 0,
+            state: HTTP2TransactionState::HTTP2StateIdle,
+            frames_tc: Vec::new(),
+            frames_ts: Vec::new(),
+            de_state: None,
+            events: std::ptr::null_mut(),
+            tx_data: AppLayerTxData::new(),
+            ft: FileTransferTracker::new(),
+            escaped_tmp: Vec::new(),
+        }
+    }
+
+    pub fn free(&mut self) {
+        if self.events != std::ptr::null_mut() {
+            core::sc_app_layer_decoder_events_free_events(&mut self.events);
+        }
+        if let Some(state) = self.de_state {
+            core::sc_detect_engine_state_free(state);
+        }
+    }
+
+    fn handle_frame(
+        &mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: u8,
+    ) {
+        //handle child_stream_id changes
+        match data {
+            HTTP2FrameTypeData::PUSHPROMISE(hs) => {
+                if dir == STREAM_TOCLIENT {
+                    //we could set an event if self.child_stream_id != 0
+                    if header.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS == 0 {
+                        self.child_stream_id = hs.stream_id;
+                    }
+                    self.state = HTTP2TransactionState::HTTP2StateReserved;
+                }
+            }
+            HTTP2FrameTypeData::CONTINUATION(_) => {
+                if dir == STREAM_TOCLIENT
+                    && header.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS != 0
+                {
+                    self.child_stream_id = 0;
+                }
+            }
+            HTTP2FrameTypeData::HEADERS(_) => {
+                if dir == STREAM_TOCLIENT {
+                    self.child_stream_id = 0;
+                }
+            }
+            HTTP2FrameTypeData::RSTSTREAM(_) => {
+                self.child_stream_id = 0;
+            }
+            _ => {}
+        }
+        //handle closing state changes
+        match data {
+            HTTP2FrameTypeData::HEADERS(_) | HTTP2FrameTypeData::DATA => {
+                if header.flags & parser::HTTP2_FLAG_HEADER_EOS != 0 {
+                    match self.state {
+                        HTTP2TransactionState::HTTP2StateHalfClosedClient => {
+                            if dir == STREAM_TOCLIENT {
+                                self.state = HTTP2TransactionState::HTTP2StateClosed;
+                            }
+                        }
+                        HTTP2TransactionState::HTTP2StateHalfClosedServer => {
+                            if dir == STREAM_TOSERVER {
+                                self.state = HTTP2TransactionState::HTTP2StateClosed;
+                            }
+                        }
+                        // do not revert back to a hald closed state
+                        HTTP2TransactionState::HTTP2StateClosed => {}
+                        HTTP2TransactionState::HTTP2StateGlobal => {}
+                        _ => {
+                            if dir == STREAM_TOCLIENT {
+                                self.state = HTTP2TransactionState::HTTP2StateHalfClosedServer;
+                            } else {
+                                self.state = HTTP2TransactionState::HTTP2StateHalfClosedClient;
+                            }
+                        }
+                    }
+                } else if header.ftype == parser::HTTP2FrameType::DATA as u8 {
+                    //not end of stream
+                    if dir == STREAM_TOSERVER {
+                        self.state = HTTP2TransactionState::HTTP2StateDataClient;
+                    } else {
+                        self.state = HTTP2TransactionState::HTTP2StateDataServer;
+                    }
+                }
+            }
+            _ => {}
+        }
+    }
+}
+
+impl Drop for HTTP2Transaction {
+    fn drop(&mut self) {
+        self.free();
+    }
+}
+
+#[repr(u32)]
+pub enum HTTP2Event {
+    InvalidFrameHeader = 0,
+    InvalidClientMagic,
+    InvalidFrameData,
+    InvalidHeader,
+    InvalidFrameLength,
+    ExtraHeaderData,
+    LongFrameData,
+    StreamIdReuse,
+}
+
+impl HTTP2Event {
+    fn from_i32(value: i32) -> Option<HTTP2Event> {
+        match value {
+            0 => Some(HTTP2Event::InvalidFrameHeader),
+            1 => Some(HTTP2Event::InvalidClientMagic),
+            2 => Some(HTTP2Event::InvalidFrameData),
+            3 => Some(HTTP2Event::InvalidHeader),
+            4 => Some(HTTP2Event::InvalidFrameLength),
+            5 => Some(HTTP2Event::ExtraHeaderData),
+            6 => Some(HTTP2Event::LongFrameData),
+            7 => Some(HTTP2Event::StreamIdReuse),
+            _ => None,
+        }
+    }
+}
+
+pub struct HTTP2State {
+    tx_id: u64,
+    request_frame_size: u32,
+    response_frame_size: u32,
+    dynamic_headers_ts: Vec<parser::HTTP2FrameHeaderBlock>,
+    dynamic_headers_tc: Vec<parser::HTTP2FrameHeaderBlock>,
+    transactions: Vec<HTTP2Transaction>,
+    progress: HTTP2ConnectionState,
+    pub files: HTTP2Files,
+}
+
+impl HTTP2State {
+    pub fn new() -> Self {
+        Self {
+            tx_id: 0,
+            request_frame_size: 0,
+            response_frame_size: 0,
+            // the headers are encoded on one byte
+            // with a fixed number of static headers, and
+            // a variable number of dynamic headers
+            dynamic_headers_ts: Vec::with_capacity(256 - parser::HTTP2_STATIC_HEADERS_NUMBER),
+            dynamic_headers_tc: Vec::with_capacity(256 - parser::HTTP2_STATIC_HEADERS_NUMBER),
+            transactions: Vec::new(),
+            progress: HTTP2ConnectionState::Http2StateInit,
+            files: HTTP2Files::new(),
+        }
+    }
+
+    pub fn free(&mut self) {
+        self.transactions.clear();
+        self.files.free();
+    }
+
+    fn set_event(&mut self, event: HTTP2Event) {
+        let len = self.transactions.len();
+        if len == 0 {
+            return;
+        }
+        let tx = &mut self.transactions[len - 1];
+        let ev = event as u8;
+        core::sc_app_layer_decoder_events_set_event_raw(&mut tx.events, ev);
+    }
+
+    // Free a transaction by ID.
+    fn free_tx(&mut self, tx_id: u64) {
+        let len = self.transactions.len();
+        let mut found = false;
+        let mut index = 0;
+        for i in 0..len {
+            let tx = &self.transactions[i];
+            if tx.tx_id == tx_id + 1 {
+                found = true;
+                index = i;
+                break;
+            }
+        }
+        if found {
+            self.transactions.remove(index);
+        }
+    }
+
+    pub fn get_tx(&mut self, tx_id: u64) -> Option<&HTTP2Transaction> {
+        for tx in &mut self.transactions {
+            if tx.tx_id == tx_id + 1 {
+                return Some(tx);
+            }
+        }
+        return None;
+    }
+
+    fn find_tx_index(&mut self, sid: u32) -> usize {
+        for i in 0..self.transactions.len() {
+            //reverse order should be faster
+            let idx = self.transactions.len() - 1 - i;
+            if sid == self.transactions[idx].stream_id {
+                if self.transactions[idx].state == HTTP2TransactionState::HTTP2StateClosed {
+                    self.set_event(HTTP2Event::StreamIdReuse);
+                    return 0;
+                }
+                return idx + 1;
+            }
+        }
+        return 0;
+    }
+
+    fn find_child_stream_id(&mut self, sid: u32) -> u32 {
+        for i in 0..self.transactions.len() {
+            //reverse order should be faster
+            if sid == self.transactions[self.transactions.len() - 1 - i].stream_id {
+                if self.transactions[self.transactions.len() - 1 - i].child_stream_id > 0 {
+                    return self.transactions[self.transactions.len() - 1 - i].child_stream_id;
+                }
+                return sid;
+            }
+        }
+        return sid;
+    }
+
+    fn create_global_tx(&mut self) -> &mut HTTP2Transaction {
+        //special transaction with only one frame
+        //as it affects the global connection, there is no end to it
+        let mut tx = HTTP2Transaction::new();
+        self.tx_id += 1;
+        tx.tx_id = self.tx_id;
+        tx.state = HTTP2TransactionState::HTTP2StateGlobal;
+        self.transactions.push(tx);
+        return self.transactions.last_mut().unwrap();
+    }
+
+    fn find_or_create_tx(
+        &mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: u8,
+    ) -> &mut HTTP2Transaction {
+        if header.stream_id == 0 {
+            return self.create_global_tx();
+        }
+        let sid = match data {
+            //yes, the right stream_id for Suricata is not the header one
+            HTTP2FrameTypeData::PUSHPROMISE(hs) => hs.stream_id,
+            HTTP2FrameTypeData::CONTINUATION(_) => {
+                if dir == STREAM_TOCLIENT {
+                    //continuation of a push promise
+                    self.find_child_stream_id(header.stream_id)
+                } else {
+                    header.stream_id
+                }
+            }
+            _ => header.stream_id,
+        };
+        let index = self.find_tx_index(sid);
+        if index > 0 {
+            return &mut self.transactions[index - 1];
+        } else {
+            let mut tx = HTTP2Transaction::new();
+            self.tx_id += 1;
+            tx.tx_id = self.tx_id;
+            tx.stream_id = sid;
+            tx.state = HTTP2TransactionState::HTTP2StateOpen;
+            self.transactions.push(tx);
+            return self.transactions.last_mut().unwrap();
+        }
+    }
+
+    fn parse_frame_data(
+        &mut self, ftype: u8, input: &[u8], complete: bool, hflags: u8, dir: u8,
+    ) -> HTTP2FrameTypeData {
+        match num::FromPrimitive::from_u8(ftype) {
+            Some(parser::HTTP2FrameType::GOAWAY) => {
+                if input.len() < HTTP2_FRAME_GOAWAY_LEN {
+                    self.set_event(HTTP2Event::InvalidFrameLength);
+                    return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                        reason: HTTP2FrameUnhandledReason::Incomplete,
+                    });
+                }
+                match parser::http2_parse_frame_goaway(input) {
+                    Ok((_, goaway)) => {
+                        return HTTP2FrameTypeData::GOAWAY(goaway);
+                    }
+                    Err(_) => {
+                        self.set_event(HTTP2Event::InvalidFrameData);
+                        return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                            reason: HTTP2FrameUnhandledReason::ParsingError,
+                        });
+                    }
+                }
+            }
+            Some(parser::HTTP2FrameType::SETTINGS) => {
+                match parser::http2_parse_frame_settings(input) {
+                    Ok((_, set)) => {
+                        //we could set an event on remaining data
+                        return HTTP2FrameTypeData::SETTINGS(set);
+                    }
+                    Err(nom::Err::Incomplete(_)) => {
+                        if complete {
+                            self.set_event(HTTP2Event::InvalidFrameData);
+                            return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                                reason: HTTP2FrameUnhandledReason::ParsingError,
+                            });
+                        } else {
+                            return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                                reason: HTTP2FrameUnhandledReason::TooLong,
+                            });
+                        }
+                    }
+                    Err(_) => {
+                        self.set_event(HTTP2Event::InvalidFrameData);
+                        return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                            reason: HTTP2FrameUnhandledReason::ParsingError,
+                        });
+                    }
+                }
+            }
+            Some(parser::HTTP2FrameType::RSTSTREAM) => {
+                if input.len() != HTTP2_FRAME_RSTSTREAM_LEN {
+                    self.set_event(HTTP2Event::InvalidFrameLength);
+                    return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                        reason: HTTP2FrameUnhandledReason::Incomplete,
+                    });
+                } else {
+                    match parser::http2_parse_frame_rststream(input) {
+                        Ok((_, rst)) => {
+                            return HTTP2FrameTypeData::RSTSTREAM(rst);
+                        }
+                        Err(_) => {
+                            self.set_event(HTTP2Event::InvalidFrameData);
+                            return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                                reason: HTTP2FrameUnhandledReason::ParsingError,
+                            });
+                        }
+                    }
+                }
+            }
+            Some(parser::HTTP2FrameType::PRIORITY) => {
+                if input.len() != HTTP2_FRAME_PRIORITY_LEN {
+                    self.set_event(HTTP2Event::InvalidFrameLength);
+                    return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                        reason: HTTP2FrameUnhandledReason::Incomplete,
+                    });
+                } else {
+                    match parser::http2_parse_frame_priority(input) {
+                        Ok((_, priority)) => {
+                            return HTTP2FrameTypeData::PRIORITY(priority);
+                        }
+                        Err(_) => {
+                            self.set_event(HTTP2Event::InvalidFrameData);
+                            return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                                reason: HTTP2FrameUnhandledReason::ParsingError,
+                            });
+                        }
+                    }
+                }
+            }
+            Some(parser::HTTP2FrameType::WINDOWUPDATE) => {
+                if input.len() != HTTP2_FRAME_WINDOWUPDATE_LEN {
+                    self.set_event(HTTP2Event::InvalidFrameLength);
+                    return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                        reason: HTTP2FrameUnhandledReason::Incomplete,
+                    });
+                } else {
+                    match parser::http2_parse_frame_windowupdate(input) {
+                        Ok((_, wu)) => {
+                            return HTTP2FrameTypeData::WINDOWUPDATE(wu);
+                        }
+                        Err(_) => {
+                            self.set_event(HTTP2Event::InvalidFrameData);
+                            return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                                reason: HTTP2FrameUnhandledReason::ParsingError,
+                            });
+                        }
+                    }
+                }
+            }
+            Some(parser::HTTP2FrameType::PUSHPROMISE) => {
+                let dyn_headers = if dir == STREAM_TOCLIENT {
+                    &mut self.dynamic_headers_tc
+                } else {
+                    &mut self.dynamic_headers_ts
+                };
+                match parser::http2_parse_frame_push_promise(input, hflags, dyn_headers) {
+                    Ok((_, hs)) => {
+                        for i in 0..hs.blocks.len() {
+                            if hs.blocks[i].error
+                                >= parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeError
+                            {
+                                self.set_event(HTTP2Event::InvalidHeader);
+                            }
+                        }
+                        return HTTP2FrameTypeData::PUSHPROMISE(hs);
+                    }
+                    Err(nom::Err::Incomplete(_)) => {
+                        if complete {
+                            self.set_event(HTTP2Event::InvalidFrameData);
+                            return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                                reason: HTTP2FrameUnhandledReason::ParsingError,
+                            });
+                        } else {
+                            return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                                reason: HTTP2FrameUnhandledReason::TooLong,
+                            });
+                        }
+                    }
+                    Err(_) => {
+                        self.set_event(HTTP2Event::InvalidFrameData);
+                        return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                            reason: HTTP2FrameUnhandledReason::ParsingError,
+                        });
+                    }
+                }
+            }
+            Some(parser::HTTP2FrameType::DATA) => {
+                return HTTP2FrameTypeData::DATA;
+            }
+            Some(parser::HTTP2FrameType::CONTINUATION) => {
+                let dyn_headers = if dir == STREAM_TOCLIENT {
+                    &mut self.dynamic_headers_tc
+                } else {
+                    &mut self.dynamic_headers_ts
+                };
+                match parser::http2_parse_frame_continuation(input, dyn_headers) {
+                    Ok((_, hs)) => {
+                        for i in 0..hs.blocks.len() {
+                            if hs.blocks[i].error
+                                >= parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeError
+                            {
+                                self.set_event(HTTP2Event::InvalidHeader);
+                            }
+                        }
+                        return HTTP2FrameTypeData::CONTINUATION(hs);
+                    }
+                    Err(nom::Err::Incomplete(_)) => {
+                        if complete {
+                            self.set_event(HTTP2Event::InvalidFrameData);
+                            return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                                reason: HTTP2FrameUnhandledReason::ParsingError,
+                            });
+                        } else {
+                            return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                                reason: HTTP2FrameUnhandledReason::TooLong,
+                            });
+                        }
+                    }
+                    Err(_) => {
+                        self.set_event(HTTP2Event::InvalidFrameData);
+                        return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                            reason: HTTP2FrameUnhandledReason::ParsingError,
+                        });
+                    }
+                }
+            }
+            Some(parser::HTTP2FrameType::HEADERS) => {
+                let dyn_headers = if dir == STREAM_TOCLIENT {
+                    &mut self.dynamic_headers_tc
+                } else {
+                    &mut self.dynamic_headers_ts
+                };
+                match parser::http2_parse_frame_headers(input, hflags, dyn_headers) {
+                    Ok((hrem, hs)) => {
+                        for i in 0..hs.blocks.len() {
+                            if hs.blocks[i].error
+                                >= parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeError
+                            {
+                                self.set_event(HTTP2Event::InvalidHeader);
+                            }
+                        }
+                        if hrem.len() > 0 {
+                            SCLogDebug!("Remaining data for HTTP2 headers");
+                            self.set_event(HTTP2Event::ExtraHeaderData);
+                        }
+                        return HTTP2FrameTypeData::HEADERS(hs);
+                    }
+                    Err(nom::Err::Incomplete(_)) => {
+                        if complete {
+                            self.set_event(HTTP2Event::InvalidFrameData);
+                            return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                                reason: HTTP2FrameUnhandledReason::ParsingError,
+                            });
+                        } else {
+                            return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                                reason: HTTP2FrameUnhandledReason::TooLong,
+                            });
+                        }
+                    }
+                    Err(_) => {
+                        self.set_event(HTTP2Event::InvalidFrameData);
+                        return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                            reason: HTTP2FrameUnhandledReason::ParsingError,
+                        });
+                    }
+                }
+            }
+            Some(parser::HTTP2FrameType::PING) => {
+                return HTTP2FrameTypeData::PING;
+            }
+            _ => {
+                return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled {
+                    reason: HTTP2FrameUnhandledReason::UnknownType,
+                });
+            }
+        }
+    }
+
+    fn stream_data(&mut self, dir: u8, input: &[u8], over: bool, txid: u64) {
+        match unsafe { SURICATA_HTTP2_FILE_CONFIG } {
+            Some(sfcm) => {
+                for tx in &mut self.transactions {
+                    if tx.tx_id == txid {
+                        let xid: u32 = tx.tx_id as u32;
+                        let (files, flags) = self.files.get(dir);
+                        tx.ft.tx_id = tx.tx_id;
+                        tx.ft.new_chunk(
+                            sfcm,
+                            files,
+                            flags,
+                            b"",
+                            input,
+                            tx.ft.tracked, //offset = append
+                            input.len() as u32,
+                            0,
+                            over,
+                            &xid,
+                        );
+                        break;
+                    }
+                }
+            }
+            None => panic!("BUG"),
+        }
+    }
+
+    fn parse_frames(&mut self, mut input: &[u8], il: usize, dir: u8) -> AppLayerResult {
+        while input.len() > 0 {
+            match parser::http2_parse_frame_header(input) {
+                Ok((rem, head)) => {
+                    let hl = head.length as usize;
+
+                    //we check for completeness first
+                    if rem.len() < hl {
+                        //but limit ourselves so as not to exhaust memory
+                        if hl < HTTP2_MAX_HANDLED_FRAME_SIZE {
+                            return AppLayerResult::incomplete(
+                                (il - input.len()) as u32,
+                                (HTTP2_FRAME_HEADER_LEN + hl) as u32,
+                            );
+                        } else if rem.len() < HTTP2_MIN_HANDLED_FRAME_SIZE {
+                            return AppLayerResult::incomplete(
+                                (il - input.len()) as u32,
+                                (HTTP2_FRAME_HEADER_LEN + HTTP2_MIN_HANDLED_FRAME_SIZE) as u32,
+                            );
+                        } else {
+                            self.set_event(HTTP2Event::LongFrameData);
+                            self.request_frame_size = head.length - (rem.len() as u32);
+                        }
+                    }
+
+                    //get a safe length for the buffer
+                    let (hlsafe, complete) = if rem.len() < hl {
+                        (rem.len(), false)
+                    } else {
+                        (hl, true)
+                    };
+
+                    if head.length == 0 && head.ftype == parser::HTTP2FrameType::SETTINGS as u8 {
+                        input = &rem[hlsafe..];
+                        continue;
+                    }
+                    let txdata = self.parse_frame_data(
+                        head.ftype,
+                        &rem[..hlsafe],
+                        complete,
+                        head.flags,
+                        dir,
+                    );
+
+                    let tx = self.find_or_create_tx(&head, &txdata, dir);
+                    tx.handle_frame(&head, &txdata, dir);
+                    let over = head.flags & parser::HTTP2_FLAG_HEADER_EOS != 0;
+                    let txid = tx.tx_id;
+                    let ftype = head.ftype;
+                    if dir == STREAM_TOSERVER {
+                        tx.frames_ts.push(HTTP2Frame {
+                            header: head,
+                            data: txdata,
+                        });
+                    } else {
+                        tx.frames_tc.push(HTTP2Frame {
+                            header: head,
+                            data: txdata,
+                        });
+                    }
+                    if ftype == parser::HTTP2FrameType::DATA as u8 {
+                        self.stream_data(dir, &rem[..hlsafe], over, txid);
+                    }
+                    input = &rem[hlsafe..];
+                }
+                Err(nom::Err::Incomplete(_)) => {
+                    //we may have consumed data from previous records
+                    return AppLayerResult::incomplete(
+                        (il - input.len()) as u32,
+                        HTTP2_FRAME_HEADER_LEN as u32,
+                    );
+                }
+                Err(_) => {
+                    self.set_event(HTTP2Event::InvalidFrameHeader);
+                    return AppLayerResult::err();
+                }
+            }
+        }
+        return AppLayerResult::ok();
+    }
+
+    fn parse_ts(&mut self, mut input: &[u8]) -> AppLayerResult {
+        //very first : skip magic
+        if self.progress < HTTP2ConnectionState::Http2StateMagicDone {
+            //skip magic
+            if input.len() >= HTTP2_MAGIC_LEN {
+                //skip magic
+                match std::str::from_utf8(&input[..HTTP2_MAGIC_LEN]) {
+                    Ok("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") => {
+                        input = &input[HTTP2_MAGIC_LEN..];
+                    }
+                    Ok(&_) => {
+                        self.set_event(HTTP2Event::InvalidClientMagic);
+                    }
+                    Err(_) => {
+                        return AppLayerResult::err();
+                    }
+                }
+                self.progress = HTTP2ConnectionState::Http2StateMagicDone;
+            } else {
+                //still more buffer
+                return AppLayerResult::incomplete(0 as u32, HTTP2_MAGIC_LEN as u32);
+            }
+        }
+        //first consume frame bytes
+        let il = input.len();
+        if self.request_frame_size > 0 {
+            let ilen = input.len() as u32;
+            if self.request_frame_size >= ilen {
+                self.request_frame_size -= ilen;
+                return AppLayerResult::ok();
+            } else {
+                let start = self.request_frame_size as usize;
+                input = &input[start..];
+                self.request_frame_size = 0;
+            }
+        }
+
+        //then parse all we can
+        return self.parse_frames(input, il, STREAM_TOSERVER);
+    }
+
+    fn parse_tc(&mut self, mut input: &[u8]) -> AppLayerResult {
+        //first consume frame bytes
+        let il = input.len();
+        if self.response_frame_size > 0 {
+            let ilen = input.len() as u32;
+            if self.response_frame_size >= ilen {
+                self.response_frame_size -= ilen;
+                return AppLayerResult::ok();
+            } else {
+                let start = self.response_frame_size as usize;
+                input = &input[start..];
+                self.response_frame_size = 0;
+            }
+        }
+        //then parse all we can
+        return self.parse_frames(input, il, STREAM_TOCLIENT);
+    }
+
+    fn tx_iterator(
+        &mut self, min_tx_id: u64, state: &mut u64,
+    ) -> Option<(&HTTP2Transaction, u64, bool)> {
+        let mut index = *state as usize;
+        let len = self.transactions.len();
+
+        while index < len {
+            let tx = &self.transactions[index];
+            if tx.tx_id < min_tx_id + 1 {
+                index += 1;
+                continue;
+            }
+            *state = index as u64;
+            return Some((tx, tx.tx_id - 1, (len - index) > 1));
+        }
+
+        return None;
+    }
+}
+
+// C exports.
+
+export_tx_get_detect_state!(rs_http2_tx_get_detect_state, HTTP2Transaction);
+export_tx_set_detect_state!(rs_http2_tx_set_detect_state, HTTP2Transaction);
+
+export_tx_data_get!(rs_http2_get_tx_data, HTTP2Transaction);
+
+//TODOnext connection upgrade from HTTP1 cf SMTP STARTTLS
+/// C entry point for a probing parser.
+#[no_mangle]
+pub extern "C" fn rs_http2_probing_parser_tc(
+    _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8,
+) -> AppProto {
+    if input != std::ptr::null_mut() {
+        let slice = build_slice!(input, input_len as usize);
+        match parser::http2_parse_frame_header(slice) {
+            Ok((_, header)) => {
+                if header.reserved != 0
+                    || header.length > HTTP2_DEFAULT_MAX_FRAME_SIZE
+                    || header.flags & 0xFE != 0
+                    || header.ftype != parser::HTTP2FrameType::SETTINGS as u8
+                {
+                    return unsafe { ALPROTO_FAILED };
+                }
+                return unsafe { ALPROTO_HTTP2 };
+            }
+            Err(nom::Err::Incomplete(_)) => {
+                return ALPROTO_UNKNOWN;
+            }
+            Err(_) => {
+                return unsafe { ALPROTO_FAILED };
+            }
+        }
+    }
+    return ALPROTO_UNKNOWN;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_state_new() -> *mut std::os::raw::c_void {
+    let state = HTTP2State::new();
+    let boxed = Box::new(state);
+    return unsafe { transmute(boxed) };
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_state_free(state: *mut std::os::raw::c_void) {
+    // Just unbox...
+    let mut state: Box<HTTP2State> = unsafe { transmute(state) };
+    state.free();
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) {
+    let state = cast_pointer!(state, HTTP2State);
+    state.free_tx(tx_id);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_parse_ts(
+    flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void,
+    input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8,
+) -> AppLayerResult {
+    let state = cast_pointer!(state, HTTP2State);
+    let buf = build_slice!(input, input_len as usize);
+
+    state.files.flags_ts = unsafe { FileFlowToFlags(flow, STREAM_TOSERVER) };
+    return state.parse_ts(buf);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_parse_tc(
+    flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void,
+    input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8,
+) -> AppLayerResult {
+    let state = cast_pointer!(state, HTTP2State);
+    let buf = build_slice!(input, input_len as usize);
+    state.files.flags_tc = unsafe { FileFlowToFlags(flow, STREAM_TOCLIENT) };
+    return state.parse_tc(buf);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_state_get_tx(
+    state: *mut std::os::raw::c_void, tx_id: u64,
+) -> *mut std::os::raw::c_void {
+    let state = cast_pointer!(state, HTTP2State);
+    match state.get_tx(tx_id) {
+        Some(tx) => {
+            return unsafe { transmute(tx) };
+        }
+        None => {
+            return std::ptr::null_mut();
+        }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 {
+    let state = cast_pointer!(state, HTTP2State);
+    return state.tx_id;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_state_progress_completion_status(_direction: u8) -> std::os::raw::c_int {
+    return HTTP2TransactionState::HTTP2StateClosed as i32;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_tx_get_state(tx: *mut std::os::raw::c_void) -> HTTP2TransactionState {
+    let tx = cast_pointer!(tx, HTTP2Transaction);
+    return tx.state;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_tx_get_alstate_progress(
+    tx: *mut std::os::raw::c_void, _direction: u8,
+) -> std::os::raw::c_int {
+    return rs_http2_tx_get_state(tx) as i32;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_state_get_events(
+    tx: *mut std::os::raw::c_void,
+) -> *mut core::AppLayerDecoderEvents {
+    let tx = cast_pointer!(tx, HTTP2Transaction);
+    return tx.events;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_state_get_event_info(
+    event_name: *const std::os::raw::c_char, event_id: *mut std::os::raw::c_int,
+    event_type: *mut core::AppLayerEventType,
+) -> std::os::raw::c_int {
+    if event_name == std::ptr::null() {
+        return -1;
+    }
+    let c_event_name: &CStr = unsafe { CStr::from_ptr(event_name) };
+    let event = match c_event_name.to_str() {
+        Ok(s) => {
+            match s {
+                "invalid_frame_header" => HTTP2Event::InvalidFrameHeader as i32,
+                "invalid_client_magic" => HTTP2Event::InvalidClientMagic as i32,
+                "invalid_frame_data" => HTTP2Event::InvalidFrameData as i32,
+                "invalid_header" => HTTP2Event::InvalidHeader as i32,
+                "invalid_frame_length" => HTTP2Event::InvalidFrameLength as i32,
+                "extra_header_data" => HTTP2Event::ExtraHeaderData as i32,
+                "long_frame_data" => HTTP2Event::LongFrameData as i32,
+                "stream_id_reuse" => HTTP2Event::StreamIdReuse as i32,
+                _ => -1, // unknown event
+            }
+        }
+        Err(_) => -1, // UTF-8 conversion failed
+    };
+    unsafe {
+        *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION;
+        *event_id = event as std::os::raw::c_int;
+    };
+    0
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_state_get_event_info_by_id(
+    event_id: std::os::raw::c_int, event_name: *mut *const std::os::raw::c_char,
+    event_type: *mut core::AppLayerEventType,
+) -> i8 {
+    if let Some(e) = HTTP2Event::from_i32(event_id as i32) {
+        let estr = match e {
+            HTTP2Event::InvalidFrameHeader => "invalid_frame_header\0",
+            HTTP2Event::InvalidClientMagic => "invalid_client_magic\0",
+            HTTP2Event::InvalidFrameData => "invalid_frame_data\0",
+            HTTP2Event::InvalidHeader => "invalid_header\0",
+            HTTP2Event::InvalidFrameLength => "invalid_frame_length\0",
+            HTTP2Event::ExtraHeaderData => "extra_header_data\0",
+            HTTP2Event::LongFrameData => "long_frame_data\0",
+            HTTP2Event::StreamIdReuse => "stream_id_reuse\0",
+        };
+        unsafe {
+            *event_name = estr.as_ptr() as *const std::os::raw::c_char;
+            *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION;
+        };
+        0
+    } else {
+        -1
+    }
+}
+#[no_mangle]
+pub extern "C" fn rs_http2_state_get_tx_iterator(
+    _ipproto: u8, _alproto: AppProto, state: *mut std::os::raw::c_void, min_tx_id: u64,
+    _max_tx_id: u64, istate: &mut u64,
+) -> applayer::AppLayerGetTxIterTuple {
+    let state = cast_pointer!(state, HTTP2State);
+    match state.tx_iterator(min_tx_id, istate) {
+        Some((tx, out_tx_id, has_next)) => {
+            let c_tx = unsafe { transmute(tx) };
+            let ires = applayer::AppLayerGetTxIterTuple::with_values(c_tx, out_tx_id, has_next);
+            return ires;
+        }
+        None => {
+            return applayer::AppLayerGetTxIterTuple::not_found();
+        }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_getfiles(
+    state: *mut std::os::raw::c_void, direction: u8,
+) -> *mut FileContainer {
+    let state = cast_pointer!(state, HTTP2State);
+    if direction == STREAM_TOCLIENT {
+        &mut state.files.files_tc as *mut FileContainer
+    } else {
+        &mut state.files.files_ts as *mut FileContainer
+    }
+}
+
+// Parser name as a C style string.
+const PARSER_NAME: &'static [u8] = b"http2\0";
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_http2_register_parser() {
+    //TODOend default port
+    let default_port = CString::new("[3000]").unwrap();
+    let parser = RustParser {
+        name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
+        default_port: default_port.as_ptr(),
+        ipproto: IPPROTO_TCP,
+        probe_ts: None, // big magic string should be enough
+        probe_tc: Some(rs_http2_probing_parser_tc),
+        min_depth: HTTP2_FRAME_HEADER_LEN as u16,
+        max_depth: HTTP2_MAGIC_LEN as u16,
+        state_new: rs_http2_state_new,
+        state_free: rs_http2_state_free,
+        tx_free: rs_http2_state_tx_free,
+        parse_ts: rs_http2_parse_ts,
+        parse_tc: rs_http2_parse_tc,
+        get_tx_count: rs_http2_state_get_tx_count,
+        get_tx: rs_http2_state_get_tx,
+        tx_get_comp_st: rs_http2_state_progress_completion_status,
+        tx_get_progress: rs_http2_tx_get_alstate_progress,
+        get_de_state: rs_http2_tx_get_detect_state,
+        set_de_state: rs_http2_tx_set_detect_state,
+        get_events: Some(rs_http2_state_get_events),
+        get_eventinfo: Some(rs_http2_state_get_event_info),
+        get_eventinfo_byid: Some(rs_http2_state_get_event_info_by_id),
+        localstorage_new: None,
+        localstorage_free: None,
+        get_files: Some(rs_http2_getfiles),
+        get_tx_iterator: Some(rs_http2_state_get_tx_iterator),
+        get_tx_data: rs_http2_get_tx_data,
+        apply_tx_config: None,
+        flags: 0,
+    };
+
+    let ip_proto_str = CString::new("tcp").unwrap();
+
+    if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
+        let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
+        ALPROTO_HTTP2 = alproto;
+        if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
+            let _ = AppLayerRegisterParser(&parser, alproto);
+        }
+        SCLogDebug!("Rust http2 parser registered.");
+    } else {
+        SCLogNotice!("Protocol detector and parser disabled for HTTP2.");
+    }
+}
diff --git a/rust/src/http2/huffman.rs b/rust/src/http2/huffman.rs
new file mode 100644 (file)
index 0000000..05ec03a
--- /dev/null
@@ -0,0 +1,504 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use nom::error::ErrorKind;
+use nom::Err;
+use nom::IResult;
+
+fn http2_huffman_table_len5(n: u32) -> Option<u8> {
+    match n {
+        0 => Some(48),
+        1 => Some(49),
+        2 => Some(50),
+        3 => Some(97),
+        4 => Some(99),
+        5 => Some(101),
+        6 => Some(105),
+        7 => Some(111),
+        8 => Some(115),
+        9 => Some(116),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len5<(&[u8], usize), u8>,
+    complete!( map_opt!(take_bits!(5u32), http2_huffman_table_len5) )
+);
+
+fn http2_huffman_table_len6(n: u32) -> Option<u8> {
+    match n {
+        0x14 => Some(32),
+        0x15 => Some(37),
+        0x16 => Some(45),
+        0x17 => Some(46),
+        0x18 => Some(47),
+        0x19 => Some(51),
+        0x1a => Some(52),
+        0x1b => Some(53),
+        0x1c => Some(54),
+        0x1d => Some(55),
+        0x1e => Some(56),
+        0x1f => Some(57),
+        0x20 => Some(61),
+        0x21 => Some(65),
+        0x22 => Some(95),
+        0x23 => Some(98),
+        0x24 => Some(100),
+        0x25 => Some(102),
+        0x26 => Some(103),
+        0x27 => Some(104),
+        0x28 => Some(108),
+        0x29 => Some(109),
+        0x2a => Some(110),
+        0x2b => Some(112),
+        0x2c => Some(114),
+        0x2d => Some(117),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len6<(&[u8], usize), u8>,
+    complete!( map_opt!(take_bits!(6u32), http2_huffman_table_len6))
+);
+
+fn http2_huffman_table_len7(n: u32) -> Option<u8> {
+    match n {
+        0x5c => Some(58),
+        0x5d => Some(66),
+        0x5e => Some(67),
+        0x5f => Some(68),
+        0x60 => Some(69),
+        0x61 => Some(70),
+        0x62 => Some(71),
+        0x63 => Some(72),
+        0x64 => Some(73),
+        0x65 => Some(74),
+        0x66 => Some(75),
+        0x67 => Some(76),
+        0x68 => Some(77),
+        0x69 => Some(78),
+        0x6a => Some(79),
+        0x6b => Some(80),
+        0x6c => Some(81),
+        0x6d => Some(82),
+        0x6e => Some(83),
+        0x6f => Some(84),
+        0x70 => Some(85),
+        0x71 => Some(86),
+        0x72 => Some(87),
+        0x73 => Some(89),
+        0x74 => Some(106),
+        0x75 => Some(107),
+        0x76 => Some(113),
+        0x77 => Some(118),
+        0x78 => Some(119),
+        0x79 => Some(120),
+        0x7a => Some(121),
+        0x7b => Some(122),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len7<(&[u8], usize), u8>,
+complete!( map_opt!(take_bits!(7u32), http2_huffman_table_len7))
+);
+
+fn http2_huffman_table_len8(n: u32) -> Option<u8> {
+    match n {
+        0xf8 => Some(38),
+        0xf9 => Some(42),
+        0xfa => Some(44),
+        0xfb => Some(59),
+        0xfc => Some(88),
+        0xfd => Some(90),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len8<(&[u8], usize), u8>,
+complete!( map_opt!(take_bits!(8u32), http2_huffman_table_len8))
+);
+
+fn http2_huffman_table_len10(n: u32) -> Option<u8> {
+    match n {
+        0x3f8 => Some(33),
+        0x3f9 => Some(34),
+        0x3fa => Some(40),
+        0x3fb => Some(41),
+        0x3fc => Some(63),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len10<(&[u8], usize), u8>,
+complete!( map_opt!(take_bits!(10u32), http2_huffman_table_len10))
+);
+
+fn http2_huffman_table_len11(n: u32) -> Option<u8> {
+    match n {
+        0x7fa => Some(39),
+        0x7fb => Some(43),
+        0x7fc => Some(124),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len11<(&[u8], usize), u8>,
+complete!( map_opt!(take_bits!(11u32), http2_huffman_table_len11))
+);
+
+fn http2_huffman_table_len12(n: u32) -> Option<u8> {
+    match n {
+        0xffa => Some(35),
+        0xffb => Some(62),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len12<(&[u8], usize), u8>,
+complete!( map_opt!(take_bits!(12u32), http2_huffman_table_len12))
+);
+
+fn http2_huffman_table_len13(n: u32) -> Option<u8> {
+    match n {
+        0x1ff8 => Some(0),
+        0x1ff9 => Some(36),
+        0x1ffa => Some(64),
+        0x1ffb => Some(91),
+        0x1ffc => Some(93),
+        0x1ffd => Some(126),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len13<(&[u8], usize), u8>,
+complete!( map_opt!(take_bits!(13u32), http2_huffman_table_len13))
+);
+
+fn http2_huffman_table_len14(n: u32) -> Option<u8> {
+    match n {
+        0x3ffc => Some(94),
+        0x3ffd => Some(125),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len14<(&[u8], usize), u8>,
+complete!( map_opt!(take_bits!(14u32), http2_huffman_table_len14))
+);
+
+fn http2_huffman_table_len15(n: u32) -> Option<u8> {
+    match n {
+        0x7ffc => Some(60),
+        0x7ffd => Some(96),
+        0x7ffe => Some(123),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len15<(&[u8], usize), u8>,
+complete!( map_opt!(take_bits!(15u32), http2_huffman_table_len15))
+);
+
+fn http2_huffman_table_len19(n: u32) -> Option<u8> {
+    match n {
+        0x7fff0 => Some(92),
+        0x7fff1 => Some(195),
+        0x7fff2 => Some(208),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len19<(&[u8], usize), u8>,
+complete!( map_opt!(take_bits!(19u32), http2_huffman_table_len19))
+);
+
+fn http2_huffman_table_len20(n: u32) -> Option<u8> {
+    match n {
+        0xfffe6 => Some(128),
+        0xfffe7 => Some(130),
+        0xfffe8 => Some(131),
+        0xfffe9 => Some(162),
+        0xfffea => Some(184),
+        0xfffeb => Some(194),
+        0xfffec => Some(224),
+        0xfffed => Some(226),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len20<(&[u8], usize), u8>,
+complete!( map_opt!(take_bits!(20u32), http2_huffman_table_len20))
+);
+
+fn http2_huffman_table_len21(n: u32) -> Option<u8> {
+    match n {
+        0x1fffdc => Some(153),
+        0x1fffdd => Some(161),
+        0x1fffde => Some(167),
+        0x1fffdf => Some(172),
+        0x1fffe0 => Some(176),
+        0x1fffe1 => Some(177),
+        0x1fffe2 => Some(179),
+        0x1fffe3 => Some(209),
+        0x1fffe4 => Some(216),
+        0x1fffe5 => Some(217),
+        0x1fffe6 => Some(227),
+        0x1fffe7 => Some(229),
+        0x1fffe8 => Some(230),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len21<(&[u8], usize), u8>,
+complete!( map_opt!(take_bits!(21u32), http2_huffman_table_len21))
+);
+
+fn http2_huffman_table_len22(n: u32) -> Option<u8> {
+    match n {
+        0x3fffd2 => Some(129),
+        0x3fffd3 => Some(132),
+        0x3fffd4 => Some(133),
+        0x3fffd5 => Some(134),
+        0x3fffd6 => Some(136),
+        0x3fffd7 => Some(146),
+        0x3fffd8 => Some(154),
+        0x3fffd9 => Some(156),
+        0x3fffda => Some(160),
+        0x3fffdb => Some(163),
+        0x3fffdc => Some(164),
+        0x3fffdd => Some(169),
+        0x3fffde => Some(170),
+        0x3fffdf => Some(173),
+        0x3fffe0 => Some(178),
+        0x3fffe1 => Some(181),
+        0x3fffe2 => Some(185),
+        0x3fffe3 => Some(186),
+        0x3fffe4 => Some(187),
+        0x3fffe5 => Some(189),
+        0x3fffe6 => Some(190),
+        0x3fffe7 => Some(196),
+        0x3fffe8 => Some(198),
+        0x3fffe9 => Some(228),
+        0x3fffea => Some(232),
+        0x3fffeb => Some(233),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len22<(&[u8], usize), u8>,
+complete!( map_opt!(take_bits!(22u32), http2_huffman_table_len22))
+);
+
+fn http2_huffman_table_len23(n: u32) -> Option<u8> {
+    match n {
+        0x7fffd8 => Some(1),
+        0x7fffd9 => Some(135),
+        0x7fffda => Some(137),
+        0x7fffdb => Some(138),
+        0x7fffdc => Some(139),
+        0x7fffdd => Some(140),
+        0x7fffde => Some(141),
+        0x7fffdf => Some(143),
+        0x7fffe0 => Some(147),
+        0x7fffe1 => Some(149),
+        0x7fffe2 => Some(150),
+        0x7fffe3 => Some(151),
+        0x7fffe4 => Some(152),
+        0x7fffe5 => Some(155),
+        0x7fffe6 => Some(157),
+        0x7fffe7 => Some(158),
+        0x7fffe8 => Some(165),
+        0x7fffe9 => Some(166),
+        0x7fffea => Some(168),
+        0x7fffeb => Some(174),
+        0x7fffec => Some(175),
+        0x7fffed => Some(180),
+        0x7fffee => Some(182),
+        0x7fffef => Some(183),
+        0x7ffff0 => Some(188),
+        0x7ffff1 => Some(191),
+        0x7ffff2 => Some(197),
+        0x7ffff3 => Some(231),
+        0x7ffff4 => Some(239),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len23<(&[u8], usize), u8>,
+    complete!( map_opt!(take_bits!(23u32), http2_huffman_table_len23))
+);
+
+fn http2_huffman_table_len24(n: u32) -> Option<u8> {
+    match n {
+        0xffffea => Some(9),
+        0xffffeb => Some(142),
+        0xffffec => Some(144),
+        0xffffed => Some(145),
+        0xffffee => Some(148),
+        0xffffef => Some(159),
+        0xfffff0 => Some(171),
+        0xfffff1 => Some(206),
+        0xfffff2 => Some(215),
+        0xfffff3 => Some(225),
+        0xfffff4 => Some(236),
+        0xfffff5 => Some(237),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len24<(&[u8], usize), u8>,
+    complete!( map_opt!(take_bits!(24u32), http2_huffman_table_len24))
+);
+
+fn http2_huffman_table_len25(n: u32) -> Option<u8> {
+    match n {
+        0x1ffffec => Some(199),
+        0x1ffffed => Some(207),
+        0x1ffffee => Some(234),
+        0x1ffffef => Some(235),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len25<(&[u8], usize), u8>,
+    complete!( map_opt!(take_bits!(25u32), http2_huffman_table_len25))
+);
+
+fn http2_huffman_table_len26(n: u32) -> Option<u8> {
+    match n {
+        0x3ffffe0 => Some(192),
+        0x3ffffe1 => Some(193),
+        0x3ffffe2 => Some(200),
+        0x3ffffe3 => Some(201),
+        0x3ffffe4 => Some(202),
+        0x3ffffe5 => Some(205),
+        0x3ffffe6 => Some(210),
+        0x3ffffe7 => Some(213),
+        0x3ffffe8 => Some(218),
+        0x3ffffe9 => Some(219),
+        0x3ffffea => Some(238),
+        0x3ffffeb => Some(240),
+        0x3ffffec => Some(242),
+        0x3ffffed => Some(243),
+        0x3ffffee => Some(255),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len26<(&[u8], usize), u8>,
+    complete!( map_opt!(take_bits!(26u32), http2_huffman_table_len26))
+);
+
+fn http2_huffman_table_len27(n: u32) -> Option<u8> {
+    match n {
+        0x7ffffde => Some(203),
+        0x7ffffdf => Some(204),
+        0x7ffffe0 => Some(211),
+        0x7ffffe1 => Some(212),
+        0x7ffffe2 => Some(214),
+        0x7ffffe3 => Some(221),
+        0x7ffffe4 => Some(222),
+        0x7ffffe5 => Some(223),
+        0x7ffffe6 => Some(241),
+        0x7ffffe7 => Some(244),
+        0x7ffffe8 => Some(245),
+        0x7ffffe9 => Some(246),
+        0x7ffffea => Some(247),
+        0x7ffffeb => Some(248),
+        0x7ffffec => Some(250),
+        0x7ffffed => Some(251),
+        0x7ffffee => Some(252),
+        0x7ffffef => Some(253),
+        0x7fffff0 => Some(254),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len27<(&[u8], usize), u8>,
+    complete!( map_opt!(take_bits!(27u32), http2_huffman_table_len27))
+);
+
+fn http2_huffman_table_len28(n: u32) -> Option<u8> {
+    match n {
+        0xfffffe2 => Some(2),
+        0xfffffe3 => Some(3),
+        0xfffffe4 => Some(4),
+        0xfffffe5 => Some(5),
+        0xfffffe6 => Some(6),
+        0xfffffe7 => Some(7),
+        0xfffffe8 => Some(8),
+        0xfffffe9 => Some(11),
+        0xfffffea => Some(12),
+        0xfffffeb => Some(14),
+        0xfffffec => Some(15),
+        0xfffffed => Some(16),
+        0xfffffee => Some(17),
+        0xfffffef => Some(18),
+        0xffffff0 => Some(19),
+        0xffffff1 => Some(20),
+        0xffffff2 => Some(21),
+        0xffffff3 => Some(23),
+        0xffffff4 => Some(24),
+        0xffffff5 => Some(25),
+        0xffffff6 => Some(26),
+        0xffffff7 => Some(27),
+        0xffffff8 => Some(28),
+        0xffffff9 => Some(29),
+        0xffffffa => Some(30),
+        0xffffffb => Some(31),
+        0xffffffc => Some(127),
+        0xffffffd => Some(220),
+        0xffffffe => Some(249),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len28<(&[u8], usize), u8>,
+    complete!( map_opt!(take_bits!(28u32), http2_huffman_table_len28))
+);
+
+fn http2_huffman_table_len30(n: u32) -> Option<u8> {
+    match n {
+        0x3ffffffc => Some(10),
+        0x3ffffffd => Some(13),
+        0x3ffffffe => Some(22),
+        // 0x3fffffff => Some(256),
+        _ => None,
+    }
+}
+
+named!(http2_decode_huffman_len30<(&[u8], usize), u8>,
+    complete!( map_opt!(take_bits!(30u32), http2_huffman_table_len30))
+);
+
+//hack to end many0 even if some bits are remaining
+fn http2_decode_huffman_end(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> {
+    return Err(Err::Error((input, ErrorKind::Eof)));
+}
+
+//we could profile and optimize performance here
+named!(pub http2_decode_huffman<(&[u8], usize), u8>,
+    alt!(http2_decode_huffman_len5 | http2_decode_huffman_len6 | http2_decode_huffman_len7 |
+    http2_decode_huffman_len8 | http2_decode_huffman_len10 | http2_decode_huffman_len11 |
+    http2_decode_huffman_len12 | http2_decode_huffman_len13 | http2_decode_huffman_len14 |
+    http2_decode_huffman_len15 | http2_decode_huffman_len19 | http2_decode_huffman_len20 |
+    http2_decode_huffman_len21 | http2_decode_huffman_len22 | http2_decode_huffman_len23 |
+    http2_decode_huffman_len24 | http2_decode_huffman_len25 | http2_decode_huffman_len26 |
+    http2_decode_huffman_len27 | http2_decode_huffman_len28 | http2_decode_huffman_len30 |
+    http2_decode_huffman_end)
+);
diff --git a/rust/src/http2/logger.rs b/rust/src/http2/logger.rs
new file mode 100644 (file)
index 0000000..5a43e62
--- /dev/null
@@ -0,0 +1,184 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use super::http2::{HTTP2Frame, HTTP2FrameTypeData, HTTP2Transaction};
+use super::parser;
+use crate::jsonbuilder::{JsonBuilder, JsonError};
+use std;
+
+fn log_http2_headers(
+    blocks: &Vec<parser::HTTP2FrameHeaderBlock>, js: &mut JsonBuilder,
+) -> Result<(), JsonError> {
+    for j in 0..blocks.len() {
+        js.start_object()?;
+        match blocks[j].error {
+            parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess => {
+                js.set_string_from_bytes("name", &blocks[j].name)?;
+                js.set_string_from_bytes("value", &blocks[j].value)?;
+            }
+            parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate => {
+                js.set_uint("table_size_update", blocks[j].sizeupdate)?;
+            }
+            _ => {
+                js.set_string("error", &blocks[j].error.to_string())?;
+            }
+        }
+        js.close()?;
+    }
+    return Ok(());
+}
+
+fn log_http2_frames(frames: &Vec<HTTP2Frame>, js: &mut JsonBuilder) -> Result<bool, JsonError> {
+    let mut has_settings = false;
+    for i in 0..frames.len() {
+        if let HTTP2FrameTypeData::SETTINGS(set) = &frames[i].data {
+            if !has_settings {
+                js.open_array("settings")?;
+                has_settings = true;
+            }
+            for j in 0..set.len() {
+                js.start_object()?;
+                js.set_string("settings_id", &set[j].id.to_string())?;
+                js.set_uint("settings_value", set[j].value as u64)?;
+                js.close()?;
+            }
+        }
+    }
+    if has_settings {
+        js.close()?;
+    }
+
+    let mut has_headers = false;
+    for i in 0..frames.len() {
+        match &frames[i].data {
+            HTTP2FrameTypeData::HEADERS(hd) => {
+                if !has_headers {
+                    js.open_array("headers")?;
+                    has_headers = true;
+                }
+                log_http2_headers(&hd.blocks, js)?;
+            }
+            HTTP2FrameTypeData::PUSHPROMISE(hd) => {
+                if !has_headers {
+                    js.open_array("headers")?;
+                    has_headers = true;
+                }
+                log_http2_headers(&hd.blocks, js)?;
+            }
+            HTTP2FrameTypeData::CONTINUATION(hd) => {
+                if !has_headers {
+                    js.open_array("headers")?;
+                    has_headers = true;
+                }
+                log_http2_headers(&hd.blocks, js)?;
+            }
+            _ => {}
+        }
+    }
+    if has_headers {
+        js.close()?;
+    }
+
+    let mut has_error_code = false;
+    let mut has_priority = false;
+    let mut has_multiple = false;
+    for i in 0..frames.len() {
+        match &frames[i].data {
+            HTTP2FrameTypeData::GOAWAY(goaway) => {
+                if !has_error_code {
+                    let errcode: Option<parser::HTTP2ErrorCode> =
+                        num::FromPrimitive::from_u32(goaway.errorcode);
+                    match errcode {
+                        Some(errstr) => {
+                            js.set_string("error_code", &errstr.to_string())?;
+                        }
+                        None => {
+                            //use uint32
+                            js.set_string("error_code", &goaway.errorcode.to_string())?;
+                        }
+                    }
+                    has_error_code = true;
+                } else if !has_multiple {
+                    js.set_string("has_multiple", "error_code")?;
+                    has_multiple = true;
+                }
+            }
+            HTTP2FrameTypeData::RSTSTREAM(rst) => {
+                if !has_error_code {
+                    let errcode: Option<parser::HTTP2ErrorCode> =
+                        num::FromPrimitive::from_u32(rst.errorcode);
+                    match errcode {
+                        Some(errstr) => {
+                            js.set_string("error_code", &errstr.to_string())?;
+                        }
+                        None => {
+                            //use uint32
+                            js.set_string("error_code", &rst.errorcode.to_string())?;
+                        }
+                    }
+                    has_error_code = true;
+                } else if !has_multiple {
+                    js.set_string("has_multiple", "error_code")?;
+                    has_multiple = true;
+                }
+            }
+            HTTP2FrameTypeData::PRIORITY(priority) => {
+                if !has_priority {
+                    js.set_uint("priority", priority.weight as u64)?;
+                    has_priority = true;
+                } else if !has_multiple {
+                    js.set_string("has_multiple", "priority")?;
+                    has_multiple = true;
+                }
+            }
+            HTTP2FrameTypeData::HEADERS(hd) => {
+                if let Some(ref priority) = hd.priority {
+                    if !has_priority {
+                        js.set_uint("priority", priority.weight as u64)?;
+                        has_priority = true;
+                    } else if !has_multiple {
+                        js.set_string("has_multiple", "priority")?;
+                        has_multiple = true;
+                    }
+                }
+            }
+            _ => {}
+        }
+    }
+    return Ok(has_settings || has_headers || has_error_code || has_priority);
+}
+
+fn log_http2(tx: &HTTP2Transaction, js: &mut JsonBuilder) -> Result<bool, JsonError> {
+    js.set_uint("stream_id", tx.stream_id as u64)?;
+    js.open_object("request")?;
+    let has_request = log_http2_frames(&tx.frames_ts, js)?;
+    js.close()?;
+    js.open_object("response")?;
+    let has_response = log_http2_frames(&tx.frames_tc, js)?;
+    js.close()?;
+
+    return Ok(has_request || has_response);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_http2_log_json(tx: *mut std::os::raw::c_void, js: &mut JsonBuilder) -> bool {
+    let tx = cast_pointer!(tx, HTTP2Transaction);
+    if let Ok(x) = log_http2(tx, js) {
+        return x;
+    }
+    return false;
+}
diff --git a/rust/src/http2/mod.rs b/rust/src/http2/mod.rs
new file mode 100644 (file)
index 0000000..ad86bdf
--- /dev/null
@@ -0,0 +1,23 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+pub mod detect;
+pub mod files;
+pub mod http2;
+mod huffman;
+pub mod logger;
+mod parser;
diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs
new file mode 100644 (file)
index 0000000..d357e27
--- /dev/null
@@ -0,0 +1,1155 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use super::huffman;
+use nom::character::complete::digit1;
+use nom::combinator::rest;
+use nom::error::ErrorKind;
+use nom::number::streaming::{be_u16, be_u32, be_u8};
+use nom::Err;
+use nom::IResult;
+use std::fmt;
+use std::str::FromStr;
+
+#[repr(u8)]
+#[derive(Clone, Copy, PartialEq, FromPrimitive, Debug)]
+pub enum HTTP2FrameType {
+    DATA = 0,
+    HEADERS = 1,
+    PRIORITY = 2,
+    RSTSTREAM = 3,
+    SETTINGS = 4,
+    PUSHPROMISE = 5,
+    PING = 6,
+    GOAWAY = 7,
+    WINDOWUPDATE = 8,
+    CONTINUATION = 9,
+}
+
+impl fmt::Display for HTTP2FrameType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
+impl std::str::FromStr for HTTP2FrameType {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let su = s.to_uppercase();
+        let su_slice: &str = &*su;
+        match su_slice {
+            "DATA" => Ok(HTTP2FrameType::DATA),
+            "HEADERS" => Ok(HTTP2FrameType::HEADERS),
+            "PRIORITY" => Ok(HTTP2FrameType::PRIORITY),
+            "RSTSTREAM" => Ok(HTTP2FrameType::RSTSTREAM),
+            "SETTINGS" => Ok(HTTP2FrameType::SETTINGS),
+            "PUSHPROMISE" => Ok(HTTP2FrameType::PUSHPROMISE),
+            "PING" => Ok(HTTP2FrameType::PING),
+            "GOAWAY" => Ok(HTTP2FrameType::GOAWAY),
+            "WINDOWUPDATE" => Ok(HTTP2FrameType::WINDOWUPDATE),
+            "CONTINUATION" => Ok(HTTP2FrameType::CONTINUATION),
+            _ => Err(format!("'{}' is not a valid value for HTTP2FrameType", s)),
+        }
+    }
+}
+
+#[derive(PartialEq)]
+pub struct HTTP2FrameHeader {
+    //we could add detection on (GOAWAY) additional data
+    pub length: u32,
+    pub ftype: u8,
+    pub flags: u8,
+    pub reserved: u8,
+    pub stream_id: u32,
+}
+
+named!(pub http2_parse_frame_header<HTTP2FrameHeader>,
+    do_parse!(
+        length: bits!( take_bits!(24u32) ) >>
+        ftype: be_u8 >>
+        flags: be_u8 >>
+        stream_id: bits!( tuple!( take_bits!(1u8),
+                                  take_bits!(31u32) ) ) >>
+        (HTTP2FrameHeader{length, ftype, flags,
+                          reserved:stream_id.0,
+                          stream_id:stream_id.1})
+    )
+);
+
+#[repr(u32)]
+#[derive(Clone, Copy, PartialEq, FromPrimitive, Debug)]
+pub enum HTTP2ErrorCode {
+    NOERROR = 0,
+    PROTOCOLERROR = 1,
+    INTERNALERROR = 2,
+    FLOWCONTROLERROR = 3,
+    SETTINGSTIMEOUT = 4,
+    STREAMCLOSED = 5,
+    FRAMESIZEERROR = 6,
+    REFUSEDSTREAM = 7,
+    CANCEL = 8,
+    COMPRESSIONERROR = 9,
+    CONNECTERROR = 10,
+    ENHANCEYOURCALM = 11,
+    INADEQUATESECURITY = 12,
+    HTTP11REQUIRED = 13,
+}
+
+impl fmt::Display for HTTP2ErrorCode {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
+impl std::str::FromStr for HTTP2ErrorCode {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let su = s.to_uppercase();
+        let su_slice: &str = &*su;
+        match su_slice {
+            "NO_ERROR" => Ok(HTTP2ErrorCode::NOERROR),
+            "PROTOCOL_ERROR" => Ok(HTTP2ErrorCode::PROTOCOLERROR),
+            "FLOW_CONTROL_ERROR" => Ok(HTTP2ErrorCode::FLOWCONTROLERROR),
+            "SETTINGS_TIMEOUT" => Ok(HTTP2ErrorCode::SETTINGSTIMEOUT),
+            "STREAM_CLOSED" => Ok(HTTP2ErrorCode::STREAMCLOSED),
+            "FRAME_SIZE_ERROR" => Ok(HTTP2ErrorCode::FRAMESIZEERROR),
+            "REFUSED_STREAM" => Ok(HTTP2ErrorCode::REFUSEDSTREAM),
+            "CANCEL" => Ok(HTTP2ErrorCode::CANCEL),
+            "COMPRESSION_ERROR" => Ok(HTTP2ErrorCode::COMPRESSIONERROR),
+            "CONNECT_ERROR" => Ok(HTTP2ErrorCode::CONNECTERROR),
+            "ENHANCE_YOUR_CALM" => Ok(HTTP2ErrorCode::ENHANCEYOURCALM),
+            "INADEQUATE_SECURITY" => Ok(HTTP2ErrorCode::INADEQUATESECURITY),
+            "HTTP_1_1_REQUIRED" => Ok(HTTP2ErrorCode::HTTP11REQUIRED),
+            _ => Err(format!("'{}' is not a valid value for HTTP2ErrorCode", s)),
+        }
+    }
+}
+
+#[derive(Clone, Copy)]
+pub struct HTTP2FrameGoAway {
+    pub errorcode: u32, //HTTP2ErrorCode
+}
+
+named!(pub http2_parse_frame_goaway<HTTP2FrameGoAway>,
+    do_parse!(
+        errorcode: be_u32 >>
+        (HTTP2FrameGoAway{errorcode})
+    )
+);
+
+#[derive(Clone, Copy)]
+pub struct HTTP2FrameRstStream {
+    pub errorcode: u32, ////HTTP2ErrorCode
+}
+
+named!(pub http2_parse_frame_rststream<HTTP2FrameRstStream>,
+    do_parse!(
+        errorcode: be_u32 >>
+        (HTTP2FrameRstStream{errorcode})
+    )
+);
+
+#[derive(Clone, Copy)]
+pub struct HTTP2FramePriority {
+    pub weight: u8,
+}
+
+named!(pub http2_parse_frame_priority<HTTP2FramePriority>,
+    do_parse!(
+        weight: be_u8 >>
+        (HTTP2FramePriority{weight})
+    )
+);
+
+#[derive(Clone, Copy)]
+pub struct HTTP2FrameWindowUpdate {
+    pub reserved: u8,
+    pub sizeinc: u32,
+}
+
+named!(pub http2_parse_frame_windowupdate<HTTP2FrameWindowUpdate>,
+    do_parse!(
+        sizeinc: bits!( tuple!( take_bits!(1u8),
+                                take_bits!(31u32) ) ) >>
+        (HTTP2FrameWindowUpdate{reserved:sizeinc.0, sizeinc:sizeinc.1})
+    )
+);
+
+#[derive(Clone, Copy)]
+pub struct HTTP2FrameHeadersPriority {
+    pub exclusive: u8,
+    pub dependency: u32,
+    pub weight: u8,
+}
+
+named!(pub http2_parse_headers_priority<HTTP2FrameHeadersPriority>,
+    do_parse!(
+        sid: bits!( tuple!( take_bits!(1u8),
+                                take_bits!(31u32) ) ) >>
+        weight: be_u8 >>
+        (HTTP2FrameHeadersPriority{exclusive:sid.0, dependency:sid.1, weight})
+    )
+);
+
+pub const HTTP2_STATIC_HEADERS_NUMBER: usize = 61;
+
+fn http2_frame_header_static(
+    n: u8, dyn_headers: &Vec<HTTP2FrameHeaderBlock>,
+) -> Option<HTTP2FrameHeaderBlock> {
+    let (name, value) = match n {
+        1 => (":authority", ""),
+        2 => (":method", "GET"),
+        3 => (":method", "POST"),
+        4 => (":path", "/"),
+        5 => (":path", "/index.html"),
+        6 => (":scheme", "http"),
+        7 => (":scheme", "https"),
+        8 => (":status", "200"),
+        9 => (":status", "204"),
+        10 => (":status", "206"),
+        11 => (":status", "304"),
+        12 => (":status", "400"),
+        13 => (":status", "404"),
+        14 => (":status", "500"),
+        15 => ("accept-charset", ""),
+        16 => ("accept-encoding", "gzip, deflate"),
+        17 => ("accept-language", ""),
+        18 => ("accept-ranges", ""),
+        19 => ("accept", ""),
+        20 => ("access-control-allow-origin", ""),
+        21 => ("age", ""),
+        22 => ("allow", ""),
+        23 => ("authorization", ""),
+        24 => ("cache-control", ""),
+        25 => ("content-disposition", ""),
+        26 => ("content-encoding", ""),
+        27 => ("content-language", ""),
+        28 => ("content-length", ""),
+        29 => ("content-location", ""),
+        30 => ("content-range", ""),
+        31 => ("content-type", ""),
+        32 => ("cookie", ""),
+        33 => ("date", ""),
+        34 => ("etag", ""),
+        35 => ("expect", ""),
+        36 => ("expires", ""),
+        37 => ("from", ""),
+        38 => ("host", ""),
+        39 => ("if-match", ""),
+        40 => ("if-modified-since", ""),
+        41 => ("if-none-match", ""),
+        42 => ("if-range", ""),
+        43 => ("if-unmodified-since", ""),
+        44 => ("last-modified", ""),
+        45 => ("link", ""),
+        46 => ("location", ""),
+        47 => ("max-forwards", ""),
+        48 => ("proxy-authenticate", ""),
+        49 => ("proxy-authorization", ""),
+        50 => ("range", ""),
+        51 => ("referer", ""),
+        52 => ("refresh", ""),
+        53 => ("retry-after", ""),
+        54 => ("server", ""),
+        55 => ("set-cookie", ""),
+        56 => ("strict-transport-security", ""),
+        57 => ("transfer-encoding", ""),
+        58 => ("user-agent", ""),
+        59 => ("vary", ""),
+        60 => ("via", ""),
+        61 => ("www-authenticate", ""),
+        _ => ("", ""),
+    };
+    if name.len() > 0 {
+        return Some(HTTP2FrameHeaderBlock {
+            name: name.as_bytes().to_vec(),
+            value: value.as_bytes().to_vec(),
+            error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess,
+            sizeupdate: 0,
+        });
+    } else {
+        //use dynamic table
+        if dyn_headers.len() + HTTP2_STATIC_HEADERS_NUMBER < n as usize {
+            return Some(HTTP2FrameHeaderBlock {
+                name: Vec::new(),
+                value: Vec::new(),
+                error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeNotIndexed,
+                sizeupdate: 0,
+            });
+        } else {
+            let indyn = dyn_headers.len() - (n as usize - HTTP2_STATIC_HEADERS_NUMBER);
+            let headcopy = HTTP2FrameHeaderBlock {
+                name: dyn_headers[indyn].name.to_vec(),
+                value: dyn_headers[indyn].value.to_vec(),
+                error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess,
+                sizeupdate: 0,
+            };
+            return Some(headcopy);
+        }
+    }
+}
+
+#[repr(u8)]
+#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
+pub enum HTTP2HeaderDecodeStatus {
+    HTTP2HeaderDecodeSuccess = 0,
+    HTTP2HeaderDecodeSizeUpdate = 1,
+    HTTP2HeaderDecodeError = 0x80,
+    HTTP2HeaderDecodeNotIndexed = 0x81,
+    HTTP2HeaderDecodeIntegerOverflow = 0x82,
+}
+
+impl fmt::Display for HTTP2HeaderDecodeStatus {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct HTTP2FrameHeaderBlock {
+    pub name: Vec<u8>,
+    pub value: Vec<u8>,
+    pub error: HTTP2HeaderDecodeStatus,
+    pub sizeupdate: u64,
+}
+
+fn http2_parse_headers_block_indexed<'a>(
+    input: &'a [u8], dyn_headers: &Vec<HTTP2FrameHeaderBlock>,
+) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
+    fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> {
+        bits!(
+            input,
+            complete!(tuple!(
+                verify!(take_bits!(1u8), |&x| x == 1),
+                take_bits!(7u8)
+            ))
+        )
+    }
+    let (i2, indexed) = parser(input)?;
+    match http2_frame_header_static(indexed.1, dyn_headers) {
+        Some(h) => Ok((i2, h)),
+        _ => Err(Err::Error((i2, ErrorKind::MapOpt))),
+    }
+}
+
+fn http2_parse_headers_block_string(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
+    fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> {
+        bits!(input, tuple!(take_bits!(1u8), take_bits!(7u8)))
+    }
+    let (i2, huffslen) = parser(input)?;
+    let (i3, data) = take!(i2, huffslen.1 as usize)?;
+    if huffslen.0 == 0 {
+        return Ok((i3, data.to_vec()));
+    } else {
+        let (_, val) = bits!(data, many0!(huffman::http2_decode_huffman))?;
+        return Ok((i3, val));
+    }
+}
+
+fn http2_parse_headers_block_literal_common<'a>(
+    input: &'a [u8], index: u8, dyn_headers: &Vec<HTTP2FrameHeaderBlock>,
+) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
+    let (i3, name, error) = if index == 0 {
+        match http2_parse_headers_block_string(input) {
+            Ok((r, n)) => Ok((r, n, HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess)),
+            Err(e) => Err(e),
+        }
+    } else {
+        match http2_frame_header_static(index, dyn_headers) {
+            Some(x) => Ok((
+                input,
+                x.name,
+                HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess,
+            )),
+            None => Ok((
+                input,
+                Vec::new(),
+                HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeNotIndexed,
+            )),
+        }
+    }?;
+    let (i4, value) = http2_parse_headers_block_string(i3)?;
+    return Ok((
+        i4,
+        HTTP2FrameHeaderBlock {
+            name,
+            value,
+            error,
+            sizeupdate: 0,
+        },
+    ));
+}
+
+fn http2_parse_headers_block_literal_incindex<'a>(
+    input: &'a [u8], dyn_headers: &mut Vec<HTTP2FrameHeaderBlock>,
+) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
+    fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> {
+        bits!(
+            input,
+            complete!(tuple!(
+                verify!(take_bits!(2u8), |&x| x == 1),
+                take_bits!(6u8)
+            ))
+        )
+    }
+    let (i2, indexed) = parser(input)?;
+    let (i3, indexreal) = if indexed.1 == 0x3F {
+        map!(i2, be_u8, |i| i + 0x3F)
+    } else {
+        Ok((i2, indexed.1))
+    }?;
+    let r = http2_parse_headers_block_literal_common(i3, indexreal, dyn_headers);
+    match r {
+        Ok((r, head)) => {
+            let headcopy = HTTP2FrameHeaderBlock {
+                name: head.name.to_vec(),
+                value: head.value.to_vec(),
+                error: head.error,
+                sizeupdate: 0,
+            };
+            dyn_headers.push(headcopy);
+            if dyn_headers.len() > 255 - HTTP2_STATIC_HEADERS_NUMBER {
+                dyn_headers.remove(0);
+            }
+            //we do not limit the dynamic table size
+            return Ok((r, head));
+        }
+        Err(e) => {
+            return Err(e);
+        }
+    }
+}
+
+fn http2_parse_headers_block_literal_noindex<'a>(
+    input: &'a [u8], dyn_headers: &Vec<HTTP2FrameHeaderBlock>,
+) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
+    fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> {
+        bits!(
+            input,
+            complete!(tuple!(
+                verify!(take_bits!(4u8), |&x| x == 0),
+                take_bits!(4u8)
+            ))
+        )
+    }
+    let (i2, indexed) = parser(input)?;
+    //undocumented in RFC ?! found with wireshark
+    let (i3, indexreal) = if indexed.1 == 0xF {
+        map!(i2, be_u8, |i| i + 0xF)
+    } else {
+        Ok((i2, indexed.1))
+    }?;
+    let r = http2_parse_headers_block_literal_common(i3, indexreal, dyn_headers);
+    return r;
+}
+
+fn http2_parse_headers_block_literal_neverindex<'a>(
+    input: &'a [u8], dyn_headers: &Vec<HTTP2FrameHeaderBlock>,
+) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
+    fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> {
+        bits!(
+            input,
+            complete!(tuple!(
+                verify!(take_bits!(4u8), |&x| x == 1),
+                take_bits!(4u8)
+            ))
+        )
+    }
+    let (i2, indexed) = parser(input)?;
+    let (i3, indexreal) = if indexed.1 == 0xF {
+        map!(i2, be_u8, |i| i + 0xF)
+    } else {
+        Ok((i2, indexed.1))
+    }?;
+    let r = http2_parse_headers_block_literal_common(i3, indexreal, dyn_headers);
+    return r;
+}
+
+fn http2_parse_headers_block_dynamic_size(input: &[u8]) -> IResult<&[u8], HTTP2FrameHeaderBlock> {
+    fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> {
+        bits!(
+            input,
+            complete!(tuple!(
+                verify!(take_bits!(3u8), |&x| x == 1),
+                take_bits!(5u8)
+            ))
+        )
+    }
+    let (i2, maxsize) = parser(input)?;
+    if maxsize.1 == 31 {
+        let (i3, maxsize2) = take_while!(i2, |ch| (ch & 0x80) != 0)?;
+        let (i4, maxsize3) = be_u8(i3)?;
+        //9 is maximum size to encode a variable length u64
+        if maxsize2.len() > 9 {
+            return Ok((
+                i4,
+                HTTP2FrameHeaderBlock {
+                    name: Vec::new(),
+                    value: Vec::new(),
+                    error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIntegerOverflow,
+                    sizeupdate: 0,
+                },
+            ));
+        }
+        let mut sizeupdate = 31 as u64;
+        for i in 0..maxsize2.len() {
+            sizeupdate += ((maxsize2[i] & 0x7F) as u64) << (7 * i);
+        }
+        sizeupdate += (maxsize3 as u64) << (7 * maxsize2.len());
+        return Ok((
+            i4,
+            HTTP2FrameHeaderBlock {
+                name: Vec::new(),
+                value: Vec::new(),
+                error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate,
+                sizeupdate: sizeupdate,
+            },
+        ));
+    }
+    return Ok((
+        i2,
+        HTTP2FrameHeaderBlock {
+            name: Vec::new(),
+            value: Vec::new(),
+            error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate,
+            sizeupdate: maxsize.1 as u64,
+        },
+    ));
+}
+
+fn http2_parse_headers_block<'a>(
+    input: &'a [u8], dyn_headers: &mut Vec<HTTP2FrameHeaderBlock>,
+) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> {
+    //caller garantees o have at least one byte
+    if input[0] & 0x80 != 0 {
+        return http2_parse_headers_block_indexed(input, dyn_headers);
+    } else if input[0] & 0x40 != 0 {
+        return http2_parse_headers_block_literal_incindex(input, dyn_headers);
+    } else if input[0] & 0x20 != 0 {
+        return http2_parse_headers_block_dynamic_size(input);
+    } else if input[0] & 0x10 != 0 {
+        return http2_parse_headers_block_literal_neverindex(input, dyn_headers);
+    } else {
+        return http2_parse_headers_block_literal_noindex(input, dyn_headers);
+    }
+}
+
+#[derive(Clone)]
+pub struct HTTP2FrameHeaders {
+    pub padlength: Option<u8>,
+    pub priority: Option<HTTP2FrameHeadersPriority>,
+    pub blocks: Vec<HTTP2FrameHeaderBlock>,
+}
+
+//end stream
+pub const HTTP2_FLAG_HEADER_EOS: u8 = 0x1;
+pub const HTTP2_FLAG_HEADER_END_HEADERS: u8 = 0x4;
+const HTTP2_FLAG_HEADER_PADDED: u8 = 0x8;
+const HTTP2_FLAG_HEADER_PRIORITY: u8 = 0x20;
+
+pub fn http2_parse_frame_headers<'a>(
+    input: &'a [u8], flags: u8, dyn_headers: &mut Vec<HTTP2FrameHeaderBlock>,
+) -> IResult<&'a [u8], HTTP2FrameHeaders> {
+    let (i2, padlength) = cond!(input, flags & HTTP2_FLAG_HEADER_PADDED != 0, be_u8)?;
+    let (mut i3, priority) = cond!(
+        i2,
+        flags & HTTP2_FLAG_HEADER_PRIORITY != 0,
+        http2_parse_headers_priority
+    )?;
+    let mut blocks = Vec::new();
+    while i3.len() > 0 {
+        match http2_parse_headers_block(i3, dyn_headers) {
+            Ok((rem, b)) => {
+                blocks.push(b);
+                debug_validate_bug_on!(i3.len() == rem.len());
+                if i3.len() == rem.len() {
+                    //infinite loop
+                    return Err(Err::Error((input, ErrorKind::Eof)));
+                }
+                i3 = rem;
+            }
+            Err(x) => {
+                return Err(x);
+            }
+        }
+    }
+    return Ok((
+        i3,
+        HTTP2FrameHeaders {
+            padlength,
+            priority,
+            blocks,
+        },
+    ));
+}
+
+#[derive(Clone)]
+pub struct HTTP2FramePushPromise {
+    pub padlength: Option<u8>,
+    pub reserved: u8,
+    pub stream_id: u32,
+    pub blocks: Vec<HTTP2FrameHeaderBlock>,
+}
+
+pub fn http2_parse_frame_push_promise<'a>(
+    input: &'a [u8], flags: u8, dyn_headers: &mut Vec<HTTP2FrameHeaderBlock>,
+) -> IResult<&'a [u8], HTTP2FramePushPromise> {
+    let (i2, padlength) = cond!(input, flags & HTTP2_FLAG_HEADER_PADDED != 0, be_u8)?;
+    let (mut i3, stream_id) = bits!(i2, tuple!(take_bits!(1u8), take_bits!(31u32)))?;
+    let mut blocks = Vec::new();
+    while i3.len() > 0 {
+        match http2_parse_headers_block(i3, dyn_headers) {
+            Ok((rem, b)) => {
+                blocks.push(b);
+                debug_validate_bug_on!(i3.len() == rem.len());
+                if i3.len() == rem.len() {
+                    //infinite loop
+                    return Err(Err::Error((input, ErrorKind::Eof)));
+                }
+                i3 = rem;
+            }
+            Err(x) => {
+                return Err(x);
+            }
+        }
+    }
+    return Ok((
+        i3,
+        HTTP2FramePushPromise {
+            padlength,
+            reserved: stream_id.0,
+            stream_id: stream_id.1,
+            blocks,
+        },
+    ));
+}
+
+#[derive(Clone)]
+pub struct HTTP2FrameContinuation {
+    pub blocks: Vec<HTTP2FrameHeaderBlock>,
+}
+
+pub fn http2_parse_frame_continuation<'a>(
+    input: &'a [u8], dyn_headers: &mut Vec<HTTP2FrameHeaderBlock>,
+) -> IResult<&'a [u8], HTTP2FrameContinuation> {
+    let mut i3 = input;
+    let mut blocks = Vec::new();
+    while i3.len() > 0 {
+        match http2_parse_headers_block(i3, dyn_headers) {
+            Ok((rem, b)) => {
+                blocks.push(b);
+                debug_validate_bug_on!(i3.len() == rem.len());
+                if i3.len() == rem.len() {
+                    //infinite loop
+                    return Err(Err::Error((input, ErrorKind::Eof)));
+                }
+                i3 = rem;
+            }
+            Err(x) => {
+                return Err(x);
+            }
+        }
+    }
+    return Ok((i3, HTTP2FrameContinuation { blocks }));
+}
+
+#[repr(u16)]
+#[derive(Clone, Copy, PartialEq, FromPrimitive, Debug)]
+pub enum HTTP2SettingsId {
+    SETTINGSHEADERTABLESIZE = 1,
+    SETTINGSENABLEPUSH = 2,
+    SETTINGSMAXCONCURRENTSTREAMS = 3,
+    SETTINGSINITIALWINDOWSIZE = 4,
+    SETTINGSMAXFRAMESIZE = 5,
+    SETTINGSMAXHEADERLISTSIZE = 6,
+}
+
+impl fmt::Display for HTTP2SettingsId {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
+impl std::str::FromStr for HTTP2SettingsId {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let su = s.to_uppercase();
+        let su_slice: &str = &*su;
+        match su_slice {
+            "SETTINGS_HEADER_TABLE_SIZE" => Ok(HTTP2SettingsId::SETTINGSHEADERTABLESIZE),
+            "SETTINGS_ENABLE_PUSH" => Ok(HTTP2SettingsId::SETTINGSENABLEPUSH),
+            "SETTINGS_MAX_CONCURRENT_STREAMS" => Ok(HTTP2SettingsId::SETTINGSMAXCONCURRENTSTREAMS),
+            "SETTINGS_INITIAL_WINDOW_SIZE" => Ok(HTTP2SettingsId::SETTINGSINITIALWINDOWSIZE),
+            "SETTINGS_MAX_FRAME_SIZE" => Ok(HTTP2SettingsId::SETTINGSMAXFRAMESIZE),
+            "SETTINGS_MAX_HEADER_LIST_SIZE" => Ok(HTTP2SettingsId::SETTINGSMAXHEADERLISTSIZE),
+            _ => Err(format!("'{}' is not a valid value for HTTP2SettingsId", s)),
+        }
+    }
+}
+
+//TODOask move elsewhere generic with DetectU64Data and such
+#[derive(PartialEq, Debug)]
+pub enum DetectUintMode {
+    DetectUintModeEqual,
+    DetectUintModeLt,
+    DetectUintModeGt,
+    DetectUintModeRange,
+}
+
+pub struct DetectU32Data {
+    pub value: u32,
+    pub valrange: u32,
+    pub mode: DetectUintMode,
+}
+
+pub struct DetectHTTP2settingsSigCtx {
+    pub id: HTTP2SettingsId,          //identifier
+    pub value: Option<DetectU32Data>, //optional value
+}
+
+named!(detect_parse_u32_start_equal<&str,DetectU32Data>,
+    do_parse!(
+        opt!( is_a!( " " ) ) >>
+        opt! (tag!("=") ) >>
+        opt!( is_a!( " " ) ) >>
+        value : map_opt!(digit1, |s: &str| s.parse::<u32>().ok()) >>
+        (DetectU32Data{value, valrange:0, mode:DetectUintMode::DetectUintModeEqual})
+    )
+);
+
+named!(detect_parse_u32_start_interval<&str,DetectU32Data>,
+    do_parse!(
+        opt!( is_a!( " " ) ) >>
+        value : map_opt!(digit1, |s: &str| s.parse::<u32>().ok()) >>
+        opt!( is_a!( " " ) ) >>
+        tag!("-") >>
+        opt!( is_a!( " " ) ) >>
+        valrange : map_opt!(digit1, |s: &str| s.parse::<u32>().ok()) >>
+        (DetectU32Data{value, valrange, mode:DetectUintMode::DetectUintModeRange})
+    )
+);
+
+named!(detect_parse_u32_start_lesser<&str,DetectU32Data>,
+    do_parse!(
+        opt!( is_a!( " " ) ) >>
+        tag!("<") >>
+        opt!( is_a!( " " ) ) >>
+        value : map_opt!(digit1, |s: &str| s.parse::<u32>().ok()) >>
+        (DetectU32Data{value, valrange:0, mode:DetectUintMode::DetectUintModeLt})
+    )
+);
+
+named!(detect_parse_u32_start_greater<&str,DetectU32Data>,
+    do_parse!(
+        opt!( is_a!( " " ) ) >>
+        tag!(">") >>
+        opt!( is_a!( " " ) ) >>
+        value : map_opt!(digit1, |s: &str| s.parse::<u32>().ok()) >>
+        (DetectU32Data{value, valrange:0, mode:DetectUintMode::DetectUintModeGt})
+    )
+);
+
+named!(detect_parse_u32<&str,DetectU32Data>,
+    do_parse!(
+        u32 : alt! (
+            detect_parse_u32_start_lesser |
+            detect_parse_u32_start_greater |
+            complete!( detect_parse_u32_start_interval ) |
+            detect_parse_u32_start_equal
+        ) >>
+        (u32)
+    )
+);
+
+named!(pub http2_parse_settingsctx<&str,DetectHTTP2settingsSigCtx>,
+    do_parse!(
+        opt!( is_a!( " " ) ) >>
+        id: map_opt!( alt! ( complete!( is_not!( " <>=" ) ) | rest ),
+            |s: &str| HTTP2SettingsId::from_str(s).ok() ) >>
+        value: opt!( complete!( detect_parse_u32 ) ) >>
+        (DetectHTTP2settingsSigCtx{id, value})
+    )
+);
+
+pub struct DetectU64Data {
+    pub value: u64,
+    pub valrange: u64,
+    pub mode: DetectUintMode,
+}
+
+named!(detect_parse_u64_start_equal<&str,DetectU64Data>,
+    do_parse!(
+        opt!( is_a!( " " ) ) >>
+        opt! (tag!("=") ) >>
+        opt!( is_a!( " " ) ) >>
+        value : map_opt!(digit1, |s: &str| s.parse::<u64>().ok()) >>
+        (DetectU64Data{value, valrange:0, mode:DetectUintMode::DetectUintModeEqual})
+    )
+);
+
+named!(detect_parse_u64_start_interval<&str,DetectU64Data>,
+    do_parse!(
+        opt!( is_a!( " " ) ) >>
+        value : map_opt!(digit1, |s: &str| s.parse::<u64>().ok()) >>
+        opt!( is_a!( " " ) ) >>
+        tag!("-") >>
+        opt!( is_a!( " " ) ) >>
+        valrange : map_opt!(digit1, |s: &str| s.parse::<u64>().ok()) >>
+        (DetectU64Data{value, valrange, mode:DetectUintMode::DetectUintModeRange})
+    )
+);
+
+named!(detect_parse_u64_start_lesser<&str,DetectU64Data>,
+    do_parse!(
+        opt!( is_a!( " " ) ) >>
+        tag!("<") >>
+        opt!( is_a!( " " ) ) >>
+        value : map_opt!(digit1, |s: &str| s.parse::<u64>().ok()) >>
+        (DetectU64Data{value, valrange:0, mode:DetectUintMode::DetectUintModeLt})
+    )
+);
+
+named!(detect_parse_u64_start_greater<&str,DetectU64Data>,
+    do_parse!(
+        opt!( is_a!( " " ) ) >>
+        tag!(">") >>
+        opt!( is_a!( " " ) ) >>
+        value : map_opt!(digit1, |s: &str| s.parse::<u64>().ok()) >>
+        (DetectU64Data{value, valrange:0, mode:DetectUintMode::DetectUintModeGt})
+    )
+);
+
+named!(pub detect_parse_u64<&str,DetectU64Data>,
+    do_parse!(
+        u64 : alt! (
+            detect_parse_u64_start_lesser |
+            detect_parse_u64_start_greater |
+            complete!( detect_parse_u64_start_interval ) |
+            detect_parse_u64_start_equal
+        ) >>
+        (u64)
+    )
+);
+
+#[derive(Clone, Copy)]
+pub struct HTTP2FrameSettings {
+    pub id: HTTP2SettingsId,
+    pub value: u32,
+}
+
+named!(
+    http2_parse_frame_setting<HTTP2FrameSettings>,
+    do_parse!(
+        id: map_opt!(be_u16, num::FromPrimitive::from_u16)
+            >> value: be_u32
+            >> (HTTP2FrameSettings { id, value })
+    )
+);
+
+named!(pub http2_parse_frame_settings<Vec<HTTP2FrameSettings>>,
+    many0!( complete!(http2_parse_frame_setting) )
+);
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+
+    #[test]
+    fn test_http2_parse_header() {
+        let buf0: &[u8] = &[0x82];
+        let mut dynh: Vec<HTTP2FrameHeaderBlock> =
+            Vec::with_capacity(255 - HTTP2_STATIC_HEADERS_NUMBER);
+        let r0 = http2_parse_headers_block(buf0, &mut dynh);
+        match r0 {
+            Ok((remainder, hd)) => {
+                // Check the first message.
+                assert_eq!(hd.name, ":method".as_bytes().to_vec());
+                assert_eq!(hd.value, "GET".as_bytes().to_vec());
+                // And we should have no bytes left.
+                assert_eq!(remainder.len(), 0);
+            }
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+        let buf1: &[u8] = &[0x53, 0x03, 0x2A, 0x2F, 0x2A];
+        let r1 = http2_parse_headers_block(buf1, &mut dynh);
+        match r1 {
+            Ok((remainder, hd)) => {
+                // Check the first message.
+                assert_eq!(hd.name, "accept".as_bytes().to_vec());
+                assert_eq!(hd.value, "*/*".as_bytes().to_vec());
+                // And we should have no bytes left.
+                assert_eq!(remainder.len(), 0);
+                assert_eq!(dynh.len(), 1);
+            }
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+        let buf: &[u8] = &[
+            0x41, 0x8a, 0xa0, 0xe4, 0x1d, 0x13, 0x9d, 0x09, 0xb8, 0xc8, 0x00, 0x0f,
+        ];
+        let result = http2_parse_headers_block(buf, &mut dynh);
+        match result {
+            Ok((remainder, hd)) => {
+                // Check the first message.
+                assert_eq!(hd.name, ":authority".as_bytes().to_vec());
+                assert_eq!(hd.value, "localhost:3000".as_bytes().to_vec());
+                // And we should have no bytes left.
+                assert_eq!(remainder.len(), 0);
+                assert_eq!(dynh.len(), 2);
+            }
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+        let buf3: &[u8] = &[0xbe];
+        let r3 = http2_parse_headers_block(buf3, &mut dynh);
+        match r3 {
+            Ok((remainder, hd)) => {
+                // same as before
+                assert_eq!(hd.name, ":authority".as_bytes().to_vec());
+                assert_eq!(hd.value, "localhost:3000".as_bytes().to_vec());
+                // And we should have no bytes left.
+                assert_eq!(remainder.len(), 0);
+                assert_eq!(dynh.len(), 2);
+            }
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+        let buf2: &[u8] = &[
+            0x04, 0x94, 0x62, 0x43, 0x91, 0x8a, 0x47, 0x55, 0xa3, 0xa1, 0x89, 0xd3, 0x4d, 0x0c,
+            0x1a, 0xa9, 0x0b, 0xe5, 0x79, 0xd3, 0x4d, 0x1f,
+        ];
+        let r2 = http2_parse_headers_block(buf2, &mut dynh);
+        match r2 {
+            Ok((remainder, hd)) => {
+                // Check the first message.
+                assert_eq!(hd.name, ":path".as_bytes().to_vec());
+                assert_eq!(hd.value, "/doc/manual/html/index.html".as_bytes().to_vec());
+                // And we should have no bytes left.
+                assert_eq!(remainder.len(), 0);
+                assert_eq!(dynh.len(), 2);
+            }
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+
+    /// Simple test of some valid data.
+    #[test]
+    fn test_http2_parse_settingsctx() {
+        let s = "SETTINGS_ENABLE_PUSH";
+        let r = http2_parse_settingsctx(s);
+        match r {
+            Ok((rem, ctx)) => {
+                assert_eq!(ctx.id, HTTP2SettingsId::SETTINGSENABLEPUSH);
+                match ctx.value {
+                    Some(_) => {
+                        panic!("Unexpected value");
+                    }
+                    None => {}
+                }
+                assert_eq!(rem.len(), 0);
+            }
+            Err(e) => {
+                panic!("Result should not be an error {:?}.", e);
+            }
+        }
+
+        //spaces in the end
+        let s1 = "SETTINGS_ENABLE_PUSH ";
+        let r1 = http2_parse_settingsctx(s1);
+        match r1 {
+            Ok((rem, ctx)) => {
+                assert_eq!(ctx.id, HTTP2SettingsId::SETTINGSENABLEPUSH);
+                match ctx.value {
+                    Some(_) => {
+                        panic!("Unexpected value");
+                    }
+                    None => {}
+                }
+                assert_eq!(rem.len(), 1);
+            }
+            Err(e) => {
+                panic!("Result should not be an error {:?}.", e);
+            }
+        }
+
+        let s2 = "SETTINGS_MAX_CONCURRENT_STREAMS  42";
+        let r2 = http2_parse_settingsctx(s2);
+        match r2 {
+            Ok((rem, ctx)) => {
+                assert_eq!(ctx.id, HTTP2SettingsId::SETTINGSMAXCONCURRENTSTREAMS);
+                match ctx.value {
+                    Some(ctxval) => {
+                        assert_eq!(ctxval.value, 42);
+                    }
+                    None => {
+                        panic!("No value");
+                    }
+                }
+                assert_eq!(rem.len(), 0);
+            }
+            Err(e) => {
+                panic!("Result should not be an error {:?}.", e);
+            }
+        }
+
+        let s3 = "SETTINGS_MAX_CONCURRENT_STREAMS 42-68";
+        let r3 = http2_parse_settingsctx(s3);
+        match r3 {
+            Ok((rem, ctx)) => {
+                assert_eq!(ctx.id, HTTP2SettingsId::SETTINGSMAXCONCURRENTSTREAMS);
+                match ctx.value {
+                    Some(ctxval) => {
+                        assert_eq!(ctxval.value, 42);
+                        assert_eq!(ctxval.mode, DetectUintMode::DetectUintModeRange);
+                        assert_eq!(ctxval.valrange, 68);
+                    }
+                    None => {
+                        panic!("No value");
+                    }
+                }
+                assert_eq!(rem.len(), 0);
+            }
+            Err(e) => {
+                panic!("Result should not be an error {:?}.", e);
+            }
+        }
+
+        let s4 = "SETTINGS_MAX_CONCURRENT_STREAMS<54";
+        let r4 = http2_parse_settingsctx(s4);
+        match r4 {
+            Ok((rem, ctx)) => {
+                assert_eq!(ctx.id, HTTP2SettingsId::SETTINGSMAXCONCURRENTSTREAMS);
+                match ctx.value {
+                    Some(ctxval) => {
+                        assert_eq!(ctxval.value, 54);
+                        assert_eq!(ctxval.mode, DetectUintMode::DetectUintModeLt);
+                    }
+                    None => {
+                        panic!("No value");
+                    }
+                }
+                assert_eq!(rem.len(), 0);
+            }
+            Err(e) => {
+                panic!("Result should not be an error {:?}.", e);
+            }
+        }
+
+        let s5 = "SETTINGS_MAX_CONCURRENT_STREAMS > 76";
+        let r5 = http2_parse_settingsctx(s5);
+        match r5 {
+            Ok((rem, ctx)) => {
+                assert_eq!(ctx.id, HTTP2SettingsId::SETTINGSMAXCONCURRENTSTREAMS);
+                match ctx.value {
+                    Some(ctxval) => {
+                        assert_eq!(ctxval.value, 76);
+                        assert_eq!(ctxval.mode, DetectUintMode::DetectUintModeGt);
+                    }
+                    None => {
+                        panic!("No value");
+                    }
+                }
+                assert_eq!(rem.len(), 0);
+            }
+            Err(e) => {
+                panic!("Result should not be an error {:?}.", e);
+            }
+        }
+    }
+
+    #[test]
+    fn test_http2_parse_headers_block_string() {
+        let buf: &[u8] = &[0x01, 0xFF];
+        let r = http2_parse_headers_block_string(buf);
+        match r {
+            Ok((remainder, _)) => {
+                assert_eq!(remainder.len(), 0);
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+            _ => {
+                panic!("Result should have been ok");
+            }
+        }
+        let buf2: &[u8] = &[0x83, 0xFF, 0xFF, 0xEA];
+        let r2 = http2_parse_headers_block_string(buf2);
+        match r2 {
+            Ok((remainder, _)) => {
+                assert_eq!(remainder.len(), 0);
+            }
+            _ => {
+                panic!("Result should have been ok");
+            }
+        }
+    }
+
+    #[test]
+    fn test_http2_parse_frame_header() {
+        let buf: &[u8] = &[
+            0x00, 0x00, 0x06, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+            0x64,
+        ];
+        let result = http2_parse_frame_header(buf);
+        match result {
+            Ok((remainder, frame)) => {
+                // Check the first message.
+                assert_eq!(frame.length, 6);
+                assert_eq!(frame.ftype, HTTP2FrameType::SETTINGS as u8);
+                assert_eq!(frame.flags, 0);
+                assert_eq!(frame.reserved, 0);
+                assert_eq!(frame.stream_id, 0);
+
+                // And we should have 6 bytes left.
+                assert_eq!(remainder.len(), 6);
+            }
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+
+}
index 9a48a9c75fac5117c03112d6b08ecd101c236eee..f0af87bcff3bdb42d558ecae15f1eb3ed8566cbc 100644 (file)
@@ -81,3 +81,4 @@ pub mod rdp;
 pub mod x509;
 pub mod asn1;
 pub mod ssh;
+pub mod http2;
index ad3e6907d36614e8d50142d381f346f11c003730..70e11faf25bc9e43153ec68226052e7ccee53480 100755 (executable)
@@ -35,6 +35,7 @@ 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-htp-xff.c app-layer-htp-xff.h \
+app-layer-http2.c app-layer-http2.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 \
@@ -208,6 +209,7 @@ detect-http-stat-code.c detect-http-stat-code.h \
 detect-http-stat-msg.c detect-http-stat-msg.h \
 detect-http-ua.c detect-http-ua.h \
 detect-http-uri.c detect-http-uri.h \
+detect-http2.c detect-http2.h \
 detect-icmp-id.c detect-icmp-id.h \
 detect-icmp-seq.c detect-icmp-seq.h \
 detect-icmpv6hdr.c detect-icmpv6hdr.h \
@@ -372,6 +374,7 @@ output-json-flow.c output-json-flow.h \
 output-json-ftp.c output-json-ftp.h \
 output-json-netflow.c output-json-netflow.h \
 output-json-http.c output-json-http.h \
+output-json-http2.c output-json-http2.h \
 output-json-sip.c output-json-sip.h \
 output-json-smtp.c output-json-smtp.h \
 output-json-ssh.c output-json-ssh.h \
index 33c853c6bd1e3e25c572c926d26659e030d75ec7..48ae6e58b1085bd704adfdf2baad6d6eb5c3f6e1 100644 (file)
@@ -687,6 +687,26 @@ static void PacketToDataProtoHTTP(const Packet *p, const PacketAlert *pa, idmef_
 
 }
 
+/**
+ * \brief Handle ALPROTO_HTTP2 JSON information
+ * \param p Packet where to extract data
+ * \param pa Packet alert information
+ * \param alert IDMEF alert
+ * \return void
+ */
+static void PacketToDataProtoHTTP2(const Packet *p, const PacketAlert *pa, idmef_alert_t *alert)
+{
+    void *http2_state = FlowGetAppState(f);
+    if (http2_state) {
+        void *tx_ptr = rs_http2_state_get_tx(http2_state, pa->tx_id);
+        json_t *js = rs_http2_log_json(tx_ptr);
+        if (unlikely(js == NULL))
+            return;
+        JsonToAdditionalData(NULL, js, alert);
+        json_decref(js);
+    }
+}
+
 /**
  * \brief Handle ALPROTO_TLS JSON information
  * \param p Packet where to extract data
@@ -811,6 +831,9 @@ static int PacketToData(const Packet *p, const PacketAlert *pa, idmef_alert_t *a
             case ALPROTO_HTTP:
                 PacketToDataProtoHTTP(p, pa, alert);
                 break;
+            case ALPROTO_HTTP2:
+                PacketToDataProtoHTTP(p, pa, alert);
+                break;
             case ALPROTO_TLS:
                 PacketToDataProtoTLS(p, pa, alert);
                 break;
diff --git a/src/app-layer-http2.c b/src/app-layer-http2.c
new file mode 100644 (file)
index 0000000..c937c5a
--- /dev/null
@@ -0,0 +1,71 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Philippe Antoine <p.antoine@catenacyber.fr>
+ *
+ * Parser for HTTP2, RFC 7540
+ */
+
+#include "suricata-common.h"
+#include "stream.h"
+#include "conf.h"
+
+#include "util-unittest.h"
+
+#include "app-layer-detect-proto.h"
+#include "app-layer-parser.h"
+
+#include "app-layer-http2.h"
+#include "rust.h"
+
+static int HTTP2RegisterPatternsForProtocolDetection(void)
+{
+    /* Using the 24 bytes pattern makes AppLayerTest09 fail/leak
+     * The complete pattern is "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
+     */
+    if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_HTTP2,
+                                               "PRI * HTTP/2.0\r\n",
+                                               16, 0, STREAM_TOSERVER) < 0)
+    {
+        return -1;
+    }
+    return 0;
+}
+
+static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER;
+static SuricataFileContext sfc = { &sbcfg };
+
+void RegisterHTTP2Parsers(void)
+{
+    const char *proto_name = "http2";
+
+    if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
+        AppLayerProtoDetectRegisterProtocol(ALPROTO_HTTP2, proto_name);
+        if (HTTP2RegisterPatternsForProtocolDetection() < 0)
+            return;
+
+        rs_http2_init(&sfc);
+        rs_http2_register_parser();
+    }
+
+#ifdef UNITTESTS
+    //TODOask HTTP2ParserRegisterTests();
+#endif
+}
diff --git a/src/app-layer-http2.h b/src/app-layer-http2.h
new file mode 100644 (file)
index 0000000..043cf7b
--- /dev/null
@@ -0,0 +1,29 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Philippe Antoine <p.antoine@catenacyber.fr>
+ */
+
+#ifndef __APP_LAYER_HTTP2_H__
+#define __APP_LAYER_HTTP2_H__
+
+void RegisterHTTP2Parsers(void);
+
+#endif /* __APP_LAYER_HTTP2_H__ */
index bef28bdf3f07ea4e951995832d7c05c3bacf49a7..a7c28c1e6aa961af6132a6235dc1bc91a8182532 100644 (file)
@@ -70,6 +70,7 @@
 #include "app-layer-template.h"
 #include "app-layer-template-rust.h"
 #include "app-layer-rdp.h"
+#include "app-layer-http2.h"
 
 #include "conf.h"
 #include "util-spm.h"
@@ -1580,6 +1581,7 @@ void AppLayerParserRegisterProtocolParsers(void)
     RegisterMQTTParsers();
     RegisterTemplateParsers();
     RegisterRdpParsers();
+    RegisterHTTP2Parsers();
 
     /** IMAP */
     AppLayerProtoDetectRegisterProtocol(ALPROTO_IMAP, "imap");
index 0260d8347d051c76d3a22431d0daadaf4027295b..d6b2618e8ff3ed2007c25a503eaae8d211c4b1c3 100644 (file)
@@ -117,6 +117,9 @@ const char *AppProtoToString(AppProto alproto)
         case ALPROTO_RDP:
             proto_name = "rdp";
             break;
+        case ALPROTO_HTTP2:
+            proto_name = "http2";
+            break;
         case ALPROTO_FAILED:
             proto_name = "failed";
             break;
@@ -161,6 +164,7 @@ AppProto StringToAppProto(const char *proto_name)
     if (strcmp(proto_name,"template")==0) return ALPROTO_TEMPLATE;
     if (strcmp(proto_name,"template-rust")==0) return ALPROTO_TEMPLATE_RUST;
     if (strcmp(proto_name,"rdp")==0) return ALPROTO_RDP;
+    if (strcmp(proto_name,"http2")==0) return ALPROTO_HTTP2;
     if (strcmp(proto_name,"failed")==0) return ALPROTO_FAILED;
 
     return ALPROTO_UNKNOWN;
index 0b421c864329a07089cf8215c0afbaae745bd3c2..278c3e4389b050a00b2aa248d1533c73b5ad09f8 100644 (file)
@@ -56,6 +56,7 @@ enum AppProtoEnum {
     ALPROTO_TEMPLATE,
     ALPROTO_TEMPLATE_RUST,
     ALPROTO_RDP,
+    ALPROTO_HTTP2,
 
     /* used by the probing parser when alproto detection fails
      * permanently for that particular stream */
index 2ccc2b7d8fdaf692750b8db661c168e16b7d9c53..00b2575c259eaf2507871d140ea2192e59087a93 100644 (file)
 #include "detect-http-stat-msg.h"
 #include "detect-http-request-line.h"
 #include "detect-http-response-line.h"
+#include "detect-http2.h"
 #include "detect-byte-extract.h"
 #include "detect-file-data.h"
 #include "detect-pkt-data.h"
@@ -473,6 +474,7 @@ void SigTableSetup(void)
 
     DetectHttpStatMsgRegister();
     DetectHttpStatCodeRegister();
+    DetectHttp2Register();
 
     DetectDnsQueryRegister();
     DetectDnsOpcodeRegister();
index 768c9fffc4db5e2b22710000d85eae4de2ec0e29..52864ddacdf711ab79fb12ef6b3c8a263dfeab97 100644 (file)
@@ -172,6 +172,15 @@ enum DetectKeywordId {
     DETECT_PKT_DATA,
     DETECT_AL_APP_LAYER_EVENT,
 
+    DETECT_HTTP2_FRAMETYPE,
+    DETECT_HTTP2_ERRORCODE,
+    DETECT_HTTP2_PRIORITY,
+    DETECT_HTTP2_WINDOW,
+    DETECT_HTTP2_SIZEUPDATE,
+    DETECT_HTTP2_SETTINGS,
+    DETECT_HTTP2_HEADERNAME,
+    DETECT_HTTP2_HEADER,
+
     DETECT_DCE_IFACE,
     DETECT_DCE_OPNUM,
     DETECT_DCE_STUB_DATA,
index fab29513f37e783819f9c00e831365e8cfdf0148..566291cb815e4739e4e34bb25b7aabf070927639 100644 (file)
@@ -103,6 +103,12 @@ void DetectFiledataRegister(void)
     DetectAppLayerMpmRegister2("file_data", SIG_FLAG_TOCLIENT, 2,
             PrefilterMpmFiledataRegister, NULL,
             ALPROTO_SMB, 0);
+    DetectAppLayerMpmRegister2("file_data", SIG_FLAG_TOSERVER, 2,
+            PrefilterMpmFiledataRegister, NULL,
+            ALPROTO_HTTP2, HTTP2StateDataClient);
+    DetectAppLayerMpmRegister2("file_data", SIG_FLAG_TOCLIENT, 2,
+            PrefilterMpmFiledataRegister, NULL,
+            ALPROTO_HTTP2, HTTP2StateDataServer);
 
     DetectAppLayerInspectEngineRegister2("file_data",
             ALPROTO_HTTP, SIG_FLAG_TOCLIENT, HTP_RESPONSE_BODY,
@@ -118,6 +124,12 @@ void DetectFiledataRegister(void)
     DetectAppLayerInspectEngineRegister2("file_data",
             ALPROTO_SMB, SIG_FLAG_TOCLIENT, 0,
             DetectEngineInspectFiledata, NULL);
+    DetectAppLayerInspectEngineRegister2("file_data",
+            ALPROTO_HTTP2, SIG_FLAG_TOSERVER, HTTP2StateDataClient,
+            DetectEngineInspectFiledata, NULL);
+    DetectAppLayerInspectEngineRegister2("file_data",
+            ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, HTTP2StateDataServer,
+            DetectEngineInspectFiledata, NULL);
 
     DetectBufferTypeSetDescriptionByName("file_data",
             "http response body, smb files or smtp attachments data");
@@ -167,7 +179,8 @@ static int DetectFiledataSetup (DetectEngineCtx *de_ctx, Signature *s, const cha
 
     if (!DetectProtoContainsProto(&s->proto, IPPROTO_TCP) ||
         (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_HTTP &&
-        s->alproto != ALPROTO_SMTP && s->alproto != ALPROTO_SMB)) {
+        s->alproto != ALPROTO_SMTP && s->alproto != ALPROTO_SMB &&
+        s->alproto != ALPROTO_HTTP2)) {
         SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords.");
         return -1;
     }
index 6d53c4705a58d0d88ce738737e87883f04fcf1de..3fc2d2858e56e2c8b7de36cfb7c6e1644fca14c6 100644 (file)
@@ -127,9 +127,9 @@ void DetectFilemagicRegister(void)
     g_file_match_list_id = DetectBufferTypeRegister("files");
 
     AppProto protos_ts[] = {
-        ALPROTO_HTTP, ALPROTO_SMTP, ALPROTO_FTP, ALPROTO_SMB, ALPROTO_NFS, 0 };
+        ALPROTO_HTTP, ALPROTO_SMTP, ALPROTO_FTP, ALPROTO_SMB, ALPROTO_NFS, ALPROTO_HTTP2, 0 };
     AppProto protos_tc[] = {
-        ALPROTO_HTTP, ALPROTO_FTP, ALPROTO_SMB, ALPROTO_NFS, 0 };
+        ALPROTO_HTTP, ALPROTO_FTP, ALPROTO_SMB, ALPROTO_NFS, ALPROTO_HTTP2, 0 };
 
     for (int i = 0; protos_ts[i] != 0; i++) {
         DetectAppLayerInspectEngineRegister2("file.magic", protos_ts[i],
diff --git a/src/detect-http2.c b/src/detect-http2.c
new file mode 100644 (file)
index 0000000..c73b42e
--- /dev/null
@@ -0,0 +1,984 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Philippe Antoine <p.antoine@catenacyber.fr>
+ *
+ */
+
+#include "suricata-common.h"
+
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-uint.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-prefilter.h"
+#include "detect-engine-content-inspection.h"
+
+#include "detect-http2.h"
+#include "util-byte.h"
+#include "rust.h"
+
+#ifdef UNITTESTS
+void DetectHTTP2frameTypeRegisterTests (void);
+void DetectHTTP2errorCodeRegisterTests (void);
+void DetectHTTP2priorityRegisterTests (void);
+void DetectHTTP2windowRegisterTests (void);
+void DetectHTTP2settingsRegisterTests (void);
+void DetectHTTP2sizeUpdateRegisterTests (void);
+#endif
+
+/* prototypes */
+static int DetectHTTP2frametypeMatch(DetectEngineThreadCtx *det_ctx,
+                                     Flow *f, uint8_t flags, void *state, void *txv, const Signature *s,
+                                     const SigMatchCtx *ctx);
+static int DetectHTTP2frametypeSetup (DetectEngineCtx *, Signature *, const char *);
+void DetectHTTP2frametypeFree (DetectEngineCtx *, void *);
+
+static int DetectHTTP2errorcodeMatch(DetectEngineThreadCtx *det_ctx,
+                                     Flow *f, uint8_t flags, void *state, void *txv, const Signature *s,
+                                     const SigMatchCtx *ctx);
+static int DetectHTTP2errorcodeSetup (DetectEngineCtx *, Signature *, const char *);
+void DetectHTTP2errorcodeFree (DetectEngineCtx *, void *);
+
+static int DetectHTTP2priorityMatch(DetectEngineThreadCtx *det_ctx,
+                                     Flow *f, uint8_t flags, void *state, void *txv, const Signature *s,
+                                     const SigMatchCtx *ctx);
+static int DetectHTTP2prioritySetup (DetectEngineCtx *, Signature *, const char *);
+void DetectHTTP2priorityFree (DetectEngineCtx *, void *);
+
+static int DetectHTTP2windowMatch(DetectEngineThreadCtx *det_ctx,
+                                     Flow *f, uint8_t flags, void *state, void *txv, const Signature *s,
+                                     const SigMatchCtx *ctx);
+static int DetectHTTP2windowSetup (DetectEngineCtx *, Signature *, const char *);
+void DetectHTTP2windowFree (DetectEngineCtx *, void *);
+
+static int DetectHTTP2sizeUpdateMatch(DetectEngineThreadCtx *det_ctx,
+                                     Flow *f, uint8_t flags, void *state, void *txv, const Signature *s,
+                                     const SigMatchCtx *ctx);
+static int DetectHTTP2sizeUpdateSetup (DetectEngineCtx *, Signature *, const char *);
+void DetectHTTP2sizeUpdateFree (DetectEngineCtx *, void *);
+
+static int DetectHTTP2settingsMatch(DetectEngineThreadCtx *det_ctx,
+                                     Flow *f, uint8_t flags, void *state, void *txv, const Signature *s,
+                                     const SigMatchCtx *ctx);
+static int DetectHTTP2settingsSetup (DetectEngineCtx *, Signature *, const char *);
+void DetectHTTP2settingsFree (DetectEngineCtx *, void *);
+
+static int DetectHTTP2headerNameSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg);
+static int PrefilterMpmHttp2HeaderNameRegister(DetectEngineCtx *de_ctx,
+                                               SigGroupHead *sgh, MpmCtx *mpm_ctx,
+                                               const DetectBufferMpmRegistery *mpm_reg, int list_id);
+static int DetectEngineInspectHttp2HeaderName(
+                                              DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+                                              const DetectEngineAppInspectionEngine *engine,
+                                              const Signature *s,
+                                              Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id);
+
+static int DetectHTTP2headerSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg);
+static int PrefilterMpmHttp2HeaderRegister(DetectEngineCtx *de_ctx,
+                                               SigGroupHead *sgh, MpmCtx *mpm_ctx,
+                                               const DetectBufferMpmRegistery *mpm_reg, int list_id);
+static int DetectEngineInspectHttp2Header(
+                                          DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+                                          const DetectEngineAppInspectionEngine *engine,
+                                          const Signature *s,
+                                          Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id);
+static bool DetectHttp2HeaderValidateCallback(const Signature *s, const char **sigerror);
+
+#ifdef UNITTESTS
+void DetectHTTP2RegisterTests (void);
+#endif
+
+static int g_http2_match_buffer_id = 0;
+static int g_http2_header_name_buffer_id = 0;
+static int g_http2_header_buffer_id = 0;
+
+static int DetectEngineInspectHTTP2(ThreadVars *tv, DetectEngineCtx *de_ctx,
+                                   DetectEngineThreadCtx *det_ctx, const Signature *s, const SigMatchData *smd,
+                                   Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
+{
+    return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd,
+                                          f, flags, alstate, txv, tx_id);
+}
+
+/**
+ * \brief Registration function for HTTP2 keywords
+ */
+
+void DetectHttp2Register(void)
+{
+    sigmatch_table[DETECT_HTTP2_FRAMETYPE].name = "http2.frametype";
+    sigmatch_table[DETECT_HTTP2_FRAMETYPE].desc = "match on HTTP2 frame type field";
+    sigmatch_table[DETECT_HTTP2_FRAMETYPE].url = "/rules/http2-keywords.html#frametype";
+    sigmatch_table[DETECT_HTTP2_FRAMETYPE].Match = NULL;
+    sigmatch_table[DETECT_HTTP2_FRAMETYPE].AppLayerTxMatch = DetectHTTP2frametypeMatch;
+    sigmatch_table[DETECT_HTTP2_FRAMETYPE].Setup = DetectHTTP2frametypeSetup;
+    sigmatch_table[DETECT_HTTP2_FRAMETYPE].Free = DetectHTTP2frametypeFree;
+#ifdef UNITTESTS
+    sigmatch_table[DETECT_HTTP2_FRAMETYPE].RegisterTests = DetectHTTP2frameTypeRegisterTests;
+#endif
+
+    sigmatch_table[DETECT_HTTP2_ERRORCODE].name = "http2.errorcode";
+    sigmatch_table[DETECT_HTTP2_ERRORCODE].desc = "match on HTTP2 error code field";
+    sigmatch_table[DETECT_HTTP2_ERRORCODE].url = "/rules/http2-keywords.html#errorcode";
+    sigmatch_table[DETECT_HTTP2_ERRORCODE].Match = NULL;
+    sigmatch_table[DETECT_HTTP2_ERRORCODE].AppLayerTxMatch = DetectHTTP2errorcodeMatch;
+    sigmatch_table[DETECT_HTTP2_ERRORCODE].Setup = DetectHTTP2errorcodeSetup;
+    sigmatch_table[DETECT_HTTP2_ERRORCODE].Free = DetectHTTP2errorcodeFree;
+#ifdef UNITTESTS
+    sigmatch_table[DETECT_HTTP2_ERRORCODE].RegisterTests = DetectHTTP2errorCodeRegisterTests;
+#endif
+
+    sigmatch_table[DETECT_HTTP2_PRIORITY].name = "http2.priority";
+    sigmatch_table[DETECT_HTTP2_PRIORITY].desc = "match on HTTP2 priority weight field";
+    sigmatch_table[DETECT_HTTP2_PRIORITY].url = "/rules/http2-keywords.html#priority";
+    sigmatch_table[DETECT_HTTP2_PRIORITY].Match = NULL;
+    sigmatch_table[DETECT_HTTP2_PRIORITY].AppLayerTxMatch = DetectHTTP2priorityMatch;
+    sigmatch_table[DETECT_HTTP2_PRIORITY].Setup = DetectHTTP2prioritySetup;
+    sigmatch_table[DETECT_HTTP2_PRIORITY].Free = DetectHTTP2priorityFree;
+#ifdef UNITTESTS
+    sigmatch_table[DETECT_HTTP2_PRIORITY].RegisterTests = DetectHTTP2priorityRegisterTests;
+#endif
+
+    sigmatch_table[DETECT_HTTP2_WINDOW].name = "http2.window";
+    sigmatch_table[DETECT_HTTP2_WINDOW].desc = "match on HTTP2 window update size increment field";
+    sigmatch_table[DETECT_HTTP2_WINDOW].url = "/rules/http2-keywords.html#window";
+    sigmatch_table[DETECT_HTTP2_WINDOW].Match = NULL;
+    sigmatch_table[DETECT_HTTP2_WINDOW].AppLayerTxMatch = DetectHTTP2windowMatch;
+    sigmatch_table[DETECT_HTTP2_WINDOW].Setup = DetectHTTP2windowSetup;
+    sigmatch_table[DETECT_HTTP2_WINDOW].Free = DetectHTTP2windowFree;
+#ifdef UNITTESTS
+    sigmatch_table[DETECT_HTTP2_WINDOW].RegisterTests = DetectHTTP2windowRegisterTests;
+#endif
+
+    sigmatch_table[DETECT_HTTP2_SIZEUPDATE].name = "http2.size_update";
+    sigmatch_table[DETECT_HTTP2_SIZEUPDATE].desc = "match on HTTP2 dynamic headers table size update";
+    sigmatch_table[DETECT_HTTP2_SIZEUPDATE].url = "/rules/http2-keywords.html#sizeupdate";
+    sigmatch_table[DETECT_HTTP2_SIZEUPDATE].Match = NULL;
+    sigmatch_table[DETECT_HTTP2_SIZEUPDATE].AppLayerTxMatch = DetectHTTP2sizeUpdateMatch;
+    sigmatch_table[DETECT_HTTP2_SIZEUPDATE].Setup = DetectHTTP2sizeUpdateSetup;
+    sigmatch_table[DETECT_HTTP2_SIZEUPDATE].Free = DetectHTTP2sizeUpdateFree;
+#ifdef UNITTESTS
+    sigmatch_table[DETECT_HTTP2_SIZEUPDATE].RegisterTests = DetectHTTP2sizeUpdateRegisterTests;
+#endif
+
+    sigmatch_table[DETECT_HTTP2_SETTINGS].name = "http2.settings";
+    sigmatch_table[DETECT_HTTP2_SETTINGS].desc = "match on HTTP2 settings identifier and value fields";
+    sigmatch_table[DETECT_HTTP2_SETTINGS].url = "/rules/http2-keywords.html#settings";
+    sigmatch_table[DETECT_HTTP2_SETTINGS].Match = NULL;
+    sigmatch_table[DETECT_HTTP2_SETTINGS].AppLayerTxMatch = DetectHTTP2settingsMatch;
+    sigmatch_table[DETECT_HTTP2_SETTINGS].Setup = DetectHTTP2settingsSetup;
+    sigmatch_table[DETECT_HTTP2_SETTINGS].Free = DetectHTTP2settingsFree;
+#ifdef UNITTESTS
+    sigmatch_table[DETECT_HTTP2_SETTINGS].RegisterTests = DetectHTTP2settingsRegisterTests;
+#endif
+
+    sigmatch_table[DETECT_HTTP2_HEADERNAME].name = "http2.header_name";
+    sigmatch_table[DETECT_HTTP2_HEADERNAME].desc = "sticky buffer to match on one HTTP2 header name";
+    sigmatch_table[DETECT_HTTP2_HEADERNAME].url = "/rules/http2-keywords.html#header_name";
+    sigmatch_table[DETECT_HTTP2_HEADERNAME].Setup = DetectHTTP2headerNameSetup;
+    sigmatch_table[DETECT_HTTP2_HEADERNAME].flags |= SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER;
+
+    DetectAppLayerMpmRegister2("http2_header_name", SIG_FLAG_TOCLIENT, 2,
+                               PrefilterMpmHttp2HeaderNameRegister, NULL,
+                               ALPROTO_HTTP2, HTTP2StateOpen);
+    DetectAppLayerInspectEngineRegister2("http2_header_name",
+                                         ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, HTTP2StateOpen,
+                                         DetectEngineInspectHttp2HeaderName, NULL);
+    DetectAppLayerMpmRegister2("http2_header_name", SIG_FLAG_TOSERVER, 2,
+                               PrefilterMpmHttp2HeaderNameRegister, NULL,
+                               ALPROTO_HTTP2, HTTP2StateOpen);
+    DetectAppLayerInspectEngineRegister2("http2_header_name",
+                                         ALPROTO_HTTP2, SIG_FLAG_TOSERVER, HTTP2StateOpen,
+                                         DetectEngineInspectHttp2HeaderName, NULL);
+
+    DetectBufferTypeSetDescriptionByName("http2_header_name",
+                                         "HTTP2 header name");
+    g_http2_header_name_buffer_id = DetectBufferTypeGetByName("http2_header_name");
+
+    sigmatch_table[DETECT_HTTP2_HEADER].name = "http2.header";
+    sigmatch_table[DETECT_HTTP2_HEADER].desc = "sticky buffer to match on one HTTP2 header name and value";
+    sigmatch_table[DETECT_HTTP2_HEADER].url = "/rules/http2-keywords.html#header";
+    sigmatch_table[DETECT_HTTP2_HEADER].Setup = DetectHTTP2headerSetup;
+    sigmatch_table[DETECT_HTTP2_HEADER].flags |= SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER;
+
+    DetectAppLayerMpmRegister2("http2_header", SIG_FLAG_TOCLIENT, 2,
+                               PrefilterMpmHttp2HeaderRegister, NULL,
+                               ALPROTO_HTTP2, HTTP2StateOpen);
+    DetectAppLayerInspectEngineRegister2("http2_header",
+                                         ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, HTTP2StateOpen,
+                                         DetectEngineInspectHttp2Header, NULL);
+    DetectAppLayerMpmRegister2("http2_header", SIG_FLAG_TOSERVER, 2,
+                               PrefilterMpmHttp2HeaderRegister, NULL,
+                               ALPROTO_HTTP2, HTTP2StateOpen);
+    DetectAppLayerInspectEngineRegister2("http2_header",
+                                         ALPROTO_HTTP2, SIG_FLAG_TOSERVER, HTTP2StateOpen,
+                                         DetectEngineInspectHttp2Header, NULL);
+
+    DetectBufferTypeSetDescriptionByName("http2_header",
+                                         "HTTP2 header name and value");
+    DetectBufferTypeRegisterValidateCallback("http2_header", DetectHttp2HeaderValidateCallback);
+    g_http2_header_buffer_id = DetectBufferTypeGetByName("http2_header");
+
+    DetectAppLayerInspectEngineRegister("http2",
+                                        ALPROTO_HTTP2, SIG_FLAG_TOSERVER, 0,
+                                        DetectEngineInspectHTTP2);
+    DetectAppLayerInspectEngineRegister("http2",
+                                        ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, 0,
+                                        DetectEngineInspectHTTP2);
+
+    g_http2_match_buffer_id = DetectBufferTypeRegister("http2");
+    DetectUintRegister();
+
+    return;
+}
+
+/**
+ * \brief This function is used to match HTTP2 frame type rule option on a transaction with those passed via http2.frametype:
+ *
+ * \retval 0 no match
+ * \retval 1 match
+ */
+static int DetectHTTP2frametypeMatch(DetectEngineThreadCtx *det_ctx,
+                               Flow *f, uint8_t flags, void *state, void *txv, const Signature *s,
+                               const SigMatchCtx *ctx)
+
+{
+    uint8_t *detect = (uint8_t *)ctx;
+
+    return rs_http2_tx_has_frametype(txv, flags, *detect);
+}
+
+static int DetectHTTP2FuncParseFrameType(const char *str, uint8_t *ft)
+{
+    // first parse numeric value
+    if (ByteExtractStringUint8(ft, 10, strlen(str), str) >= 0) {
+        return 1;
+    }
+
+    // it it failed so far, parse string value from enumeration
+    int r = rs_http2_parse_frametype(str);
+    if (r >= 0) {
+        *ft = r;
+        return 1;
+    }
+
+    return 0;
+}
+
+/**
+ * \brief this function is used to attach the parsed http2.frametype data into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param str pointer to the user provided http2.frametype options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectHTTP2frametypeSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str)
+{
+    uint8_t frame_type;
+
+    if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0)
+        return -1;
+
+    if (!DetectHTTP2FuncParseFrameType(str, &frame_type)) {
+        SCLogError(SC_ERR_INVALID_SIGNATURE,
+                   "Invalid argument \"%s\" supplied to http2.frametype keyword.", str);
+        return -1;
+    }
+
+    uint8_t *http2ft = SCCalloc(1, sizeof(uint8_t));
+    if (http2ft == NULL)
+        return -1;
+    *http2ft = frame_type;
+
+    SigMatch *sm = SigMatchAlloc();
+    if (sm == NULL) {
+        DetectHTTP2frametypeFree(NULL, http2ft);
+        return -1;
+    }
+
+    sm->type = DETECT_HTTP2_FRAMETYPE;
+    sm->ctx = (SigMatchCtx *)http2ft;
+
+    SigMatchAppendSMToList(s, sm, g_http2_match_buffer_id);
+
+    return 0;
+}
+
+/**
+ * \brief this function will free memory associated with uint8_t
+ *
+ * \param ptr pointer to uint8_t
+ */
+void DetectHTTP2frametypeFree(DetectEngineCtx *de_ctx, void *ptr)
+{
+    SCFree(ptr);
+}
+
+/**
+ * \brief This function is used to match HTTP2 error code rule option on a transaction with those passed via http2.errorcode:
+ *
+ * \retval 0 no match
+ * \retval 1 match
+ */
+static int DetectHTTP2errorcodeMatch(DetectEngineThreadCtx *det_ctx,
+                               Flow *f, uint8_t flags, void *state, void *txv, const Signature *s,
+                               const SigMatchCtx *ctx)
+
+{
+    uint32_t *detect = (uint32_t *)ctx;
+
+    return rs_http2_tx_has_errorcode(txv, flags, *detect);
+    //TODOask handle negation rules
+}
+
+static int DetectHTTP2FuncParseErrorCode(const char *str, uint32_t *ec)
+{
+    // first parse numeric value
+    if (ByteExtractStringUint32(ec, 10, strlen(str), str) >= 0) {
+        return 1;
+    }
+
+    // it it failed so far, parse string value from enumeration
+    int r = rs_http2_parse_errorcode(str);
+    if (r >= 0) {
+        *ec = r;
+        return 1;
+    }
+
+    return 0;
+}
+
+/**
+ * \brief this function is used to attach the parsed http2.errorcode data into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param str pointer to the user provided http2.errorcode options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectHTTP2errorcodeSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str)
+{
+    uint32_t error_code;
+
+    if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0)
+        return -1;
+
+    if (!DetectHTTP2FuncParseErrorCode(str, &error_code)) {
+        SCLogError(SC_ERR_INVALID_SIGNATURE,
+                   "Invalid argument \"%s\" supplied to http2.errorcode keyword.", str);
+        return -1;
+    }
+
+    uint32_t *http2ec = SCCalloc(1, sizeof(uint32_t));
+    if (http2ec == NULL)
+        return -1;
+    *http2ec = error_code;
+
+    SigMatch *sm = SigMatchAlloc();
+    if (sm == NULL) {
+        DetectHTTP2errorcodeFree(NULL, http2ec);
+        return -1;
+    }
+
+    sm->type = DETECT_HTTP2_ERRORCODE;
+    sm->ctx = (SigMatchCtx *)http2ec;
+
+    SigMatchAppendSMToList(s, sm, g_http2_match_buffer_id);
+
+    return 0;
+}
+
+/**
+ * \brief this function will free memory associated with uint32_t
+ *
+ * \param ptr pointer to uint32_t
+ */
+void DetectHTTP2errorcodeFree(DetectEngineCtx *de_ctx, void *ptr)
+{
+    SCFree(ptr);
+}
+
+/**
+ * \brief This function is used to match HTTP2 error code rule option on a transaction with those passed via http2.priority:
+ *
+ * \retval 0 no match
+ * \retval 1 match
+ */
+static int DetectHTTP2priorityMatch(DetectEngineThreadCtx *det_ctx,
+                               Flow *f, uint8_t flags, void *state, void *txv, const Signature *s,
+                               const SigMatchCtx *ctx)
+
+{
+    uint32_t nb = 0;
+    int value = rs_http2_tx_get_next_priority(txv, flags, nb);
+    const DetectU8Data *du8 = (const DetectU8Data *)ctx;
+    while (value >= 0) {
+        if (DetectU8Match(value, du8)) {
+            return 1;
+        }
+        nb++;
+        value = rs_http2_tx_get_next_priority(txv, flags, nb);
+    }
+    return 0;
+}
+
+/**
+ * \brief this function is used to attach the parsed http2.priority data into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param str pointer to the user provided http2.priority options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectHTTP2prioritySetup (DetectEngineCtx *de_ctx, Signature *s, const char *str)
+{
+    if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0)
+        return -1;
+
+    DetectU8Data *prio = DetectU8Parse(str);
+    if (prio == NULL)
+        return -1;
+
+    SigMatch *sm = SigMatchAlloc();
+    if (sm == NULL) {
+        SCFree(prio);
+        return -1;
+    }
+
+    sm->type = DETECT_HTTP2_PRIORITY;
+    sm->ctx = (SigMatchCtx *)prio;
+
+    SigMatchAppendSMToList(s, sm, g_http2_match_buffer_id);
+
+    return 0;
+}
+
+/**
+ * \brief this function will free memory associated with uint32_t
+ *
+ * \param ptr pointer to DetectU8Data
+ */
+void DetectHTTP2priorityFree(DetectEngineCtx *de_ctx, void *ptr)
+{
+    SCFree(ptr);
+}
+
+/**
+ * \brief This function is used to match HTTP2 window rule option on a transaction with those passed via http2.window:
+ *
+ * \retval 0 no match
+ * \retval 1 match
+ */
+static int DetectHTTP2windowMatch(DetectEngineThreadCtx *det_ctx,
+                               Flow *f, uint8_t flags, void *state, void *txv, const Signature *s,
+                               const SigMatchCtx *ctx)
+
+{
+    uint32_t nb = 0;
+    int value = rs_http2_tx_get_next_window(txv, flags, nb);
+    const DetectU32Data *du32 = (const DetectU32Data *)ctx;
+    while (value >= 0) {
+        if (DetectU32Match(value, du32)) {
+            return 1;
+        }
+        nb++;
+        value = rs_http2_tx_get_next_window(txv, flags, nb);
+    }
+    return 0;
+}
+
+/**
+ * \brief this function is used to attach the parsed http2.window data into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param str pointer to the user provided http2.window options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectHTTP2windowSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str)
+{
+    if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0)
+        return -1;
+
+    DetectU32Data *wu = DetectU32Parse(str);
+    if (wu == NULL)
+        return -1;
+
+    SigMatch *sm = SigMatchAlloc();
+    if (sm == NULL) {
+        SCFree(wu);
+        return -1;
+    }
+
+    sm->type = DETECT_HTTP2_WINDOW;
+    sm->ctx = (SigMatchCtx *)wu;
+
+    SigMatchAppendSMToList(s, sm, g_http2_match_buffer_id);
+
+    return 0;
+}
+
+/**
+ * \brief this function will free memory associated with uint32_t
+ *
+ * \param ptr pointer to DetectU8Data
+ */
+void DetectHTTP2windowFree(DetectEngineCtx *de_ctx, void *ptr)
+{
+    SCFree(ptr);
+}
+
+/**
+ * \brief This function is used to match HTTP2 size update rule option on a transaction with those passed via http2.size_update:
+ *
+ * \retval 0 no match
+ * \retval 1 match
+ */
+static int DetectHTTP2sizeUpdateMatch(DetectEngineThreadCtx *det_ctx,
+                               Flow *f, uint8_t flags, void *state, void *txv, const Signature *s,
+                               const SigMatchCtx *ctx)
+
+{
+    return rs_http2_detect_sizeupdatectx_match(ctx, txv, flags);
+}
+
+/**
+ * \brief this function is used to attach the parsed http2.size_update data into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param str pointer to the user provided http2.size_update options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectHTTP2sizeUpdateSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str)
+{
+    if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0)
+        return -1;
+
+    void *su = rs_detect_u64_parse(str);
+    if (su == NULL)
+        return -1;
+
+    SigMatch *sm = SigMatchAlloc();
+    if (sm == NULL) {
+        DetectHTTP2settingsFree(NULL, su);
+        return -1;
+    }
+
+    sm->type = DETECT_HTTP2_SIZEUPDATE;
+    sm->ctx = (SigMatchCtx *)su;
+
+    SigMatchAppendSMToList(s, sm, g_http2_match_buffer_id);
+
+    return 0;
+}
+
+/**
+ * \brief this function will free memory associated with uint32_t
+ *
+ * \param ptr pointer to DetectU8Data
+ */
+void DetectHTTP2sizeUpdateFree(DetectEngineCtx *de_ctx, void *ptr)
+{
+    rs_detect_u64_free(ptr);
+}
+
+/**
+ * \brief This function is used to match HTTP2 error code rule option on a transaction with those passed via http2.settings:
+ *
+ * \retval 0 no match
+ * \retval 1 match
+ */
+static int DetectHTTP2settingsMatch(DetectEngineThreadCtx *det_ctx,
+                               Flow *f, uint8_t flags, void *state, void *txv, const Signature *s,
+                               const SigMatchCtx *ctx)
+
+{
+    return rs_http2_detect_settingsctx_match(ctx, txv, flags);
+}
+
+/**
+ * \brief this function is used to attach the parsed http2.settings data into the current signature
+ *
+ * \param de_ctx pointer to the Detection Engine Context
+ * \param s pointer to the Current Signature
+ * \param str pointer to the user provided http2.settings options
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+static int DetectHTTP2settingsSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str)
+{
+    if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0)
+        return -1;
+
+    void *http2set = rs_http2_detect_settingsctx_parse(str);
+    if (http2set == NULL)
+        return -1;
+
+    SigMatch *sm = SigMatchAlloc();
+    if (sm == NULL) {
+        DetectHTTP2settingsFree(NULL, http2set);
+        return -1;
+    }
+
+    sm->type = DETECT_HTTP2_SETTINGS;
+    sm->ctx = (SigMatchCtx *)http2set;
+
+    SigMatchAppendSMToList(s, sm, g_http2_match_buffer_id);
+
+    return 0;
+}
+
+/**
+ * \brief this function will free memory associated with rust signature context
+ *
+ * \param ptr pointer to rust signature context
+ */
+void DetectHTTP2settingsFree(DetectEngineCtx *de_ctx, void *ptr)
+{
+    rs_http2_detect_settingsctx_free(ptr);
+}
+
+static int DetectHTTP2headerNameSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+    if (DetectBufferSetActiveList(s, g_http2_header_name_buffer_id) < 0)
+        return -1;
+
+    if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0)
+        return -1;
+
+    return 0;
+}
+
+static void PrefilterMpmHttp2HNameFree(void *ptr)
+{
+    SCFree(ptr);
+}
+
+static InspectionBuffer *GetHttp2HNameData(DetectEngineThreadCtx *det_ctx,
+        const uint8_t flags, const DetectEngineTransforms *transforms,
+        Flow *_f, const struct MpmListIdDataArgs *cbdata,
+        int list_id, bool first)
+{
+    SCEnter();
+
+    InspectionBufferMultipleForList *fb = InspectionBufferGetMulti(det_ctx, list_id);
+    InspectionBuffer *buffer = InspectionBufferMultipleForListGet(fb, cbdata->local_id);
+    if (buffer == NULL)
+        return NULL;
+    if (!first && buffer->inspect != NULL)
+        return buffer;
+
+    uint32_t b_len = 0;
+    const uint8_t *b = NULL;
+
+    if (rs_http2_tx_get_header_name(cbdata->txv, flags, (uint32_t)cbdata->local_id, &b, &b_len) != 1)
+        return NULL;
+    if (b == NULL || b_len == 0)
+        return NULL;
+
+    InspectionBufferSetup(buffer, b, b_len);
+    InspectionBufferApplyTransforms(buffer, transforms);
+
+    SCReturnPtr(buffer, "InspectionBuffer");
+}
+
+static void PrefilterTxHttp2HName(DetectEngineThreadCtx *det_ctx,
+        const void *pectx,
+        Packet *p, Flow *f, void *txv,
+        const uint64_t idx, const uint8_t flags)
+{
+    SCEnter();
+
+    const PrefilterMpmListId *ctx = (const PrefilterMpmListId *)pectx;
+    const MpmCtx *mpm_ctx = ctx->mpm_ctx;
+    const int list_id = ctx->list_id;
+
+    int local_id = 0;
+
+    while(1) {
+        // loop until we get a NULL
+
+        struct MpmListIdDataArgs cbdata = { local_id, txv };
+        InspectionBuffer *buffer = GetHttp2HNameData(det_ctx, flags, ctx->transforms,
+                f, &cbdata, list_id, true);
+        if (buffer == NULL)
+            break;
+
+        if (buffer->inspect_len >= mpm_ctx->minlen) {
+            (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx,
+                    &det_ctx->mtcu, &det_ctx->pmq,
+                    buffer->inspect, buffer->inspect_len);
+        }
+
+        local_id++;
+    }
+}
+
+static int PrefilterMpmHttp2HeaderNameRegister(DetectEngineCtx *de_ctx,
+        SigGroupHead *sgh, MpmCtx *mpm_ctx,
+        const DetectBufferMpmRegistery *mpm_reg, int list_id)
+{
+    //TODOask use PrefilterMpmListId elsewhere
+    PrefilterMpmListId *pectx = SCCalloc(1, sizeof(*pectx));
+    if (pectx == NULL)
+        return -1;
+    pectx->list_id = list_id;
+    pectx->mpm_ctx = mpm_ctx;
+    pectx->transforms = &mpm_reg->transforms;
+
+    return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxHttp2HName,
+            mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress,
+            pectx, PrefilterMpmHttp2HNameFree, mpm_reg->name);
+}
+
+static int DetectEngineInspectHttp2HeaderName(
+        DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+        const DetectEngineAppInspectionEngine *engine,
+        const Signature *s,
+        Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
+{
+    int local_id = 0;
+
+    const DetectEngineTransforms *transforms = NULL;
+    if (!engine->mpm) {
+        transforms = engine->v2.transforms;
+    }
+
+    while (1) {
+        //TODOask use MpmListIdDataArgs elsewhere
+        struct MpmListIdDataArgs cbdata = { local_id, txv, };
+        InspectionBuffer *buffer = GetHttp2HNameData(det_ctx, flags,
+                transforms, f, &cbdata, engine->sm_list, false);
+
+        if (buffer == NULL || buffer->inspect == NULL)
+            break;
+
+        det_ctx->buffer_offset = 0;
+        det_ctx->discontinue_matching = 0;
+        det_ctx->inspection_recursion_counter = 0;
+
+        const int match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd,
+                                              NULL, f,
+                                              (uint8_t *)buffer->inspect,
+                                              buffer->inspect_len,
+                                              buffer->inspect_offset, DETECT_CI_FLAGS_SINGLE,
+                                              DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE);
+        if (match == 1) {
+            return DETECT_ENGINE_INSPECT_SIG_MATCH;
+        }
+        local_id++;
+    }
+
+    return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
+}
+
+static int DetectHTTP2headerSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+    if (DetectBufferSetActiveList(s, g_http2_header_buffer_id) < 0)
+        return -1;
+
+    if (DetectSignatureSetAppProto(s, ALPROTO_HTTP2) != 0)
+        return -1;
+
+    return 0;
+}
+
+static void PrefilterMpmHttp2HeaderFree(void *ptr)
+{
+    SCFree(ptr);
+}
+
+static InspectionBuffer *GetHttp2HeaderData(DetectEngineThreadCtx *det_ctx,
+        const uint8_t flags, const DetectEngineTransforms *transforms,
+        Flow *_f, const struct MpmListIdDataArgs *cbdata,
+        int list_id, bool first)
+{
+    SCEnter();
+
+    InspectionBufferMultipleForList *fb = InspectionBufferGetMulti(det_ctx, list_id);
+    InspectionBuffer *buffer = InspectionBufferMultipleForListGet(fb, cbdata->local_id);
+    if (buffer == NULL)
+        return NULL;
+    if (!first && buffer->inspect != NULL)
+        return buffer;
+
+    uint32_t b_len = 0;
+    const uint8_t *b = NULL;
+
+    if (rs_http2_tx_get_header(cbdata->txv, flags, (uint32_t)cbdata->local_id, &b, &b_len) != 1)
+        return NULL;
+    if (b == NULL || b_len == 0)
+        return NULL;
+
+    InspectionBufferSetup(buffer, b, b_len);
+    InspectionBufferApplyTransforms(buffer, transforms);
+
+    SCReturnPtr(buffer, "InspectionBuffer");
+}
+
+static void PrefilterTxHttp2Header(DetectEngineThreadCtx *det_ctx,
+        const void *pectx,
+        Packet *p, Flow *f, void *txv,
+        const uint64_t idx, const uint8_t flags)
+{
+    SCEnter();
+
+    const PrefilterMpmListId *ctx = (const PrefilterMpmListId *)pectx;
+    const MpmCtx *mpm_ctx = ctx->mpm_ctx;
+    const int list_id = ctx->list_id;
+
+    int local_id = 0;
+
+    while(1) {
+        // loop until we get a NULL
+
+        struct MpmListIdDataArgs cbdata = { local_id, txv };
+        InspectionBuffer *buffer = GetHttp2HeaderData(det_ctx, flags, ctx->transforms,
+                f, &cbdata, list_id, true);
+        if (buffer == NULL)
+            break;
+
+        if (buffer->inspect_len >= mpm_ctx->minlen) {
+            (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx,
+                    &det_ctx->mtcu, &det_ctx->pmq,
+                    buffer->inspect, buffer->inspect_len);
+        }
+
+        local_id++;
+    }
+}
+
+static int PrefilterMpmHttp2HeaderRegister(DetectEngineCtx *de_ctx,
+        SigGroupHead *sgh, MpmCtx *mpm_ctx,
+        const DetectBufferMpmRegistery *mpm_reg, int list_id)
+{
+    PrefilterMpmListId *pectx = SCCalloc(1, sizeof(*pectx));
+    if (pectx == NULL)
+        return -1;
+    pectx->list_id = list_id;
+    pectx->mpm_ctx = mpm_ctx;
+    pectx->transforms = &mpm_reg->transforms;
+
+    return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxHttp2Header,
+            mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress,
+            pectx, PrefilterMpmHttp2HeaderFree, mpm_reg->name);
+}
+
+static int DetectEngineInspectHttp2Header(
+        DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+        const DetectEngineAppInspectionEngine *engine,
+        const Signature *s,
+        Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
+{
+    int local_id = 0;
+
+    const DetectEngineTransforms *transforms = NULL;
+    if (!engine->mpm) {
+        transforms = engine->v2.transforms;
+    }
+
+    while (1) {
+        struct MpmListIdDataArgs cbdata = { local_id, txv, };
+        InspectionBuffer *buffer = GetHttp2HeaderData(det_ctx, flags,
+                transforms, f, &cbdata, engine->sm_list, false);
+
+        if (buffer == NULL || buffer->inspect == NULL)
+            break;
+
+        det_ctx->buffer_offset = 0;
+        det_ctx->discontinue_matching = 0;
+        det_ctx->inspection_recursion_counter = 0;
+
+        const int match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd,
+                                              NULL, f,
+                                              (uint8_t *)buffer->inspect,
+                                              buffer->inspect_len,
+                                              buffer->inspect_offset, DETECT_CI_FLAGS_SINGLE,
+                                              DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE);
+        if (match == 1) {
+            return DETECT_ENGINE_INSPECT_SIG_MATCH;
+        }
+        local_id++;
+    }
+
+    return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
+}
+
+static bool DetectHttp2HeaderValidateCallback(const Signature *s, const char **sigerror)
+{
+    const SigMatch *sm = s->init_data->smlists[g_http2_header_buffer_id];
+    for ( ; sm != NULL; sm = sm->next) {
+        if (sm->type != DETECT_CONTENT)
+            continue;
+        const DetectContentData *cd = (DetectContentData *)sm->ctx;
+        bool escaped = false;
+        bool namevaluesep = false;
+        for (size_t i = 0; i < cd->content_len; ++i) {
+            if (escaped) {
+                if (cd->content[i] == ' ') {
+                    if (namevaluesep) {
+                        *sigerror = "Invalid http2.header string : "
+                        "': ' is a special sequence for separation between name and value "
+                        " and thus can only be present once";
+                        SCLogWarning(SC_WARN_POOR_RULE,  "rule %u: %s", s->id, *sigerror);
+                        return false;
+                    }
+                    namevaluesep = true;
+                } else if (cd->content[i] != ':') {
+                    *sigerror = "Invalid http2.header string : "
+                                "':' is an escaping character for itself, "
+                                "or space for the separation between name and value";
+                    SCLogWarning(SC_WARN_POOR_RULE,  "rule %u: %s", s->id, *sigerror);
+                    return false;
+                }
+                escaped = false;
+            } else if(cd->content[i] == ':') {
+                escaped = true;
+            }
+        }
+        if (escaped) {
+            *sigerror = "Invalid http2.header string : "
+            "':' is an escaping character for itself, "
+            "or space for the separation between name and value";
+            SCLogWarning(SC_WARN_POOR_RULE,  "rule %u: %s", s->id, *sigerror);
+            return false;
+        }
+    }
+    return true;
+}
+
+#ifdef UNITTESTS
+#include "tests/detect-http2.c"
+#endif
diff --git a/src/detect-http2.h b/src/detect-http2.h
new file mode 100644 (file)
index 0000000..11bc17e
--- /dev/null
@@ -0,0 +1,29 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Philippe Antoine <p.antoine@catenacyber.fr>
+ */
+
+#ifndef _DETECT_HTTP2_H
+#define _DETECT_HTTP2_H
+
+void DetectHttp2Register(void);
+
+#endif /* _DETECT_HTTP2_H */
index a6dfe1142fc5949b7f7140eb776ab571a280651c..32c51f4dbe6c33203e2e3aaf02f514348189d409 100644 (file)
@@ -162,6 +162,24 @@ static void AlertJsonSsh(const Flow *f, JsonBuilder *js)
     return;
 }
 
+static void AlertJsonHttp2(const Flow *f, const uint64_t tx_id, JsonBuilder *js)
+{
+    void *h2_state = FlowGetAppState(f);
+    if (h2_state) {
+        void *tx_ptr = rs_http2_state_get_tx(h2_state, tx_id);
+        JsonBuilderMark mark = { 0, 0, 0 };
+        jb_get_mark(js, &mark);
+        jb_open_object(js, "http2");
+        if (rs_http2_log_json(tx_ptr, js)) {
+            jb_close(js);
+        } else {
+            jb_restore_mark(js, &mark);
+        }
+    }
+
+    return;
+}
+
 static void AlertJsonDnp3(const Flow *f, const uint64_t tx_id, JsonBuilder *js)
 {
     DNP3State *dnp3_state = (DNP3State *)FlowGetAppState(f);
@@ -504,6 +522,9 @@ static void AlertAddAppLayer(const Packet *p, JsonBuilder *jb,
         case ALPROTO_DNP3:
             AlertJsonDnp3(p->flow, tx_id, jb);
             break;
+        case ALPROTO_HTTP2:
+            AlertJsonHttp2(p->flow, tx_id, jb);
+            break;
         case ALPROTO_DNS:
             AlertJsonDns(p->flow, tx_id, jb);
             break;
index 8e03b27ed80ea1c003ed6d5bae12e8fb24856abb..5932c6ab8f245de188684ec5eed6434d01d7292f 100644 (file)
@@ -62,6 +62,7 @@
 #include "output-json-email-common.h"
 #include "output-json-nfs.h"
 #include "output-json-smb.h"
+#include "output-json-http2.h"
 
 #include "app-layer-htp.h"
 #include "app-layer-htp-xff.h"
@@ -172,6 +173,15 @@ JsonBuilder *JsonBuildFileInfoRecord(const Packet *p, const File *ff,
                 jb_restore_mark(js, &mark);
             }
             break;
+        case ALPROTO_HTTP2:
+            jb_get_mark(js, &mark);
+            jb_open_object(js, "http2");
+            if (EveHTTP2AddMetadata(p->flow, ff->txid, js)) {
+                jb_close(js);
+            } else {
+                jb_restore_mark(js, &mark);
+            }
+            break;
     }
 
     jb_set_string(js, "app_proto", AppProtoToString(p->flow->alproto));
diff --git a/src/output-json-http2.c b/src/output-json-http2.c
new file mode 100644 (file)
index 0000000..9357c26
--- /dev/null
@@ -0,0 +1,251 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Philippe Antoine <p.antoine@catenacyber.fr>
+ *
+ * Implements HTTP2 JSON logging portion of the engine.
+ */
+
+#include "suricata-common.h"
+#include "debug.h"
+#include "detect.h"
+#include "pkt-var.h"
+#include "conf.h"
+
+#include "threads.h"
+#include "threadvars.h"
+#include "tm-threads.h"
+
+#include "util-print.h"
+#include "util-unittest.h"
+
+#include "util-debug.h"
+#include "app-layer-parser.h"
+#include "output.h"
+#include "app-layer-http2.h"
+#include "app-layer.h"
+#include "util-privs.h"
+#include "util-buffer.h"
+
+#include "util-logopenfile.h"
+#include "util-crypt.h"
+
+#include "output-json.h"
+#include "output-json-http2.h"
+#include "rust.h"
+
+#define MODULE_NAME "LogHttp2Log"
+
+typedef struct OutputHttp2Ctx_ {
+    LogFileCtx *file_ctx;
+    OutputJsonCommonSettings cfg;
+} OutputHttp2Ctx;
+
+
+typedef struct JsonHttp2LogThread_ {
+    OutputHttp2Ctx *http2log_ctx;
+    MemBuffer *buffer;
+} JsonHttp2LogThread;
+
+
+bool EveHTTP2AddMetadata(const Flow *f, uint64_t tx_id, JsonBuilder *jb)
+{
+    void *state = FlowGetAppState(f);
+    if (state) {
+        void *tx = AppLayerParserGetTx(f->proto, ALPROTO_HTTP2, state, tx_id);
+        if (tx) {
+            return rs_http2_log_json(tx, jb);
+        }
+    }
+    return false;
+}
+
+static int JsonHttp2Logger(ThreadVars *tv, void *thread_data, const Packet *p,
+                         Flow *f, void *state, void *txptr, uint64_t tx_id)
+{
+    JsonHttp2LogThread *aft = (JsonHttp2LogThread *)thread_data;
+    OutputHttp2Ctx *http2_ctx = aft->http2log_ctx;
+
+    if (unlikely(state == NULL)) {
+        return 0;
+    }
+
+    JsonBuilder *js = CreateEveHeaderWithTxId(p, LOG_DIR_FLOW, "http2", NULL, tx_id);
+    if (unlikely(js == NULL))
+        return 0;
+
+    EveAddCommonOptions(&http2_ctx->cfg, p, f, js);
+
+    /* reset */
+    MemBufferReset(aft->buffer);
+
+    jb_open_object(js, "http2");
+    if (!rs_http2_log_json(txptr, js)) {
+        goto end;
+    }
+    jb_close(js);
+    OutputJsonBuilderBuffer(js, http2_ctx->file_ctx, &aft->buffer);
+end:
+    jb_free(js);
+    return 0;
+}
+
+static TmEcode JsonHttp2LogThreadInit(ThreadVars *t, const void *initdata, void **data)
+{
+    JsonHttp2LogThread *aft = SCMalloc(sizeof(JsonHttp2LogThread));
+    if (unlikely(aft == NULL))
+        return TM_ECODE_FAILED;
+    memset(aft, 0, sizeof(JsonHttp2LogThread));
+
+    if(initdata == NULL)
+    {
+        SCLogDebug("Error getting context for EveLogHTTP2.  \"initdata\" argument NULL");
+        SCFree(aft);
+        return TM_ECODE_FAILED;
+    }
+
+    /* Use the Ouptut Context (file pointer and mutex) */
+    aft->http2log_ctx = ((OutputCtx *)initdata)->data;
+
+    aft->buffer = MemBufferCreateNew(JSON_OUTPUT_BUFFER_SIZE);
+    if (aft->buffer == NULL) {
+        SCFree(aft);
+        return TM_ECODE_FAILED;
+    }
+
+    *data = (void *)aft;
+    return TM_ECODE_OK;
+}
+
+static TmEcode JsonHttp2LogThreadDeinit(ThreadVars *t, void *data)
+{
+    JsonHttp2LogThread *aft = (JsonHttp2LogThread *)data;
+    if (aft == NULL) {
+        return TM_ECODE_OK;
+    }
+
+    MemBufferFree(aft->buffer);
+    /* clear memory */
+    memset(aft, 0, sizeof(JsonHttp2LogThread));
+
+    SCFree(aft);
+    return TM_ECODE_OK;
+}
+
+static void OutputHttp2LogDeinit(OutputCtx *output_ctx)
+{
+    OutputHttp2Ctx *http2_ctx = output_ctx->data;
+    LogFileCtx *logfile_ctx = http2_ctx->file_ctx;
+    LogFileFreeCtx(logfile_ctx);
+    SCFree(http2_ctx);
+    SCFree(output_ctx);
+}
+
+#define DEFAULT_LOG_FILENAME "http2.json"
+static OutputInitResult OutputHttp2LogInit(ConfNode *conf)
+{
+    OutputInitResult result = { NULL, false };
+    LogFileCtx *file_ctx = LogFileNewCtx();
+    if(file_ctx == NULL) {
+        SCLogError(SC_ERR_HTTP2_LOG_GENERIC, "couldn't create new file_ctx");
+        return result;
+    }
+
+    if (SCConfLogOpenGeneric(conf, file_ctx, DEFAULT_LOG_FILENAME, 1) < 0) {
+        LogFileFreeCtx(file_ctx);
+        return result;
+    }
+
+    OutputHttp2Ctx *http2_ctx = SCMalloc(sizeof(OutputHttp2Ctx));
+    if (unlikely(http2_ctx == NULL)) {
+        LogFileFreeCtx(file_ctx);
+        return result;
+    }
+
+    OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
+    if (unlikely(output_ctx == NULL)) {
+        LogFileFreeCtx(file_ctx);
+        SCFree(http2_ctx);
+        return result;
+    }
+
+    http2_ctx->file_ctx = file_ctx;
+
+    output_ctx->data = http2_ctx;
+    output_ctx->DeInit = OutputHttp2LogDeinit;
+
+    AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_HTTP2);
+
+    result.ctx = output_ctx;
+    result.ok = true;
+    return result;
+}
+
+static void OutputHttp2LogDeinitSub(OutputCtx *output_ctx)
+{
+    OutputHttp2Ctx *http2_ctx = output_ctx->data;
+    SCFree(http2_ctx);
+    SCFree(output_ctx);
+}
+
+static OutputInitResult OutputHttp2LogInitSub(ConfNode *conf, OutputCtx *parent_ctx)
+{
+    OutputInitResult result = { NULL, false };
+    OutputJsonCtx *ojc = parent_ctx->data;
+
+    OutputHttp2Ctx *http2_ctx = SCMalloc(sizeof(OutputHttp2Ctx));
+    if (unlikely(http2_ctx == NULL))
+        return result;
+
+    OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx));
+    if (unlikely(output_ctx == NULL)) {
+        SCFree(http2_ctx);
+        return result;
+    }
+
+    http2_ctx->file_ctx = ojc->file_ctx;
+    http2_ctx->cfg = ojc->cfg;
+
+    output_ctx->data = http2_ctx;
+    output_ctx->DeInit = OutputHttp2LogDeinitSub;
+
+    AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_HTTP2);
+
+    result.ctx = output_ctx;
+    result.ok = true;
+    return result;
+}
+
+void JsonHttp2LogRegister (void)
+{
+    /* register as separate module */
+    OutputRegisterTxModuleWithProgress(LOGGER_JSON_HTTP2,
+        MODULE_NAME, "http2-json-log",
+        OutputHttp2LogInit, ALPROTO_HTTP2, JsonHttp2Logger,
+        HTTP2StateClosed, HTTP2StateClosed,
+        JsonHttp2LogThreadInit, JsonHttp2LogThreadDeinit, NULL);
+
+    /* also register as child of eve-log */
+    OutputRegisterTxSubModuleWithProgress(LOGGER_JSON_HTTP2,
+        "eve-log", MODULE_NAME, "eve-log.http2",
+        OutputHttp2LogInitSub, ALPROTO_HTTP2, JsonHttp2Logger,
+        HTTP2StateClosed, HTTP2StateClosed,
+        JsonHttp2LogThreadInit, JsonHttp2LogThreadDeinit, NULL);
+}
diff --git a/src/output-json-http2.h b/src/output-json-http2.h
new file mode 100644 (file)
index 0000000..66bf2ad
--- /dev/null
@@ -0,0 +1,30 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Philippe Antoine <p.antoine@catenacyber.fr>
+ */
+
+#ifndef __OUTPUT_JSON_HTTP2_H__
+#define __OUTPUT_JSON_HTTP2_H__
+
+void JsonHttp2LogRegister(void);
+bool EveHTTP2AddMetadata(const Flow *f, uint64_t tx_id, JsonBuilder *jb);
+
+#endif /* __OUTPUT_JSON_HTTP2_H__ */
index 379a58768c555a9ac8f0f789ba280d2ae58333e3..b41f2b488d59d46bf566dbe24394724f49f9a6ab 100644 (file)
@@ -79,6 +79,7 @@
 #include "output-json-template.h"
 #include "output-json-template-rust.h"
 #include "output-json-rdp.h"
+#include "output-json-http2.h"
 #include "output-lua.h"
 #include "output-json-dnp3.h"
 #include "output-json-metadata.h"
@@ -1094,6 +1095,7 @@ void OutputRegisterLoggers(void)
     /* http log */
     LogHttpLogRegister();
     JsonHttpLogRegister();
+    JsonHttp2LogRegister();
     /* tls log */
     LogTlsLogRegister();
     JsonTlsLogRegister();
index f1e70013760d8ae9e2ac8f447aee156e753fc703..e9d52a080e1402ad1354fe9a67aa585718bfcd72 100644 (file)
@@ -468,6 +468,7 @@ typedef enum {
     LOGGER_JSON_TEMPLATE,
     LOGGER_JSON_RDP,
     LOGGER_JSON_DCERPC,
+    LOGGER_JSON_HTTP2,
 
     LOGGER_ALERT_DEBUG,
     LOGGER_ALERT_FAST,
diff --git a/src/tests/detect-http2.c b/src/tests/detect-http2.c
new file mode 100644 (file)
index 0000000..2ce1bd4
--- /dev/null
@@ -0,0 +1,161 @@
+/* Copyright (C) 2020 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include "../suricata-common.h"
+
+#include "../detect-engine.h"
+
+#include "../detect-http2.h"
+
+#include "../util-unittest.h"
+
+/**
+ * \test signature with a valid http2.frametype value.
+ */
+
+static int DetectHTTP2frameTypeParseTest01 (void)
+{
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    Signature *sig = DetectEngineAppendSig(de_ctx,
+                                           "alert http2 any any -> any any (http2.frametype:GOAWAY; sid:1; rev:1;)");
+    FAIL_IF_NULL(sig);
+
+    DetectEngineCtxFree(de_ctx);
+    PASS;
+}
+
+/**
+ * \brief this function registers unit tests for DetectHTTP2frameType
+ */
+void DetectHTTP2frameTypeRegisterTests(void)
+{
+    UtRegisterTest("DetectHTTP2frameTypeParseTest01", DetectHTTP2frameTypeParseTest01);
+}
+
+/**
+ * \test signature with a valid http2.errorcode value.
+ */
+
+static int DetectHTTP2errorCodeParseTest01 (void)
+{
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    Signature *sig = DetectEngineAppendSig(de_ctx,
+                                           "alert http2 any any -> any any (http2.errorcode:NO_ERROR; sid:1; rev:1;)");
+    FAIL_IF_NULL(sig);
+
+    DetectEngineCtxFree(de_ctx);
+    PASS;
+}
+
+void DetectHTTP2errorCodeRegisterTests(void)
+{
+    UtRegisterTest("DetectHTTP2errorCodeParseTest01", DetectHTTP2errorCodeParseTest01);
+}
+
+/**
+ * \test signature with a valid http2.priority value.
+ */
+
+static int DetectHTTP2priorityParseTest01 (void)
+{
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    Signature *sig = DetectEngineAppendSig(de_ctx,
+                                           "alert http2 any any -> any any (http2.priority:>100; sid:1; rev:1;)");
+    FAIL_IF_NULL(sig);
+
+    DetectEngineCtxFree(de_ctx);
+    PASS;
+}
+
+void DetectHTTP2priorityRegisterTests(void)
+{
+    UtRegisterTest("DetectHTTP2priorityParseTest01", DetectHTTP2priorityParseTest01);
+}
+
+/**
+ * \test signature with a valid http2.window value.
+ */
+
+static int DetectHTTP2windowParseTest01 (void)
+{
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    Signature *sig = DetectEngineAppendSig(de_ctx,
+                                           "alert http2 any any -> any any (http2.window:<42; sid:1; rev:1;)");
+    FAIL_IF_NULL(sig);
+
+    DetectEngineCtxFree(de_ctx);
+    PASS;
+}
+
+void DetectHTTP2windowRegisterTests(void)
+{
+    UtRegisterTest("DetectHTTP2windowParseTest01", DetectHTTP2windowParseTest01);
+}
+
+
+/**
+ * \test signature with a valid http2.settings value.
+ */
+
+static int DetectHTTP2settingsParseTest01 (void)
+{
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    Signature *sig = DetectEngineAppendSig(de_ctx,
+                                           "alert http2 any any -> any any (http2.settings:SETTINGS_MAX_HEADER_LIST_SIZE >1024; sid:1; rev:1;)");
+    FAIL_IF_NULL(sig);
+
+    DetectEngineCtxFree(de_ctx);
+    PASS;
+}
+
+void DetectHTTP2settingsRegisterTests(void)
+{
+    UtRegisterTest("DetectHTTP2settingsParseTest01", DetectHTTP2settingsParseTest01);
+}
+
+
+/**
+* \test signature with a valid http2.size_update value.
+*/
+
+static int DetectHTTP2sizeUpdateParseTest01 (void)
+{
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    Signature *sig = DetectEngineAppendSig(de_ctx,
+                                           "alert http2 any any -> any any (http2.size_update:>4096; sid:1; rev:1;)");
+    FAIL_IF_NULL(sig);
+
+    DetectEngineCtxFree(de_ctx);
+    PASS;
+}
+
+void DetectHTTP2sizeUpdateRegisterTests(void)
+{
+    UtRegisterTest("DetectHTTP2sizeUpdateParseTest01", DetectHTTP2sizeUpdateParseTest01);
+}
index 8cb634af9815b0d7fc985f02ae4d582697431e73..822f7033de8eabbb81a11061a0397db996ef7452 100644 (file)
@@ -108,6 +108,7 @@ const char * SCErrorToString(SCError err)
         CASE_CODE (SC_ERR_DEBUG_LOG_GENERIC);
         CASE_CODE (SC_ERR_UNIFIED_LOG_GENERIC);
         CASE_CODE (SC_ERR_HTTP_LOG_GENERIC);
+        CASE_CODE (SC_ERR_HTTP2_LOG_GENERIC);
         CASE_CODE (SC_ERR_FTP_LOG_GENERIC);
         CASE_CODE (SC_ERR_UNIFIED_ALERT_GENERIC);
         CASE_CODE (SC_ERR_UNIFIED2_ALERT_GENERIC);
index d3260af10e6a694b9224edd8762c29a9efa602b1..260f4697aede47757e6653ab295b00334215a283 100644 (file)
@@ -363,6 +363,7 @@ typedef enum {
     SC_WARN_HASSH_DISABLED,
     SC_WARN_FILESTORE_CONFIG,
     SC_WARN_PATH_READ_ERROR,
+    SC_ERR_HTTP2_LOG_GENERIC,
 
     SC_ERR_MAX
 } SCError;
index 37a37794f8948ce12ab322e3c1cbabde7f6fc332..cc6d5c0856e861cd15d89faec6add7e2f0f5c675 100644 (file)
@@ -1322,6 +1322,7 @@ const char * PacketProfileLoggertIdToString(LoggerId id)
         CASE_CODE (LOGGER_JSON_TEMPLATE);
         CASE_CODE (LOGGER_JSON_RDP);
         CASE_CODE (LOGGER_JSON_DCERPC);
+        CASE_CODE (LOGGER_JSON_HTTP2);
         CASE_CODE (LOGGER_TLS_STORE);
         CASE_CODE (LOGGER_TLS);
         CASE_CODE (LOGGER_FILE_STORE);
index f747c501406d609b3a4ff4c635c4c138daab73e5..8833480cc4aef488031ff8a606b5409228caaeda 100644 (file)
@@ -281,6 +281,7 @@ outputs:
         - ssh
         - mqtt:
             # passwords: yes           # enable output of passwords
+        - http2
         - stats:
             totals: yes       # stats for all threads merged together
             threads: no       # per thread stats
@@ -756,6 +757,8 @@ app-layer:
     ssh:
       enabled: yes
       #hassh: yes
+    http2:
+      enabled: yes
     smtp:
       enabled: yes
       raw-extraction: no