#include "capture_file_writer.h" #include "channel_bundle_support.hpp" #include "data_preprocessor.hpp" #include "locator/tcp_server.hpp" #include "object_approach_filter.hpp" #include "processing_live_config.hpp" #include "runtime_wait.hpp" #include "shm_ring.hpp" #include "tty_protocol_writer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { using namespace radar; using Json = nlohmann::json; void require(bool condition, const char* message) { if (!condition) { throw std::runtime_error(message); } } template void expect_error(Callable operation) { try { operation(); } catch (const std::bad_alloc&) { throw; // A malformed count must be rejected before allocating. } catch (const std::exception&) { return; } throw std::runtime_error("Expected operation to reject malformed input"); } struct Workspace { std::filesystem::path path; Workspace() { char pattern[] = "/tmp/radar-native-XXXXXX"; const auto* created = ::mkdtemp(pattern); require(created != nullptr, "mkdtemp failed"); path = created; } ~Workspace() { std::filesystem::remove_all(path); } }; struct RingName { std::string name; explicit RingName(const std::string& suffix = {}) : name("/radar_native_test_" + std::to_string(::getpid()) + suffix) { ::shm_unlink(name.c_str()); } ~RingName() { ::shm_unlink(name.c_str()); } }; auto save_bundle(const Workspace& workspace, const char* name, const ipc::RawSweepCollection& collection) -> std::string { const auto path = workspace.path / name; const auto bytes = ipc::serialize_raw_collection(collection); std::ofstream(path, std::ios::binary).write( reinterpret_cast(bytes.data()), static_cast(bytes.size())); return path.string(); } void write_u32(std::vector& bytes, std::size_t offset, std::uint32_t value) { require(offset + sizeof(value) <= bytes.size(), "invalid test offset"); std::memcpy(bytes.data() + offset, &value, sizeof(value)); } void test_wire_format() { ipc::RawSweepCollection raw{.collection_id = 7, .monotonic_ns = 42, .capture_start_ns = 21, .capture_end_ns = 40, .traces = {{.combo = {1, 2}, .frequency_hz = {1, 2}, .s21 = {{5, 6}, {7, 8}}, .capture_start_ns = 25, .capture_end_ns = 39}}}; ipc::ResultCollection results{.collection_id = 7, .monotonic_ns = 0x0102030405060708ULL, .processing_duration_ns = 17, .settings_revision = 0xfedcba9876543210ULL, .capture_start_ns = 21, .capture_end_ns = 40, .speed_m_s = -1.5F, .collection_payloads = { {.kind = ipc::ResultKind::TraceComplex, .frequency_hz = {1}, .trace = {{2, 3}}}, {.kind = ipc::ResultKind::ScalarF32, .scalar_value = 4}, {.kind = ipc::ResultKind::ImageF32, .image_x_axis = {1, 2}, .image_y_axis = {3}, .image_values = {4, 5}}, {.kind = ipc::ResultKind::TableF32, .table_columns = 2, .table_values = {6, 7}}, {.kind = ipc::ResultKind::ImageF32, .image_y_axis = {1}}, {.kind = ipc::ResultKind::TableF32, .table_columns = 3}}, .blocks = {{.combo = {1, 2}, .payloads = {{.kind = ipc::ResultKind::ScalarF32}}}}}; const auto raw_bytes = ipc::serialize_raw_collection(raw); const auto pre_bytes = ipc::serialize_preprocessed_collection(raw); const auto result_bytes = ipc::serialize_result_collection(results); const std::array result_header{ 'R', 'S', 'L', '3', 7, 0, 0, 0, 0, 0, 0, 0, 8, 7, 6, 5, 4, 3, 2, 1, 17, 0, 0, 0, 0, 0, 0, 0, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, 21, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xc0, 0xbf, 6, 0, 0, 0, 1, 0, 0, 0, }; require(std::equal(result_header.begin(), result_header.end(), result_bytes.begin()), "RSL3 must preserve exact acquisition identity and settings in its 64-byte header"); require(std::memcmp(raw_bytes.data(), "RAW3", 4) == 0 && std::memcmp(pre_bytes.data(), "PRP3", 4) == 0, "trace collections must use the S21-only version 3 format"); // Collection header + one trace header + two 12-byte points + capture trailer. require(raw_bytes.size() == 24 + 12 + 2 * 12 + 16 + 4 + 16 && pre_bytes.size() == raw_bytes.size(), "trace wire format must contain only frequency and S21 samples"); auto old_raw = raw_bytes; auto old_pre = pre_bytes; write_u32(old_raw, 0, 0x32574152U); write_u32(old_pre, 0, 0x32525050U); expect_error([&] { (void)ipc::deserialize_raw_collection(old_raw); }); expect_error([&] { (void)ipc::deserialize_preprocessed_collection(old_pre); }); require(ipc::serialize_raw_collection(ipc::deserialize_raw_collection(raw_bytes)) == raw_bytes, "raw roundtrip changed data"); require(ipc::serialize_preprocessed_collection(ipc::deserialize_preprocessed_collection(pre_bytes)) == pre_bytes, "preprocessed roundtrip changed data"); for (const std::size_t tail : {36U, 20U}) { expect_error([&] { (void)ipc::deserialize_raw_collection(std::span(raw_bytes).first(raw_bytes.size() - tail)); }); expect_error([&] { (void)ipc::deserialize_preprocessed_collection(std::span(pre_bytes).first(pre_bytes.size() - tail)); }); } // Deterministic random mutations include wire-compatible valid frames as seeds. std::mt19937 random(0x52414452U); for (unsigned iteration = 0; iteration < 10'000U; ++iteration) { auto bytes = iteration % 2U == 0U ? raw_bytes : pre_bytes; for (unsigned edit = 0; edit < 1U + random() % 5U; ++edit) { bytes[random() % bytes.size()] = static_cast(random()); } if (iteration % 4U == 0U) { bytes.resize(random() % bytes.size()); } try { if (iteration % 2U == 0U) { (void)ipc::deserialize_raw_collection(bytes); } else { (void)ipc::deserialize_preprocessed_collection(bytes); } } catch (const std::bad_alloc&) { throw; } catch (const std::runtime_error&) {} } } void test_configuration(const std::string& seed_path, const Workspace& workspace) { std::ifstream input(seed_path); const auto seed = Json::parse(input); const auto config_path = workspace.path / "config.json"; const auto save = [&](const Json& config) { std::ofstream(config_path) << config; }; save(seed); const auto config = config::load_run_config(config_path.string()); auto no_taps = seed; no_taps["rings"]["preprocessed_tap"]["name"] = ""; save(no_taps); const auto disabled = config::load_run_config(config_path.string()).rings; require(disabled.preprocessed_tap.name.empty(), "explicit empty tap names must disable the optional rings"); for (const auto* key : {"raw", "preprocessed", "results"}) { auto invalid = no_taps; invalid["rings"][key]["name"] = ""; save(invalid); expect_error([&] { (void)config::load_run_config(config_path.string()); }); } for (const auto* port : {"port1", "port2"}) { auto with_driver = seed; with_driver["switches"][port]["driver"] = "h7992"; save(with_driver); (void)config::load_run_config(config_path.string()); for (const auto& driver : {Json("hmc349a"), Json("unknown"), Json(7)}) { with_driver["switches"][port]["driver"] = driver; save(with_driver); expect_error([&] { (void)config::load_run_config(config_path.string()); }); } } auto with_object_calibration = seed; with_object_calibration["preprocess"]["s21"]["object_calibration"] = Json{{"set_name", "reflector"}, {"bundle_path", "corrections.bin"}}; save(with_object_calibration); const auto selected = config::load_run_config(config_path.string()).preprocess.s21.object_calibration; require(selected.bundle_path == "corrections.bin", "object calibration asset did not load"); with_object_calibration["preprocess"]["s21"].erase("object_calibration"); save(with_object_calibration); require(config::load_run_config(config_path.string()).preprocess.s21.object_calibration.bundle_path.empty(), "absent object calibration was not disabled"); for (const auto& invalid_asset : {Json(nullptr), Json{{"bundle_path", true}}}) { with_object_calibration["preprocess"]["s21"]["object_calibration"] = invalid_asset; save(with_object_calibration); expect_error([&] { (void)config::load_run_config(config_path.string()); }); } for (const auto& [pointer, value] : std::array, 3>{{ {"/run/combos/0/input", 0.0000001}, {"/rings/raw/slot_size_bytes", 65}, {"/gpr/rx_geometry/0/x_m", 1e100}}}) { auto invalid = seed; invalid[Json::json_pointer(pointer)] = value; save(invalid); expect_error([&] { (void)config::load_run_config(config_path.string()); }); } const auto live_path = workspace.path / "live.json"; std::ofstream(live_path) << Json{{"bscan_gain", 2}, {"settings_revision", 11}}; processing::ProcessingLiveConfigLoader loader(live_path.string(), config); require(loader.refresh_if_needed().bscan_gain == 2, "valid live config did not load"); const auto revision = loader.revision(); for (const auto& invalid : {Json{{"bscan_gain", 1e100}}, Json{{"gpr_input_positions", {0.0000001}}}, Json{{"bscan_gain", true}}}) { std::ofstream(live_path) << invalid; std::this_thread::sleep_for(std::chrono::milliseconds(110)); const auto current = loader.refresh_if_needed(); require(current.bscan_gain == 2 && loader.revision() == revision, "invalid live config replaced the last valid settings"); } } void test_ring() { RingName name; expect_error([&] { (void)ipc::ShmRing::open_or_create(name.name, 2, 65); }); expect_error([&] { (void)ipc::ShmRing::open_or_create(name.name, 0xffffffffU, 0xfffffff8U); }); auto ring = ipc::ShmRing::open_or_create(name.name, 2, 4096); const auto child = ::fork(); require(child >= 0, "fork failed"); if (child == 0) { std::array words{}; for (std::uint64_t sequence = 1; sequence <= 20'000U; ++sequence) { words.fill(sequence); if (!ring.push({reinterpret_cast(words.data()), sizeof(words)})) { ::_exit(2); } } ::_exit(0); } std::uint64_t previous = 0; std::vector payload; int status = 0; bool finished = false; const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); try { while (true) { if (ring.pop(payload)) { require(payload.size() == 4096, "stress payload changed size"); std::uint64_t sequence = 0; std::memcpy(&sequence, payload.data(), sizeof(sequence)); require(sequence > previous, "stress ring yielded duplicate or older data"); for (std::size_t offset = 0; offset < payload.size(); offset += sizeof(sequence)) { std::uint64_t word = 0; std::memcpy(&word, payload.data() + offset, sizeof(word)); require(word == sequence, "stress ring accepted a torn payload"); } previous = sequence; } else if (finished) { break; } finished = finished || ::waitpid(child, &status, WNOHANG) == child; require(std::chrono::steady_clock::now() < deadline, "ring stress timed out"); } require(WIFEXITED(status) && WEXITSTATUS(status) == 0 && previous != 0, "ring stress writer failed"); } catch (...) { ::kill(child, SIGKILL); if (!finished) { (void)::waitpid(child, &status, 0); } throw; } } void test_acquisition_identity() { processing::ObjectApproachFilter filter; const std::vector points{{0, 2}}; require(!filter.confirm(points, 1, 1'000'000'000ULL, 0, 0, 2).front(), "first frame confirmed a track"); require(filter.confirm(points, 1, 2'000'000'000ULL, 0, 0, 2).front(), "reused collection id replaced a distinct acquisition"); require(filter.confirm(points, 1, 2'000'000'000ULL, 0, 0, 2).front(), "reprocessing duplicated a frame or lost its predecessor"); require(!filter.confirm(points, 7, 1'000'000'000ULL, 0, 0, 2).front(), "an earlier capture time failed to reset replay history"); } void test_interruptible_wait() { std::atomic stop{false}; std::jthread request_stop([&] { std::this_thread::sleep_for(std::chrono::milliseconds(10)); stop.store(true); }); const auto start = std::chrono::steady_clock::now(); require(config::wait_or_stop(0xffffffffU, stop), "large wait ignored stop"); require(std::chrono::steady_clock::now() - start < std::chrono::seconds(1), "large wait delayed shutdown"); } void test_cosine_notch_taper() { // Band 2.0-2.1 GHz with 0.1 GHz shoulders: 1 at the outer edges, 0.5 halfway, // 0 from the band edges inwards. const std::vector frequency_hz{1.85e9F, 1.9e9F, 1.95e9F, 2.0e9F, 2.05e9F, 2.1e9F, 2.15e9F, 2.2e9F, 2.25e9F}; const std::vector expected{1.0F, 1.0F, 0.5F, 0.0F, 0.0F, 0.0F, 0.5F, 1.0F, 1.0F}; const auto mask = preprocessing::build_notch_mask(frequency_hz, {.enabled = true, .bands_hz = {{2.0e9F, 2.1e9F}}, .taper_width_hz = 1e8F, .taper_type = "cosine"}); for (std::size_t index = 0U; index < expected.size(); ++index) { require(std::abs(mask[index] - expected[index]) < 1e-3F, "cosine notch shoulders are not 1 -> 0 towards the band"); } } void test_csv_failures(const Workspace& workspace) { const auto csv = workspace.path / "capture.csv"; const auto spool = workspace.path / "capture.capture_spool.bin"; CaptureFileWriter writer(csv.string(), (workspace.path / "capture.svg").string(), "", ""); writer.initialize_csv(2, false); CapturePacket packet{.ch1 = {1, 2}, .ch2 = {3, 4}, .di1 = {}}; auto invalid_packet = packet; invalid_packet.ch2.pop_back(); expect_error([&] { writer.append_csv_packet(invalid_packet); }); require(std::filesystem::file_size(spool) == 16, "invalid packet partially changed the spool"); writer.append_csv_packet(packet); std::filesystem::remove(csv); std::filesystem::create_symlink("/dev/full", csv); expect_error([&] { writer.write({packet}, 1000, 10); }); require(std::filesystem::file_size(spool) > 16, "failed CSV write deleted the recovery spool"); std::filesystem::remove(csv); writer.write({packet}, 1000, 10); require(!std::filesystem::exists(spool), "successful CSV export retained the spool"); std::ifstream csv_input(csv); const std::string csv_text((std::istreambuf_iterator(csv_input)), {}); require(csv_text.find("1.000000000,3.000000000") != std::string::npos, "CSV retry lost channel samples"); writer.initialize_csv(2, false); std::ofstream(spool, std::ios::binary | std::ios::app).put('x'); expect_error([&] { writer.write({}, 1000, 10); }); require(std::filesystem::exists(spool), "truncated packet header deleted the spool"); writer.initialize_csv(2, false); const std::array corrupt_packet{0, 2, 0xffffffffffffffffULL, 0}; std::ofstream(spool, std::ios::binary | std::ios::app).write( reinterpret_cast(corrupt_packet.data()), sizeof(corrupt_packet)); expect_error([&] { writer.write({}, 1000, 10); }); require(std::filesystem::exists(spool), "oversized packet count deleted the spool"); } void test_asset_reload_preserves_data(const Workspace& workspace) { const auto save = [&](const char* name, float sample) { return save_bundle(workspace, name, {.traces = { {.frequency_hz = {1}, .s21 = {{sample, 0}}}}}); }; const auto open = save("open.bin", 1); const auto nan = save("nan.bin", std::numeric_limits::quiet_NaN()); preprocessing::S21CalibrationBundle s21_calibration; s21_calibration.load(save("through.bin", 2)); expect_error([&] { s21_calibration.load(nan); }); const std::array frequency{1}; std::array samples{{{6, 8}}}; s21_calibration.apply({}, frequency, samples); require(samples[0].re == 3 && samples[0].im == 4, "failed S21 reload changed previous calibration"); const std::array nan_axis{std::numeric_limits::quiet_NaN()}; expect_error([&] { s21_calibration.apply({}, nan_axis, samples); }); preprocessing::S21ReferenceBundle reference; reference.load(open, s21_calibration, {{}}); expect_error([&] { reference.load(nan, s21_calibration, {{}}); }); ipc::SweepTraceBlock trace{.frequency_hz = {1}, .s21 = {{2, 0}}}; reference.apply(trace); require(trace.s21[0].re == 1.5F, "failed reference reload changed the calibrated reference"); } void test_object_calibration(const Workspace& workspace) { const ipc::SweepTraceBlock first{.combo = {7, 3}, .frequency_hz = {1e9F, 2e9F}, .s21 = {{2, 3}, {4, 5}}, .capture_start_ns = 5, .capture_end_ns = 15}; const ipc::SweepTraceBlock second{.combo = {1, 9}, .frequency_hz = {3e9F, 4e9F, 5e9F}, .s21 = {{6, 7}, {8, 9}, {10, 11}}}; auto factors_first = first; factors_first.s21 = {{0, 2}, {2, 0}}; auto factors_second = second; factors_second.s21 = {{1, -1}, {-1, 0}, {0, 1}}; preprocessing::S21ObjectCalibrationBundle correction; auto trace = first; correction.apply(trace); require(ipc::serialize_raw_collection({.traces = {trace}}) == ipc::serialize_raw_collection({.traces = {first}}), "disabled object calibration changed a trace"); correction.load(save_bundle(workspace, "object.bin", {.traces = {factors_second, factors_first}})); correction.validate_combos({first.combo, second.combo}); expect_error([&] { correction.validate_combos({{0, 0}}); }); for (auto invalid : {first, second}) { invalid.combo = {0, 0}; expect_error([&] { correction.apply(invalid); }); } auto mismatch = second; mismatch.frequency_hz[1] += 1e8F; // Same endpoints and count; interior grid differs. expect_error([&] { correction.apply(mismatch); }); mismatch = second; mismatch.s21.pop_back(); expect_error([&] { correction.apply(mismatch); }); auto nonfinite = factors_first; nonfinite.s21[0].re = std::numeric_limits::quiet_NaN(); expect_error([&] { correction.load(save_bundle(workspace, "object_nan.bin", {.traces = {nonfinite}})); }); expect_error([&] { correction.load(save_bundle(workspace, "object_duplicate.bin", {.traces = {factors_first, factors_first}})); }); auto expected = first; expected.s21 = {{-6, 4}, {8, 10}}; trace = first; correction.apply(trace); require(ipc::serialize_raw_collection({.traces = {trace}}) == ipc::serialize_raw_collection({.traces = {expected}}), "object calibration changed timing, pair identity, or multiplication after a failed reload"); trace = second; correction.apply(trace); require(trace.s21[0].re == 13 && trace.s21[0].im == 1 && trace.s21[1].re == -8 && trace.s21[1].im == -9 && trace.s21[2].re == -11 && trace.s21[2].im == 10, "object calibration mixed pairs or frequency grids"); trace = first; trace.s21[0] = {std::numeric_limits::max(), std::numeric_limits::max()}; expect_error([&] { correction.apply(trace); }); correction.load(""); trace = first; correction.apply(trace); require(trace.s21[0].re == 2 && trace.s21[0].im == 3, "clearing object calibration left stale factors"); } void test_object_calibration_pipeline(const Workspace& workspace) { const ipc::SweepTraceBlock trace{.combo = {7, 3}, .frequency_hz = {1e9F, 2e9F}, .s21 = {{2, 4}, {6, 8}}, .capture_start_ns = 5, .capture_end_ns = 15}; auto standard = trace; standard.s21 = {{2, 0}, {2, 0}}; auto reference = trace; reference.s21 = {{1, 2}, {2, 2}}; auto factors = trace; factors.s21 = {{0, 2}, {5, 1}}; config::RunConfig config; config.preprocess.s21.calibration.bundle_path = save_bundle(workspace, "pipeline_through.bin", {.traces = {standard}}); config.preprocess.s21.reference.bundle_path = save_bundle(workspace, "pipeline_reference.bin", {.traces = {reference}}); config.run_combos = {trace.combo}; config.preprocess.s21.object_calibration.bundle_path = save_bundle(workspace, "pipeline_object.bin", {.traces = {factors}}); config.preprocess.notch = {.enabled = true, .bands_hz = {{2e9F, 2e9F}}, .taper_type = "hard"}; RingName raw_name("_object_raw"), pre_name("_object_pre"); auto raw_ring = ipc::ShmRing::open_or_create(raw_name.name, 2, 4096); auto pre_ring = ipc::ShmRing::open_or_create(pre_name.name, 2, 4096); preprocessing::DataPreprocessor preprocessor(config, raw_ring, pre_ring); require(raw_ring.push(ipc::serialize_raw_collection({.collection_id = 41, .capture_start_ns = 5, .capture_end_ns = 15, .traces = {trace}})), "test raw ring could not hold a sweep"); std::atomic stop{false}; std::exception_ptr error; std::jthread worker([&] { try { preprocessor.run(stop); } catch (...) { error = std::current_exception(); } }); std::vector bytes; const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); while (bytes.empty() && std::chrono::steady_clock::now() < deadline) { (void)pre_ring.pop(bytes); std::this_thread::sleep_for(std::chrono::milliseconds(1)); } stop.store(true); worker.join(); if (error) { std::rethrow_exception(error); } require(!bytes.empty(), "object-corrected preprocessed collection was not published"); const auto result = ipc::deserialize_preprocessed_collection(bytes); const auto& output = result.traces.at(0); require(result.collection_id == 41 && result.capture_start_ns == 5 && result.capture_end_ns == 15 && output.combo == trace.combo && output.capture_start_ns == 5 && output.capture_end_ns == 15, "object calibration lost capture identity or timing"); require(output.s21[0].re == -2 && output.s21[0].im == 1 && output.s21[1].re == 0 && output.s21[1].im == 0, "object calibration ran before existing preprocessing or was applied more than once"); } void test_locator_packet_errors() { int sockets[2]{}; require(::socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0, "socketpair failed"); std::atomic speed{0}; std::atomic at_ns{0}; locator::ClientSession client(sockets[0], "regression", 2, 1024); client.start(speed, at_ns); const auto send_json = [&](const std::string& json) { std::vector bytes(8 + json.size()); write_u32(bytes, 4, static_cast(json.size())); std::memcpy(bytes.data() + 8, json.data(), json.size()); const auto sent = ::send(sockets[1], bytes.data(), bytes.size(), MSG_NOSIGNAL); if (sent != static_cast(bytes.size())) { throw std::runtime_error(std::string("send test packet failed: ") + std::strerror(errno)); } }; send_json("{\"vlc\":1e400}"); send_json("{\"vlc\":1e308}"); std::this_thread::sleep_for(std::chrono::milliseconds(50)); require(at_ns.load() == 0 && speed.load() == 0, "invalid socket speed reached the processor"); send_json("{\"vlc\":3.5}"); const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); while (at_ns.load() == 0 && std::chrono::steady_clock::now() < deadline) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } require(speed.load() == 3.5 && at_ns.load() > 0, "locator stopped after oversized JSON number"); client.request_stop(); client.join(); ::close(sockets[1]); require(client.has_exited(), "locator session did not finish both loops"); locator::LocatorServerConfig config{.host = "127.0.0.1", .port = 0}; locator::TcpServer server(config); for (unsigned iteration = 0; iteration < 30; ++iteration) { server.start(); server.stop(); } } void test_unread_tty_shutdown(const Workspace& workspace) { TtyProtocolWriter writer((workspace.path / "tty").string(), 160'000); const std::vector words(80'000, 42); writer.enqueue_encoded_frames(words.data(), words.size() / 4U); std::this_thread::sleep_for(std::chrono::milliseconds(50)); const auto start = std::chrono::steady_clock::now(); writer.shutdown(); require(std::chrono::steady_clock::now() - start < std::chrono::seconds(1), "unread tty blocked shutdown"); writer.shutdown(); } } // namespace int main(int argc, char** argv) { try { require(argc == 2, "Pass the simulator config fixture path"); Workspace workspace; test_wire_format(); test_configuration(argv[1], workspace); test_asset_reload_preserves_data(workspace); test_object_calibration(workspace); test_object_calibration_pipeline(workspace); test_ring(); test_acquisition_identity(); test_interruptible_wait(); test_cosine_notch_taper(); test_csv_failures(workspace); test_locator_packet_errors(); test_unread_tty_shutdown(workspace); std::cout << "native regressions passed (10,000 decoder mutations; 20,000 concurrent ring frames)\n"; } catch (const std::exception& error) { std::cerr << "native regression failed: " << error.what() << '\n'; return 1; } }