Skip to content

Commit c9dc651

Browse files
Merge pull request #2 from pfeatherstone/fuzz
Code coverage
2 parents 9f72729 + 7744067 commit c9dc651

File tree

14 files changed

+571
-323
lines changed

14 files changed

+571
-323
lines changed

.github/workflows/coverage.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Coverage
2+
on:
3+
push:
4+
branches:
5+
- '**'
6+
pull_request:
7+
branches:
8+
- '**'
9+
10+
jobs:
11+
build-test:
12+
runs-on: ubuntu-24.04
13+
name: ubuntu-coverage
14+
steps:
15+
- name: Checkout repository
16+
uses: actions/checkout@v4
17+
18+
- name: Install dependencies
19+
run: |
20+
sudo apt update
21+
sudo apt install -y ninja-build cmake clang llvm
22+
23+
- name: Run CMake configuration and build
24+
run: |
25+
cmake examples -B build -G Ninja \
26+
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
27+
-DCMAKE_C_COMPILER=clang \
28+
-DCMAKE_CXX_COMPILER=clang++ \
29+
-DHTTP_BUILD_FUZZERS=ON
30+
cmake --build build --target request_parser response_parser websocket_parser sha1 base64 --parallel
31+
32+
- name: Extract corpus
33+
run: |
34+
tar -zxvf examples/fuzz/seeds.tgz
35+
36+
- name: Run fuzzers
37+
run: |
38+
mkdir -p /tmp/corpus
39+
LLVM_PROFILE_FILE="request.profraw" ./build/request_parser /tmp/corpus/ seeds/request_parser/ -max_total_time=30
40+
LLVM_PROFILE_FILE="response.profraw" ./build/response_parser /tmp/corpus/ seeds/response_parser/ -max_total_time=30
41+
LLVM_PROFILE_FILE="websocket.profraw" ./build/websocket_parser /tmp/corpus/ seeds/websocket_parser/ -max_total_time=30
42+
LLVM_PROFILE_FILE="sha1.profraw" ./build/sha1 -max_total_time=30
43+
LLVM_PROFILE_FILE="base64.profraw" ./build/base64 -max_total_time=30
44+
45+
- name: Generate coverage report
46+
run: |
47+
llvm-profdata merge -o coverage.profdata \
48+
request.profraw \
49+
response.profraw \
50+
websocket.profraw \
51+
sha1.profraw \
52+
base64.profraw
53+
llvm-cov export -format=lcov -instr-profile coverage.profdata \
54+
-object ./build/request_parser \
55+
-object ./build/response_parser \
56+
-object ./build/websocket_parser \
57+
-object ./build/sha1 \
58+
-object ./build/base64 \
59+
-sources ./src \
60+
> coverage_${{github.sha}}.txt
61+
62+
- name: Upload coverage reports to Codecov
63+
uses: codecov/codecov-action@v5
64+
with:
65+
token: ${{ secrets.CODECOV_TOKEN }}
66+
slug: pfeatherstone/https
67+
files: coverage_${{github.sha}}.txt

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
**/build
2-
**/.vscode
2+
**/.vscode
3+
**/*.profraw
4+
**/*.profdata
5+
**/seeds/**

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
![Ubuntu](https://github.com/pfeatherstone/https/actions/workflows/ubuntu.yml/badge.svg)
22
![MacOS](https://github.com/pfeatherstone/https/actions/workflows/macos.yml/badge.svg)
33
![Windows](https://github.com/pfeatherstone/https/actions/workflows/windows.yml/badge.svg)
4+
[![codecov](https://codecov.io/gh/pfeatherstone/https/branch/main/graph/badge.svg)](https://codecov.io/gh/pfeatherstone/https)
45

56
# https
67

examples/CMakeLists.txt

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
cmake_minimum_required(VERSION 3.13)
22
project(Http)
33

4+
# Policies
5+
if(POLICY CMP0127)
6+
cmake_policy(SET CMP0127 NEW)
7+
endif()
8+
if(POLICY CMP0135)
9+
cmake_policy(SET CMP0135 OLD)
10+
endif()
11+
412
# Deps
513
include(FetchContent)
14+
include(CMakeDependentOption)
615
find_package(Threads REQUIRED)
716
find_package(OpenSSL REQUIRED)
817

9-
if(POLICY CMP0135)
10-
cmake_policy(SET CMP0135 OLD)
11-
endif()
1218
set(BOOST_INCLUDE_LIBRARIES compat asio)
1319
set(BOOST_ENABLE_CMAKE ON)
1420
FetchContent_Declare(
@@ -17,30 +23,32 @@ FetchContent_Declare(
1723
URL_HASH MD5=3edffaacd2cfe63c240ef1b99497c74f)
1824
FetchContent_MakeAvailable(Boost)
1925

26+
# Options
27+
cmake_dependent_option(HTTP_BUILD_FUZZERS "Builds Fuzz tests" OFF "CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\"" OFF)
28+
2029
# Lib
2130
add_library(http ${CMAKE_CURRENT_SOURCE_DIR}/../src/http.cpp)
2231
target_compile_features(http PUBLIC cxx_std_17)
2332
target_include_directories(http PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../src)
2433
target_link_libraries(http PUBLIC OpenSSL::SSL OpenSSL::Crypto Boost::asio)
34+
target_compile_options(http PRIVATE $<$<BOOL:${HTTP_BUILD_FUZZERS}>:-fprofile-instr-generate -fcoverage-mapping>)
2535

2636
# Examples
27-
function(add_executable_20 target_name)
28-
add_executable(${target_name} ${ARGN})
29-
target_compile_features(${target_name} PUBLIC cxx_std_20)
30-
target_link_options(${target_name} PUBLIC $<$<CONFIG:Release>:-s>)
31-
target_link_libraries(${target_name} PUBLIC Threads::Threads http)
32-
endfunction()
33-
34-
function(add_executable_coro target_name)
37+
function(add_example target_name)
3538
add_executable(${target_name} ${ARGN})
3639
target_link_options(${target_name} PRIVATE $<$<CONFIG:Release>:-s>)
3740
target_link_libraries(${target_name} PRIVATE Threads::Threads Boost::compat http)
3841
endfunction()
3942

40-
add_executable_20(server ${CMAKE_CURRENT_SOURCE_DIR}/server.cpp)
41-
add_executable_20(client_http ${CMAKE_CURRENT_SOURCE_DIR}/client_http.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/yyjson.c)
42-
add_executable_20(client_ws_awaitable ${CMAKE_CURRENT_SOURCE_DIR}/client_ws_awaitable.cpp)
43-
add_executable_coro(client_ws_coro ${CMAKE_CURRENT_SOURCE_DIR}/client_ws_coro.cpp)
43+
function(add_example_20 target_name)
44+
add_example(${target_name} ${ARGN})
45+
target_compile_features(${target_name} PUBLIC cxx_std_20)
46+
endfunction()
47+
48+
add_example_20(server ${CMAKE_CURRENT_SOURCE_DIR}/server.cpp)
49+
add_example_20(client_http ${CMAKE_CURRENT_SOURCE_DIR}/client_http.cpp ${CMAKE_CURRENT_SOURCE_DIR}/extra/yyjson.c)
50+
add_example_20(client_ws_awaitable ${CMAKE_CURRENT_SOURCE_DIR}/client_ws_awaitable.cpp)
51+
add_example(client_ws_coro ${CMAKE_CURRENT_SOURCE_DIR}/client_ws_coro.cpp)
4452

4553
# Unit tests
4654
add_executable(tests
@@ -54,4 +62,20 @@ add_executable(tests
5462
target_compile_features(tests PUBLIC cxx_std_20)
5563
target_link_options(tests PRIVATE $<$<CONFIG:Release>:-s>)
5664
target_include_directories(tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/extra)
57-
target_link_libraries(tests PRIVATE http)
65+
target_link_libraries(tests PRIVATE http)
66+
67+
# Fuzz tests
68+
if (HTTP_BUILD_FUZZERS)
69+
function(add_fuzz target_name)
70+
add_executable(${target_name} ${ARGN})
71+
target_link_libraries(${target_name} PRIVATE http)
72+
target_compile_options(${target_name} PRIVATE -fprofile-instr-generate -fcoverage-mapping)
73+
target_link_options(${target_name} PRIVATE -fsanitize=fuzzer -fprofile-instr-generate -fcoverage-mapping)
74+
endfunction()
75+
76+
add_fuzz(request_parser fuzz/request_parser.cpp)
77+
add_fuzz(response_parser fuzz/response_parser.cpp)
78+
add_fuzz(websocket_parser fuzz/websocket_parser.cpp)
79+
add_fuzz(sha1 fuzz/sha1.cpp)
80+
add_fuzz(base64 fuzz/base64.cpp)
81+
endif()

examples/fuzz/base64.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <cstdint>
2+
#include <cstddef>
3+
#include <algorithm>
4+
#include <http.h>
5+
6+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
7+
{
8+
auto encoded = http::base64_encode(size, data);
9+
auto decoded = http::base64_decode(encoded);
10+
encoded.erase(std::remove(begin(encoded), end(encoded), '='), end(encoded));
11+
auto decoded2 = http::base64_decode(encoded);
12+
return 0;
13+
}

examples/fuzz/request_parser.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include <cstdint>
2+
#include <cstddef>
3+
#include <http.h>
4+
5+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
6+
{
7+
std::string buf(reinterpret_cast<const char*>(data), size);
8+
http::request req{};
9+
std::error_code ec{};
10+
http::parser_request parser;
11+
parser.parse(req, buf, ec);
12+
bool is_keep_alive = req.keep_alive();
13+
bool is_websocket_req = req.is_websocket_req();
14+
std::string ec_msg = ec ? ec.message() : "";
15+
const auto& ec_cat = ec.category();
16+
buf.clear();
17+
ec = {};
18+
http::serialize_header(req, buf, ec);
19+
req.clear();
20+
ec_msg = ec ? ec.message() : "";
21+
return 0;
22+
}

examples/fuzz/response_parser.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include <cstdint>
2+
#include <cstddef>
3+
#include <http.h>
4+
5+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
6+
{
7+
std::string buf(reinterpret_cast<const char*>(data), size);
8+
http::response resp{};
9+
std::error_code ec{};
10+
http::parser_response parser;
11+
parser.parse(resp, buf, ec);
12+
buf.clear();
13+
std::string ec_msg = ec ? ec.message() : "";
14+
const auto& ec_cat = ec.category();
15+
ec = {};
16+
http::serialize_header(resp, buf, ec);
17+
resp.keep_alive(true);
18+
bool is_websocket = resp.is_websocket_response();
19+
resp.clear();
20+
ec_msg = ec ? ec.message() : "";
21+
return 0;
22+
}

examples/fuzz/seeds.tgz

2.93 MB
Binary file not shown.

examples/fuzz/sha1.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include <cstdint>
2+
#include <cstddef>
3+
#include <http.h>
4+
5+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
6+
{
7+
auto sum = http::sha1{}.push(size, data).finish();
8+
return 0;
9+
}

examples/fuzz/websocket_parser.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#include <cstdint>
2+
#include <cstddef>
3+
#include <http.h>
4+
5+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
6+
{
7+
std::string buf(reinterpret_cast<const char*>(data), size);
8+
9+
http::request req{};
10+
std::error_code ec{};
11+
http::parser_request parser;
12+
parser.parse(req, buf, ec);
13+
bool is_websocket_req = req.is_websocket_req();
14+
std::string ec_msg = ec ? ec.message() : "";
15+
const auto& ec_cat = ec.category();
16+
17+
std::vector<char> msg;
18+
http::dynamic_buffer view(msg);
19+
http::websocket_parser ws_parser;
20+
ws_parser.parse(view, buf, ec);
21+
const auto opcode = ws_parser.get_opcode();
22+
const auto is_server = ws_parser.is_server();
23+
const auto current_size = view.size();
24+
const auto current_data = view.data();
25+
const auto current_buf = view.buffer();
26+
ec_msg = ec ? ec.message() : "";
27+
28+
view.resize(10);
29+
view.clear();
30+
msg.assign(data, data+size);
31+
buf.clear();
32+
http::serialize_websocket_message(boost::asio::buffer(msg), http::WS_OPCODE_DATA_BINARY, true, buf);
33+
buf.clear();
34+
http::serialize_websocket_message(boost::asio::buffer(msg), http::WS_OPCODE_DATA_BINARY, false, buf);
35+
buf.clear();
36+
http::serialize_websocket_message(boost::asio::buffer(msg), http::WS_OPCODE_DATA_TEXT, true, buf);
37+
buf.clear();
38+
http::serialize_websocket_message(boost::asio::buffer(msg), http::WS_OPCODE_DATA_TEXT, false, buf);
39+
return 0;
40+
}

0 commit comments

Comments
 (0)