#include "init.h" #include #include #include #define SATDUMP_DLL_EXPORT 1 #include "common/utils.h" #include "core/config.h" #include "core/plugin.h" #include "logger.h" #include "nlohmann/json_utils.h" #include "satdump_vars.h" #include "tle.h" #include "utils/http.h" #include #include #include #include #include namespace satdump { int parseTLEStream(std::istream &inputStream, std::vector &new_registry) { std::string this_line; std::deque tle_lines; int total_lines = 0; while (std::getline(inputStream, this_line)) { this_line.erase(0, this_line.find_first_not_of(" \t\n\r")); this_line.erase(this_line.find_last_not_of(" \t\n\r") + 1); if (this_line != "") tle_lines.push_back(this_line); if (tle_lines.size() == 3) { bool checksum_good = true; for (int i = 1; i < 3; i++) { if (!isdigit(tle_lines[i].back()) || !isdigit(tle_lines[i].front()) || tle_lines[i].front() - '0' != i) { checksum_good = false; break; } int checksum = tle_lines[i].back() - '0'; int actualsum = 0; for (int j = 0; j < (int)tle_lines[i].size() - 1; j++) { if (tle_lines[i][j] == '-') actualsum++; else if (isdigit(tle_lines[i][j])) actualsum += tle_lines[i][j] - '0'; } checksum_good = checksum == actualsum % 10; if (!checksum_good) break; } if (!checksum_good) { tle_lines.pop_front(); continue; } int norad; try { std::string noradstr = tle_lines[2].substr(2, tle_lines[2].substr(2, tle_lines[2].size() - 1).find(' ')); norad = std::stoi(noradstr); } catch (std::exception &) { tle_lines.pop_front(); continue; } new_registry.push_back({norad, tle_lines[0], tle_lines[1], tle_lines[2]}); tle_lines.clear(); total_lines++; } } // Sort and remove duplicates sort(new_registry.begin(), new_registry.end(), [](TLE &a, TLE &b) { return a.name < b.name; }); new_registry.erase(std::unique(new_registry.begin(), new_registry.end(), [](TLE &a, TLE &b) { return a.norad == b.norad; }), new_registry.end()); return total_lines; } std::vector tryFetchTLEsFromFileURL(std::string url_str) { bool success = true; std::vector new_registry; logger->info(url_str); std::string result; int http_res = 1, trials = 0; while (http_res == 1 && trials < 10) { if ((http_res = satdump::perform_http_request(url_str, result)) != 1) { std::istringstream tle_stream(result); success = parseTLEStream(tle_stream, new_registry) > 0; } else success = false; trials++; if (!success) { std::this_thread::sleep_for(std::chrono::seconds(1)); logger->info("Failed getting TLEs. Retrying..."); } } if (!success) logger->warn("Failed to get TLE for %s", url_str.c_str()); return new_registry; } std::vector tryFetchSingleTLEwithNorad(int norad) { bool success = true; std::vector new_registry; std::string url_str = satdump_cfg.main_cfg["tle_settings"]["url_template"].get(); while (url_str.find("%NORAD%") != std::string::npos) url_str.replace(url_str.find("%NORAD%"), 7, std::to_string(norad)); logger->info(url_str); std::string result; int http_res = 1, trials = 0; while (http_res == 1 && trials < 10) { if ((http_res = perform_http_request(url_str, result)) != 1) { std::istringstream tle_stream(result); success = parseTLEStream(tle_stream, new_registry) > 0; } else success = false; trials++; if (!success) { std::this_thread::sleep_for(std::chrono::seconds(1)); logger->info("Failed getting TLEs. Retrying..."); } } if (!success) logger->error("Error updating TLE for %d. Ignoring!", norad); return new_registry; } std::optional get_from_spacetrack_time(int norad, time_t timestamp) { time_t last_update = std::stoull(db->get_meta("tles_last_updated", "0")); std::string sc_login = satdump_cfg.getValueFromSatDumpGeneral("tle_space_track_login"); std::string sc_passw = satdump_cfg.getValueFromSatDumpGeneral("tle_space_track_password"); // Use local time if Space Track credentials are not entered, or time is close to TLE catalog time if (sc_login == "" || sc_passw == "" || sc_login == "yourloginemail" || sc_passw == "yourpassword" || fabs((double)last_update - (double)timestamp) < 4 * 24 * 3600) return std::optional(); // Otherwise, request on Space-Track's archive logger->trace("Pulling historical TLE from Space Track..."); CURL *curl; CURLcode res; curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); if (!curl) { logger->warn("Failed to pull historical TLE due to internal curl failure! Using current TLE"); curl_global_cleanup(); return std::optional(); } // Get the cookie std::string post_fields = "identity=" + sc_login + "&password=" + sc_passw; curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); curl_easy_setopt(curl, CURLOPT_USERAGENT, std::string((std::string) "SatDump/v" + SATDUMP_VERSION).c_str()); curl_easy_setopt(curl, CURLOPT_URL, "https://www.space-track.org/ajaxauth/login"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields.c_str()); #ifdef CURLSSLOPT_NATIVE_CA curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); #endif res = curl_easy_perform(curl); if (res != CURLE_OK) { logger->warn("Failed to authenticate to Space Track! Using current TLE"); curl_easy_cleanup(curl); curl_global_cleanup(); return std::optional(); } // Get the actual TLE std::string timestamp_day, timestamp_daytime; { if (timestamp < 0) timestamp = 0; time_t tttime = timestamp; std::tm *timeReadable = gmtime(&tttime); timestamp_day = std::to_string(timeReadable->tm_year + 1900) + "-" + (timeReadable->tm_mon + 1 > 9 ? std::to_string(timeReadable->tm_mon + 1) : "0" + std::to_string(timeReadable->tm_mon + 1)) + "-" + (timeReadable->tm_mday > 9 ? std::to_string(timeReadable->tm_mday) : "0"); timestamp_daytime = (timeReadable->tm_hour > 9 ? std::to_string(timeReadable->tm_hour) : "0" + std::to_string(timeReadable->tm_hour)) + "%3A" + (timeReadable->tm_min > 9 ? std::to_string(timeReadable->tm_min) : "0" + std::to_string(timeReadable->tm_min)) + "%3A" + (timeReadable->tm_sec > 9 ? std::to_string(timeReadable->tm_sec) : "0" + std::to_string(timeReadable->tm_sec)); } std::string final_url = "https://www.space-track.org/basicspacedata/query/class/gp_history/NORAD_CAT_ID/" + std::to_string(norad) + "/EPOCH/%3C" + timestamp_day + "T" + timestamp_daytime + "/orderby/EPOCH%20desc/limit/1/emptyresult/show"; std::string result; curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(curl, CURLOPT_POST, 0); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, satdump::curl_write_std_string); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result); curl_easy_setopt(curl, CURLOPT_URL, final_url.c_str()); logger->trace("Request URL : %s", final_url.c_str()); res = curl_easy_perform(curl); if (res != CURLE_OK) { logger->warn("Failed to download TLE from Space Track! Using built-in TLE"); curl_easy_cleanup(curl); curl_global_cleanup(); return std::optional(); } // Log out and clean up std::string logout_result; curl_easy_setopt(curl, CURLOPT_WRITEDATA, &logout_result); curl_easy_setopt(curl, CURLOPT_URL, "https://www.space-track.org/ajaxauth/logout"); curl_easy_perform(curl); // We do not care about the result curl_easy_cleanup(curl); curl_global_cleanup(); // Parse the downloaded TLE try { bool parsed = true; nlohmann::json res; try { res = nlohmann::json::parse(result)[0]; } catch (std::exception &) { parsed = false; } if (!parsed || !res.contains("TLE_LINE0") || !res.contains("TLE_LINE1") || !res.contains("TLE_LINE2")) { logger->warn("Error pulling TLE from Space-Track! Returned data: %s", result.c_str()); return std::optional(); } TLE tle; tle.norad = norad; tle.name = res["TLE_LINE0"].get().substr(2, res["TLE_LINE0"].get().size()); tle.line1 = res["TLE_LINE1"].get(); tle.line2 = res["TLE_LINE2"].get(); tle.time = timestamp; // TODOREWORK actual time!!!! return tle; } catch (std::exception &e) { logger->error("Could not get TLE from Space-Track : %s", e.what()); return std::optional(); } return std::optional(); } std::vector get_from_spacetrack_latest_list(std::vector norad) { time_t last_update = std::stoull(db->get_meta("tles_last_updated", "0")); std::string sc_login = satdump_cfg.getValueFromSatDumpGeneral("tle_space_track_login"); std::string sc_passw = satdump_cfg.getValueFromSatDumpGeneral("tle_space_track_password"); // Otherwise, request on Space-Track's archive logger->warn("Pulling current TLEs from Space Track... (ONLY COVERS OBJECTS CURRENTLY IN DATABASE!)"); CURL *curl; CURLcode res; curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); if (!curl) { logger->warn("Failed to pull current TLEs due to internal curl failure! Using current TLE"); curl_global_cleanup(); return std::vector(); } // Get the cookie std::string post_fields = "identity=" + sc_login + "&password=" + sc_passw; curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); curl_easy_setopt(curl, CURLOPT_USERAGENT, std::string((std::string) "SatDump/v" + SATDUMP_VERSION).c_str()); curl_easy_setopt(curl, CURLOPT_URL, "https://www.space-track.org/ajaxauth/login"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields.c_str()); #ifdef CURLSSLOPT_NATIVE_CA curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); #endif res = curl_easy_perform(curl); if (res != CURLE_OK) { logger->warn("Failed to authenticate to Space Track! Using current TLE"); curl_easy_cleanup(curl); curl_global_cleanup(); return std::vector(); } std::string final_url = "https://www.space-track.org/basicspacedata/query/class/gp/NORAD_CAT_ID/"; for (int i = 0; i < norad.size() - 1; i++) final_url += std::to_string(norad[i]) + "%2C"; final_url += std::to_string(norad[norad.size() - 1]); final_url += "/orderby/NORAD_CAT_ID%20desc/emptyresult/show"; std::string result; curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(curl, CURLOPT_POST, 0); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, satdump::curl_write_std_string); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result); curl_easy_setopt(curl, CURLOPT_URL, final_url.c_str()); logger->trace("Request URL : %s", final_url.c_str()); res = curl_easy_perform(curl); if (res != CURLE_OK) { logger->warn("Failed to download TLE from Space Track! Using built-in TLE"); curl_easy_cleanup(curl); curl_global_cleanup(); return std::vector(); } // Log out and clean up std::string logout_result; curl_easy_setopt(curl, CURLOPT_WRITEDATA, &logout_result); curl_easy_setopt(curl, CURLOPT_URL, "https://www.space-track.org/ajaxauth/logout"); curl_easy_perform(curl); // We do not care about the result curl_easy_cleanup(curl); curl_global_cleanup(); // Parse the downloaded TLE try { bool parsed = true; nlohmann::json res; try { res = nlohmann::json::parse(result); } catch (std::exception &) { parsed = false; } std::vector tles; for (int g = 0; g < res.size(); g++) { auto &f = res[g]; TLE tle; tle.norad = std::stoi(f["NORAD_CAT_ID"].get()); tle.name = f["TLE_LINE0"].get().substr(2, f["TLE_LINE0"].get().size()); tle.line1 = f["TLE_LINE1"].get(); tle.line2 = f["TLE_LINE2"].get(); tles.push_back(tle); } return tles; } catch (std::exception &e) { logger->error("Could not get current TLEs from Space-Track : %s", e.what()); return std::vector(); } return std::vector(); } std::optional get_from_norad_in_vec(std::vector vec, int norad) { std::vector::iterator it = std::find_if(vec.begin(), vec.end(), [&norad](const TLE &e) { return e.norad == norad; }); if (it != vec.end()) return std::optional(*it); else return std::optional(); } } // namespace satdump