LCOV - code coverage report
Current view: top level - io - RestApiProvider.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 77.4 % 115 89
Test Date: 2026-03-18 19:01:10 Functions: 92.3 % 13 12
Branches: 42.0 % 162 68

             Branch data     Line data    Source code
       1                 :             : #include "RestApiProvider.h"
       2                 :             : #include "Logger.h"
       3                 :             : #include <iostream>
       4                 :             : #include <regex>
       5                 :             : 
       6                 :             : #ifdef _WIN32
       7                 :             : #include <windows.h>
       8                 :             : #include <wininet.h>
       9                 :             : #pragma comment(lib, "wininet.lib")
      10                 :             : #endif
      11                 :             : 
      12                 :         655 : RestApiProvider& RestApiProvider::Get() {
      13   [ +  +  +  -  :         655 :     static RestApiProvider instance;
             +  -  -  - ]
      14                 :         655 :     return instance;
      15                 :             : }
      16                 :             : 
      17                 :           1 : RestApiProvider::~RestApiProvider() {
      18                 :           1 :     std::lock_guard<std::mutex> lock(m_threadMutex);
      19         [ +  - ]:           1 :     if (m_requestThread.joinable()) {
      20                 :           1 :         m_requestThread.join();
      21                 :             :     }
      22                 :           1 : }
      23                 :             : 
      24                 :           4 : void RestApiProvider::RequestSteeringRange(int port) {
      25         [ +  + ]:           4 :     if (m_isRequesting.load()) return;
      26                 :             : 
      27         [ +  - ]:           3 :     std::lock_guard<std::mutex> lock(m_threadMutex);
      28         [ +  - ]:           3 :     if (m_requestThread.joinable()) {
      29         [ +  - ]:           3 :         m_requestThread.join();
      30                 :             :     }
      31                 :             : 
      32                 :           3 :     m_isRequesting = true;
      33         [ +  - ]:           6 :     m_requestThread = std::thread([this, port]() {
      34                 :             :         try {
      35         [ +  - ]:           3 :             this->PerformRequest(port);
      36                 :           0 :         } catch (...) {
      37   [ -  -  -  - ]:           0 :             Logger::Get().LogFile("RestApiProvider: Unexpected exception in request thread");
      38                 :           0 :         }
      39                 :           3 :         this->m_isRequesting = false;
      40                 :           6 :     });
      41                 :           3 : }
      42                 :             : 
      43                 :         300 : float RestApiProvider::GetFallbackRangeDeg() const {
      44                 :         300 :     return m_fallbackRangeDeg.load();
      45                 :             : }
      46                 :             : 
      47                 :           0 : bool RestApiProvider::IsRequesting() const {
      48                 :           0 :     return m_isRequesting.load();
      49                 :             : }
      50                 :             : 
      51                 :         115 : void RestApiProvider::RequestManufacturer(int port, const std::string& vehicleName) {
      52         [ +  + ]:         115 :     if (m_isRequesting.load()) return;
      53                 :             : 
      54         [ +  - ]:         107 :     std::lock_guard<std::mutex> lock(m_threadMutex);
      55         [ +  + ]:         107 :     if (m_requestThread.joinable()) {
      56         [ +  - ]:         106 :         m_requestThread.join();
      57                 :             :     }
      58                 :             : 
      59                 :         107 :     m_isRequesting = true;
      60   [ +  -  +  - ]:         214 :     m_requestThread = std::thread([this, port, vehicleName]() {
      61                 :             :         try {
      62   [ +  -  +  - ]:         107 :             this->PerformManufacturerRequest(port, vehicleName);
      63                 :           0 :         } catch (...) {
      64   [ -  -  -  - ]:           0 :             Logger::Get().LogFile("RestApiProvider: Unexpected exception in manufacturer request thread");
      65                 :           0 :         }
      66                 :         107 :         this->m_isRequesting = false;
      67                 :         214 :     });
      68                 :         107 : }
      69                 :             : 
      70                 :           2 : std::string RestApiProvider::GetManufacturer() const {
      71         [ +  - ]:           2 :     std::lock_guard<std::mutex> lock(m_manufacturerMutex);
      72         [ +  - ]:           4 :     return m_manufacturer;
      73                 :           2 : }
      74                 :             : 
      75                 :           3 : void RestApiProvider::PerformRequest(int port) {
      76                 :           3 :     std::string response;
      77                 :           3 :     bool success = false;
      78                 :             : 
      79                 :             : #ifdef _WIN32
      80                 :             :     HINTERNET hInternet = InternetOpenA("lmuFFB", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
      81                 :             :     if (hInternet) {
      82                 :             :         std::string url = "http://localhost:" + std::to_string(port) + "/rest/garage/getPlayerGarageData";
      83                 :             :         HINTERNET hConnect = InternetOpenUrlA(hInternet, url.c_str(), NULL, 0, INTERNET_FLAG_RELOAD, 0);
      84                 :             :         if (hConnect) {
      85                 :             :             char buffer[4096];
      86                 :             :             DWORD bytesRead;
      87                 :             :             while (InternetReadFile(hConnect, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0) {
      88                 :             :                 response.append(buffer, bytesRead);
      89                 :             :             }
      90                 :             :             InternetCloseHandle(hConnect);
      91                 :             :             success = true;
      92                 :             :         } else {
      93                 :             :             Logger::Get().LogFile("RestApiProvider: Failed to open URL (Port %d)", port);
      94                 :             :         }
      95                 :             :         InternetCloseHandle(hInternet);
      96                 :             :     } else {
      97                 :             :         Logger::Get().LogFile("RestApiProvider: Failed to initialize WinINet");
      98                 :             :     }
      99                 :             : #else
     100   [ +  -  +  - ]:           3 :     Logger::Get().LogFile("RestApiProvider: Mock request on Linux (Port %d)", port);
     101                 :             : #endif
     102                 :             : 
     103   [ -  +  -  -  :           3 :     if (success && !response.empty()) {
                   -  + ]
     104         [ #  # ]:           0 :         float range = ParseSteeringLock(response);
     105         [ #  # ]:           0 :         if (range > 0.0f) {
     106                 :           0 :             m_fallbackRangeDeg = range;
     107   [ #  #  #  # ]:           0 :             Logger::Get().LogFile("RestApiProvider: Retrieved steering range: %.1f deg", range);
     108                 :             :         } else {
     109   [ #  #  #  # ]:           0 :             Logger::Get().LogFile("RestApiProvider: Could not parse VM_STEER_LOCK from response");
     110                 :             :         }
     111                 :             :     }
     112                 :           3 : }
     113                 :             : 
     114                 :         107 : void RestApiProvider::PerformManufacturerRequest(int port, std::string vehicleName) {
     115                 :         107 :     std::string response;
     116                 :         107 :     bool success = false;
     117                 :             : 
     118                 :             : #ifdef _WIN32
     119                 :             :     HINTERNET hInternet = InternetOpenA("lmuFFB", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
     120                 :             :     if (hInternet) {
     121                 :             :         std::string url = "http://localhost:" + std::to_string(port) + "/rest/race/car";
     122                 :             :         HINTERNET hConnect = InternetOpenUrlA(hInternet, url.c_str(), NULL, 0, INTERNET_FLAG_RELOAD, 0);
     123                 :             :         if (hConnect) {
     124                 :             :             char buffer[8192];
     125                 :             :             DWORD bytesRead;
     126                 :             :             while (InternetReadFile(hConnect, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0) {
     127                 :             :                 response.append(buffer, bytesRead);
     128                 :             :             }
     129                 :             :             InternetCloseHandle(hConnect);
     130                 :             :             success = true;
     131                 :             :         } else {
     132                 :             :             Logger::Get().LogFile("RestApiProvider: Failed to open URL (Port %d)", port);
     133                 :             :         }
     134                 :             :         InternetCloseHandle(hInternet);
     135                 :             :     } else {
     136                 :             :         Logger::Get().LogFile("RestApiProvider: Failed to initialize WinINet");
     137                 :             :     }
     138                 :             : #else
     139   [ +  -  +  - ]:         107 :     Logger::Get().LogFile("RestApiProvider: Mock manufacturer request on Linux (Port %d)", port);
     140                 :             : #endif
     141                 :             : 
     142   [ -  +  -  -  :         107 :     if (success && !response.empty()) {
                   -  + ]
     143                 :             :         // Log the full response for diagnostics
     144   [ #  #  #  # ]:           0 :         Logger::Get().LogFile("RestApiProvider: Full REST API response for car info: %s", response.c_str());
     145                 :             : 
     146         [ #  # ]:           0 :         std::string manufacturer = ParseManufacturer(response, vehicleName);
     147                 :             : 
     148         [ #  # ]:           0 :         std::lock_guard<std::mutex> lock(m_manufacturerMutex);
     149         [ #  # ]:           0 :         m_manufacturer = manufacturer;
     150                 :           0 :         m_hasManufacturer = true;
     151   [ #  #  #  # ]:           0 :         Logger::Get().LogFile("RestApiProvider: Identified manufacturer from REST API: %s", m_manufacturer.c_str());
     152                 :           0 :     }
     153                 :         107 : }
     154                 :             : 
     155                 :           4 : std::string RestApiProvider::ParseManufacturer(const std::string& json, const std::string& vehicleName) {
     156                 :             :     // LMU JSON contains "desc" and "manufacturer"
     157                 :             :     // Match "desc": "vehicleName" and extract "manufacturer"
     158                 :             : 
     159   [ +  -  +  - ]:           4 :     size_t descPos = json.find("\"desc\":\"" + vehicleName + "\"");
     160         [ +  - ]:           4 :     if (descPos == std::string::npos) {
     161                 :             :         // Try loose match if exact match fails (shared memory might have extra spaces or truncated)
     162                 :             :         // This is a heuristic.
     163                 :           4 :         descPos = json.find("\"desc\"");
     164         [ +  + ]:          10 :         while (descPos != std::string::npos) {
     165                 :           9 :             size_t startQuote = json.find('\"', descPos + 6);
     166                 :           9 :             size_t endQuote = json.find('\"', startQuote + 1);
     167   [ +  -  +  - ]:           9 :             if (startQuote != std::string::npos && endQuote != std::string::npos) {
     168         [ +  - ]:           9 :                 std::string desc = json.substr(startQuote + 1, endQuote - startQuote - 1);
     169   [ +  +  -  +  :           9 :                 if (desc.find(vehicleName) != std::string::npos || vehicleName.find(desc) != std::string::npos) {
                   +  + ]
     170                 :           3 :                     break;
     171                 :             :                 }
     172         [ +  + ]:           9 :             }
     173                 :           6 :             descPos = json.find("\"desc\"", descPos + 1);
     174                 :             :         }
     175                 :             :     }
     176                 :             : 
     177   [ +  +  +  - ]:           6 :     if (descPos == std::string::npos) return "Unknown";
     178                 :             : 
     179                 :             :     // Now find "manufacturer" near this desc
     180                 :             :     // Search within 1024 characters after desc
     181                 :           3 :     size_t manufacturerPos = json.find("\"manufacturer\"", descPos);
     182   [ +  -  -  + ]:           3 :     if (manufacturerPos == std::string::npos || (manufacturerPos - descPos) > 1024) {
     183                 :             :          // Search backwards just in case
     184                 :           0 :          manufacturerPos = json.rfind("\"manufacturer\"", descPos);
     185                 :             :     }
     186                 :             : 
     187   [ -  +  -  - ]:           3 :     if (manufacturerPos == std::string::npos) return "Unknown";
     188                 :             : 
     189                 :           3 :     size_t colonPos = json.find(':', manufacturerPos);
     190                 :           3 :     size_t startQuote = json.find('\"', colonPos);
     191                 :           3 :     size_t endQuote = json.find('\"', startQuote + 1);
     192                 :             : 
     193   [ +  -  +  - ]:           3 :     if (startQuote != std::string::npos && endQuote != std::string::npos) {
     194                 :           3 :         return json.substr(startQuote + 1, endQuote - startQuote - 1);
     195                 :             :     }
     196                 :             : 
     197         [ #  # ]:           0 :     return "Unknown";
     198                 :             : }
     199                 :             : 
     200                 :           5 : float RestApiProvider::ParseSteeringLock(const std::string& json) {
     201                 :             :     // Look for "VM_STEER_LOCK" and then "stringValue"
     202                 :             :     // Example: "VM_STEER_LOCK" : { ... "stringValue" : "540 deg" ... }
     203                 :             : 
     204                 :             :     // 1. Find the property
     205                 :           5 :     size_t propPos = json.find("\"VM_STEER_LOCK\"");
     206         [ +  + ]:           5 :     if (propPos == std::string::npos) return 0.0f;
     207                 :             : 
     208                 :             :     // 2. Find "stringValue" within the next few hundred characters
     209                 :           3 :     size_t searchLimit = 512;
     210         [ +  - ]:           3 :     std::string sub = json.substr(propPos, searchLimit);
     211                 :           3 :     size_t valPos = sub.find("\"stringValue\"");
     212         [ -  + ]:           3 :     if (valPos == std::string::npos) return 0.0f;
     213                 :             : 
     214                 :             :     // 3. Extract the value string
     215                 :           3 :     size_t colonPos = sub.find(':', valPos);
     216         [ -  + ]:           3 :     if (colonPos == std::string::npos) return 0.0f;
     217                 :             : 
     218                 :           3 :     size_t startQuote = sub.find('\"', colonPos);
     219         [ -  + ]:           3 :     if (startQuote == std::string::npos) return 0.0f;
     220                 :             : 
     221                 :           3 :     size_t endQuote = sub.find('\"', startQuote + 1);
     222         [ -  + ]:           3 :     if (endQuote == std::string::npos) return 0.0f;
     223                 :             : 
     224         [ +  - ]:           3 :     std::string valueStr = sub.substr(startQuote + 1, endQuote - startQuote - 1);
     225                 :             : 
     226                 :             :     // 4. Extract numeric value using regex
     227         [ +  - ]:           3 :     std::regex re(R"(\d*\.?\d+)");
     228                 :           3 :     std::smatch match;
     229   [ +  -  +  - ]:           3 :     if (std::regex_search(valueStr, match, re)) {
     230                 :             :         try {
     231   [ +  -  +  - ]:           3 :             return std::stof(match.str());
     232                 :           0 :         } catch (...) {
     233                 :           0 :             return 0.0f;
     234         [ -  - ]:           0 :         }
     235                 :             :     }
     236                 :             : 
     237                 :           0 :     return 0.0f;
     238                 :           3 : }
        

Generated by: LCOV version 2.0-1