#include #include #include #include #include #include #include "wireframe/capture_session.hpp" #include "wireframe/pcapng/writer.hpp" using namespace wireframe; namespace { // A real, named pcapng file on disk - CaptureSession::open() takes a // path, not a FILE*, so a std::tmpfile() (unnamed) doesn't work here // the way it does in test_pcapng.cpp. Cleans itself up via RAII. struct TempPcapngFile { std::string path; explicit TempPcapngFile(int link_type, const std::vector>& packets) { char path_template[] = "/tmp/wireframe_test_XXXXXX"; int fd = mkstemp(path_template); REQUIRE(fd != -1); path = path_template; std::FILE* f = fdopen(fd, "wb"); REQUIRE(f != nullptr); pcapng::Writer writer(f); writer.write_section_header(); writer.write_interface_description(65535, static_cast(link_type)); std::uint32_t ts = 1700000000; for (const auto& packet : packets) { writer.write_packet(0, ts++, 0, packet, static_cast(packet.size())); } std::fclose(f); } ~TempPcapngFile() { std::remove(path.c_str()); } }; } // namespace TEST_CASE("is_supported_datalink accepts EN10MB and RAW, rejects others") { CHECK(is_supported_datalink(DLT_EN10MB)); CHECK(is_supported_datalink(DLT_RAW)); CHECK_FALSE(is_supported_datalink(DLT_IEEE802_11)); } TEST_CASE("CaptureSession::open reports an error for a nonexistent device, without needing root") { CaptureSession session; CaptureSessionOptions options; options.device = "this-device-does-not-exist-0xdeadbeef"; auto err = session.open(options); REQUIRE(err.has_value()); CHECK_FALSE(err->empty()); } TEST_CASE("CaptureSession::stats returns nullopt before open()") { CaptureSession session; CHECK_FALSE(session.stats().has_value()); } TEST_CASE("CaptureSession::open replays a pcapng file without needing root or a live device") { TempPcapngFile file(DLT_EN10MB, {{0xDE, 0xAD}, {0xBE, 0xEF}}); CaptureSession session; CaptureSessionOptions options; options.replay_input_path = file.path; CHECK_FALSE(session.open(options).has_value()); CHECK(session.is_replay()); CHECK(session.datalink() == DLT_EN10MB); CHECK(session.device() == file.path); CHECK_FALSE(session.stats().has_value()); // pcap_stats() needs a live handle; replay has none } TEST_CASE("CaptureSession::open rejects combining -r (replay) with -f (capture filter)") { TempPcapngFile file(DLT_EN10MB, {{0x01}}); CaptureSession session; CaptureSessionOptions options; options.replay_input_path = file.path; options.filter_expr = "tcp"; auto err = session.open(options); REQUIRE(err.has_value()); CHECK(err->find("-r") != std::string::npos); } TEST_CASE("CaptureSession::open reports an error for a nonexistent replay file") { CaptureSession session; CaptureSessionOptions options; options.replay_input_path = "/tmp/this-file-does-not-exist-0xdeadbeef.pcapng"; auto err = session.open(options); REQUIRE(err.has_value()); CHECK_FALSE(err->empty()); } TEST_CASE("CaptureSession::open reports an error for a pcapng file with no packets") { TempPcapngFile file(DLT_EN10MB, {}); // just SHB + IDB, no EPBs CaptureSession session; CaptureSessionOptions options; options.replay_input_path = file.path; auto err = session.open(options); REQUIRE(err.has_value()); } TEST_CASE("replayed packets reach the CaptureQueue in order, and the thread stops on its own") { TempPcapngFile file(DLT_RAW, {{0x01, 0x02}, {0x03, 0x04}, {0x05, 0x06}}); CaptureSession session; CaptureSessionOptions options; options.replay_input_path = file.path; REQUIRE_FALSE(session.open(options).has_value()); CHECK(session.datalink() == DLT_RAW); CaptureQueue queue(4096); auto capture_thread = session.start_capture_thread(queue); std::vector first_bytes; int count = 0; while (auto packet = queue.pop()) { if (count == 0) first_bytes = packet->data; ++count; } capture_thread.join(); CHECK(count == 3); REQUIRE(first_bytes.size() == 2); CHECK(first_bytes[0] == 0x01); }