Program Listing for File config_fuzz.cpp#
↰ Return to documentation for file (Test/fuzz/config_fuzz.cpp)
#include "kataglyphis_test_support.h"
#include "fuzztest/fuzztest.h"
#include "gtest/gtest.h"
#include <cstdint>
#include <limits>
#include <string>
#include <string_view>
#include "kShippedInferenceConfig.inc"
import kataglyphis.config_loader;
import kataglyphis.toml_config;
import kataglyphis.webrtc_streamer;
import kataglyphis.yolo_detector;
namespace cfg = kataglyphis::config;
namespace webrtc = kataglyphis::webrtc;
namespace {
constexpr std::string_view kSettingsSeed =
R"({"signalingServerUrl": "ws://127.0.0.1:8443", "reconnectionTimeoutMs": 5000, "stunServers": ["stun:a"],
"video": {"defaultWidth": 1280, "defaultHeight": 720}, "texture": {"width": 640},
"android": {"fps": 15}, "stream": {"source": "test", "encoder": "vp8", "producerId": "p"}})";
// JSON-escapes printable ASCII, the only characters the domain below produces.
auto json_string(std::string_view text) -> std::string
{
std::string quoted = "\"";
for (const char ch : text) {
if (ch == '"' || ch == '\\') { quoted.push_back('\\'); }
quoted.push_back(ch);
}
quoted.push_back('"');
return quoted;
}
}// namespace
// Untrusted settings files: any bytes either parse or come back as an error, never a crash or a hang.
void ParseWebRtcSettingsNeverCrashes(const std::string &bytes)
{
const auto config = cfg::parse_webrtc_config(bytes);
if (!config.has_value()) {
EXPECT_TRUE(config.error() == cfg::ConfigError::ParseError || config.error() == cfg::ConfigError::InvalidValue);
}
}
FUZZ_TEST(ConfigFuzz, ParseWebRtcSettingsNeverCrashes)
.WithSeeds({ { std::string(kSettingsSeed) }, { "{}" }, { "[]" }, { R"({"video": {"defaultWidth": -1}})" } });
// Every unsigned field holds exactly what the file says, or the file is rejected; nothing wraps.
void UnsignedSettingsRoundTripOrAreRejected(std::uint64_t value)
{
const auto config = cfg::parse_webrtc_config(R"({"texture": {"width": )" + std::to_string(value) + "}}");
if (value <= std::numeric_limits<std::uint32_t>::max()) {
ASSERT_TRUE(config.has_value()) << value;
EXPECT_EQ(config->texture.width, value);
} else {
ASSERT_FALSE(config.has_value()) << value;
EXPECT_EQ(config.error(), cfg::ConfigError::InvalidValue);
}
}
FUZZ_TEST(ConfigFuzz, UnsignedSettingsRoundTripOrAreRejected)
.WithSeeds({ { 0U }, { 4294967295ULL }, { 4294967296ULL }, { std::numeric_limits<std::uint64_t>::max() } });
void StringSettingsRoundTrip(const std::string &text)
{
const auto config = cfg::parse_webrtc_config(R"({"stream": {"device": )" + json_string(text) + "}}");
ASSERT_TRUE(config.has_value()) << text;
EXPECT_EQ(config->stream.device, text);
}
FUZZ_TEST(ConfigFuzz, StringSettingsRoundTrip).WithDomains(fuzztest::PrintableAsciiString());
// Untrusted inference configs, the same contract for the TOML parser.
void ParseInferenceConfigNeverCrashes(const std::string &bytes)
{
const auto config = cfg::parse_inference_config(bytes);
if (!config.has_value()) { EXPECT_EQ(config.error(), cfg::TomlConfigError::ParseError); }
}
FUZZ_TEST(ConfigFuzz, ParseInferenceConfigNeverCrashes)
.WithSeeds({ { std::string(kShippedInferenceConfig) }, { "[model]\ninput_width = 320\n" }, { "" } });
// An integer the uint32 fields cannot hold falls back to the default instead of wrapping.
void TomlUnsignedFieldsTakeOnlyTheirRange(std::int64_t value)
{
const auto config = cfg::parse_inference_config("[model]\ninput_width = " + std::to_string(value) + "\n");
ASSERT_TRUE(config.has_value()) << value;
if (value >= 0 && value <= std::numeric_limits<std::uint32_t>::max()) {
EXPECT_EQ(config->model.input_width, static_cast<std::uint64_t>(value));
} else {
EXPECT_EQ(config->model.input_width, 640U) << value;
}
}
FUZZ_TEST(ConfigFuzz, TomlUnsignedFieldsTakeOnlyTheirRange)
.WithSeeds({ { 0 }, { -1 }, { 4294967295LL }, { 4294967296LL }, { std::numeric_limits<std::int64_t>::min() } });
// The settings' free-text source and encoder names map to an enum for every input.
void StreamNamesAlwaysMap(const std::string &source, const std::string &encoder)
{
cfg::WebRTCConfig settings;
settings.stream.source = source;
settings.stream.encoder = encoder;
const auto config = webrtc::create_stream_config_from_webrtc_config(
settings, webrtc::VideoSource::TestPattern, webrtc::VideoEncoder::VP9);
const bool known_source =
source == "libcamera" || source == "v4l2" || source == "test" || source == "file" || source == "uri";
const bool known_encoder = encoder == "h264-hw" || encoder == "h264-sw" || encoder == "vp8" || encoder == "vp9";
if (!known_source) { EXPECT_EQ(config.source, webrtc::VideoSource::TestPattern); }
if (!known_encoder) { EXPECT_EQ(config.encoder, webrtc::VideoEncoder::VP9); }
}
FUZZ_TEST(ConfigFuzz, StreamNamesAlwaysMap)
.WithSeeds({ { "libcamera", "h264-hw" }, { "v4l2", "vp8" }, { "file", "h264-sw" }, { "camera", "h265" } });
void CocoClassNamesAreTotal(int class_id)
{
const auto name = kataglyphis::detection::YoloDetector::get_coco_class_name(class_id);
EXPECT_FALSE(name.empty());
EXPECT_EQ(name == "unknown", class_id < 0 || class_id >= 80) << class_id;
}
FUZZ_TEST(ConfigFuzz, CocoClassNamesAreTotal);