#include #include #include #include #include #include #include #include #include #include // native webstocket // SERVER STRUCTS #include "server_structs.h" #include "stack.h" #include "telemetry.h" #define SERVER_SOCKET_PATH "/tmp/ACplayer_socket" const int STACK_SIZE = 512; Stack api_queue(STACK_SIZE); void checkConn(PGconn *conn) { if (PQstatus(conn) != CONNECTION_OK) { fprintf(stderr, "[!] Connection to database failed: %s", PQerrorMessage(conn)); PQfinish(conn); exit(1); } } void sanitize_utf8(char *str) { if (!str) return; for (size_t i = 0; str[i] != '\0'; i++) { // Replace invalid UTF-8 bytes with '?' if ((unsigned char)str[i] > 127) { str[i] = '?'; } } } void *db_write_thread(void *arg) { (void)arg; // Unused parameter const char *conninfo = "host=localhost port=5432 dbname=acservers_players user=admin password=123wer789"; PGconn *conn = PQconnectdb(conninfo); checkConn(conn); if (PQstatus(conn) == CONNECTION_OK) { printf("[+] Database connection established successfully.\n"); } while (1) { api_packet packet; if (api_queue.pop(packet)) { // Check if message was Handshake if (packet.tracker_id == 65) { printf("[+] Handshake packet received for server \"%s\" (%d), skipping database write.\n", packet.track_info.server_name, packet.tracker_id); continue; // Skip database write for handshake packets } broadcast_telemetry(packet); // DEBUG printf("[W] Writing packet for Server with tracker ID: %d to database.\tQueue: %u/%d\n", packet.tracker_id, (unsigned int)api_queue.size(), STACK_SIZE); // Time for servers table insert/UPDATE const char *query = "INSERT INTO servers (" "server_id, server_name, session_type, session_count, server_track, " "server_config, server_weather_graphics, typ, session_time, session_laps, " "session_wait_time, session_ambient_temp, session_road_temp, session_elapsed_time, connected_players" ") " "VALUES ($1, $2, $4, $3, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) " "ON CONFLICT (server_id) DO UPDATE SET " "session_type = EXCLUDED.session_type, " "session_count = EXCLUDED.session_count, " "server_track = EXCLUDED.server_track, " "server_config = EXCLUDED.server_config, " "server_weather_graphics = EXCLUDED.server_weather_graphics, " "typ = EXCLUDED.typ, " "session_time = EXCLUDED.session_time, " "session_laps = EXCLUDED.session_laps, " "session_wait_time = EXCLUDED.session_wait_time, " "session_ambient_temp = EXCLUDED.session_ambient_temp, " "session_road_temp = EXCLUDED.session_road_temp, " "session_elapsed_time = EXCLUDED.session_elapsed_time, " "connected_players = EXCLUDED.connected_players " "RETURNING server_id;"; const char *server_name = packet.track_info.server_name; const char *server_track = packet.track_info.track; const char *server_config = packet.track_info.track_config; const char *server_weather_graphics = packet.track_info.weather_graphics; std::string server_id_str = std::to_string(packet.tracker_id); std::string session_count_s = std::to_string(packet.track_info.session_count); std::string session_type_s = std::to_string(packet.track_info.session_type); std::string typ_s = std::to_string(packet.track_info.typ); std::string session_time_s = std::to_string(packet.track_info.time); std::string session_laps_s = std::to_string(packet.track_info.laps); std::string session_wait_s = std::to_string(packet.track_info.wait_time); std::string ambient_temp_s = std::to_string(packet.track_info.ambient_temp); std::string road_temp_s = std::to_string(packet.track_info.road_temp); std::string elapsed_time_s = std::to_string((u_int16_t)packet.track_info.elapsed_ms); std::string connected_players_s = std::to_string(packet.connected_players); const char *paramValues[15] = {server_id_str.c_str(), server_name, session_count_s.c_str(), session_type_s.c_str(), server_track, server_config, server_weather_graphics, typ_s.c_str(), session_time_s.c_str(), session_laps_s.c_str(), session_wait_s.c_str(), ambient_temp_s.c_str(), road_temp_s.c_str(), elapsed_time_s.c_str(), connected_players_s.c_str()}; PGresult *res = PQexecParams(conn, query, 15, nullptr, paramValues, nullptr, nullptr, 0); if (PQresultStatus(res) != PGRES_TUPLES_OK) { printf("[!] Insert failed: %s", PQerrorMessage(conn)); PQclear(res); continue; } PQclear(res); // Time for users table insert/UPDATE for (int i = 0; i < MAX_PLAYERS; i++) { if (packet.cars[i].isConnected == 0) { if (packet.cars[i].driver_GUID[0] == '\0') { continue; // Skip empty slots } printf("[D] User disconnected (%d): GUID=%s, Name=%s\n", i, packet.cars[i].driver_GUID, packet.cars[i].driver_name); // If the car is not connected, we still need to update its status in the db const char *disconnect_query = "UPDATE users SET is_connect = false, current_server = NULL WHERE driver_guid = $1;"; const char *disconnect_paramValues[1] = {packet.cars[i].driver_GUID}; PGresult *disconnect_res = PQexecParams(conn, disconnect_query, 1, nullptr, disconnect_paramValues, nullptr, nullptr, 0); if (PQresultStatus(disconnect_res) != PGRES_COMMAND_OK) { printf("[!] User disconnect update failed for GUID %s: %s", packet.cars[i].driver_GUID, PQerrorMessage(conn)); } PQclear(disconnect_res); continue; // Skip disconnected cars } u_int8_t car_id = packet.cars[i].carID; const char *user_query = "INSERT INTO users (" "driver_guid, driver_name, driver_team, car_model, car_skin, " "cuts_alltime, contacts_alltime, laps_completed, is_connect, is_loading, current_server" ") " "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) " "ON CONFLICT (driver_guid) DO UPDATE SET " "driver_name = EXCLUDED.driver_name, " "driver_team = EXCLUDED.driver_team, " "car_model = EXCLUDED.car_model, " "car_skin = EXCLUDED.car_skin, " "cuts_alltime = EXCLUDED.cuts_alltime, " "contacts_alltime = EXCLUDED.contacts_alltime, " "laps_completed = EXCLUDED.laps_completed, " "is_connect = EXCLUDED.is_connect, " "is_loading = EXCLUDED.is_loading, " "current_server = EXCLUDED.current_server;"; const char *driver_guid = packet.cars[car_id].driver_GUID; const char *driver_name = packet.cars[car_id].driver_name; const char *driver_team = packet.cars[car_id].driver_team; const char *car_model = packet.cars[car_id].car_model; const char *car_skin = packet.cars[car_id].car_skin; std::string cuts_alltime_s = std::to_string(packet.cars[car_id].total_cuts_alltime); std::string contacts_alltime_s = std::to_string(packet.cars[car_id].total_contacts); std::string laps_completed_s = std::to_string(packet.cars[car_id].total_laps_completed); std::string is_connect_s = std::to_string(packet.cars[car_id].isConnected); std::string is_loading_s = std::to_string(packet.cars[car_id].isLoading); std::string current_server_s = std::to_string(packet.tracker_id); // Debug output for user data being inserted/updated printf("[D] Processing user \(%d\): GUID=%s, Name=%s, Team=%s Car Model=%s, Skin=%s, Cuts=%s, Contacts=%s, Laps=%s, IsConnect=%s, IsLoading=%s, CurrentServer=%s\n", i, driver_guid, driver_name, driver_team, car_model, car_skin, cuts_alltime_s.c_str(), contacts_alltime_s.c_str(), laps_completed_s.c_str(), is_connect_s.c_str(), is_loading_s.c_str(), current_server_s.c_str()); const char *user_paramValues[11] = {driver_guid, driver_name, driver_team, car_model, car_skin, cuts_alltime_s.c_str(), contacts_alltime_s.c_str(), laps_completed_s.c_str(), is_connect_s.c_str(), is_loading_s.c_str(), current_server_s.c_str()}; PGresult *user_res = PQexecParams(conn, user_query, 11, nullptr, user_paramValues, nullptr, nullptr, 0); if (PQresultStatus(user_res) != PGRES_COMMAND_OK) { printf("[!] User insert/update failed for GUID %s: %s", driver_guid, PQerrorMessage(conn)); PQclear(user_res); continue; // Proceed to next user } PQclear(user_res); } } else { usleep(1000); // Sleep for 1ms if no packets are available } } PQfinish(conn); return NULL; } void *handle_client(void *arg) { int client_fd = *(int *)arg; free(arg); api_packet api_packet_storage; ssize_t n; while ((n = read(client_fd, &api_packet_storage, sizeof(api_packet))) > 0) { printf("[+] Received %zd bytes\n", n); if (api_packet_storage.tracker_id == 65) { printf("[+] Handshake received from server \"%s\" (%d).\n", api_packet_storage.track_info.server_name, api_packet_storage.tracker_id); continue; } printf("[*] Info: Tracker id: %d (\"%s\")\tQueue: %u/%d\n", api_packet_storage.tracker_id, api_packet_storage.track_info.server_name, (unsigned int)api_queue.size(), (int)api_queue.getCapacity()); if (!api_queue.push(api_packet_storage)) { printf("[!] Api queue full, dropping packet from tracker id: %d\n", api_packet_storage.tracker_id); } usleep(1500); } if (n < 0) perror("[!] Error on read"); else printf("[+] Client disconnected\n"); close(client_fd); return NULL; } int main(void) { int server_fd, client_fd; struct sockaddr_un addr; // create socket server_fd = socket(AF_UNIX, SOCK_STREAM, 0); if (server_fd < 0) { perror("socket"); exit(1); } unlink(SERVER_SOCKET_PATH); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SERVER_SOCKET_PATH, sizeof(addr.sun_path) - 1); addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; // null-terminate just in case if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); exit(1); } if (listen(server_fd, 5) < 0) { perror("listen"); exit(1); } // start db write db_write_thread pthread_t db_thread; if (pthread_create(&db_thread, NULL, db_write_thread, NULL) != 0) { perror("pthread create"); exit(1); } // detach the thread so it cleans up after itself pthread_detach(db_thread); pthread_t telemetry_thread; if (pthread_create(&telemetry_thread, NULL, telemetry_server_thread, NULL) != 0) { perror("[!] Failed to create telemetry server thread"); return EXIT_FAILURE; } pthread_detach(telemetry_thread); printf("[+] Server listening on %s\n", SERVER_SOCKET_PATH); while (1) { client_fd = accept(server_fd, NULL, NULL); if (client_fd < 0) { perror("[!] accept"); continue; } printf("[+] Client connected\n"); int *client_fd_ptr = (int *)malloc(sizeof(int)); *client_fd_ptr = client_fd; pthread_t client_thread; if (pthread_create(&client_thread, NULL, handle_client, client_fd_ptr) != 0) { perror("pthread_create"); close(client_fd); free(client_fd_ptr); continue; } pthread_detach(client_thread); // no need to join manually } return 0; }