diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..22eb0c8 --- /dev/null +++ b/.clang-format @@ -0,0 +1,7 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +UseTab: ForIndentation +TabWidth: 4 +ColumnLimit: 180 +BreakBeforeBraces: Attach +AllowShortFunctionsOnASingleLine: Empty diff --git a/API b/API new file mode 100755 index 0000000..6f7c4ab Binary files /dev/null and b/API differ diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b40ae0..76b2268 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.20) -project(CPP_TEMPLATE VERSION 0.1.0 LANGUAGES CXX) +project(CPP_TEMPLATE VERSION 0.1.0 LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -47,6 +47,10 @@ add_executable(${EXECUTABLE_NAME} ${PROJECT_HEADERS} ) +find_package(PostgreSQL REQUIRED) +include_directories(${PostgreSQL_INCLUDE_DIRS}) +target_link_libraries(${EXECUTABLE_NAME} ${PostgreSQL_LIBRARIES}) + # Set output name property (for consistency) set_target_properties(${EXECUTABLE_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_NAME}) @@ -72,4 +76,4 @@ endif() # Optionally, enable testing # enable_testing() -# add_subdirectory(tests) \ No newline at end of file +# add_subdirectory(tests) diff --git a/compile_commands.json b/compile_commands.json new file mode 120000 index 0000000..25eb4b2 --- /dev/null +++ b/compile_commands.json @@ -0,0 +1 @@ +build/compile_commands.json \ No newline at end of file diff --git a/include/server_structs.h b/include/server_structs.h new file mode 100644 index 0000000..968da3f --- /dev/null +++ b/include/server_structs.h @@ -0,0 +1,244 @@ +#ifndef SERVER_STRUCTS_H +#define SERVER_STRUCTS_H + +#include +#include + +typedef struct handshake { + // [not used in the current Remote Telemtry version by AC] + // In future versions it will identify the platform type of the client. + // This will be used to adjust a specific behaviour for each platform. (eIPadDevice for now (1)) + int32_t identifier; + + // [not used in the current Remote Telemtry version by AC] + // In future version this field will identify the AC Remote Telemetry version that the device expects to speak with. + int32_t version; + + // This is the type of operation required by the client. + // The following operations are now available: + // ---------------------------------------------------------------- + // HANDSHAKE = 0 : + // This operation identifier must be set when the client wants to start the comunication. + // + // SUBSCRIBE_UPDATE = 1 : + // This operation identifier must be set when the client wants to be updated from the specific ACServer. + // + // SUBSCRIBE_SPOT = 2 : + // This operation identifier must be set when the client wants to be updated from the specific ACServer just for SPOT Events (e.g.: the end of a lap). + // + // DISMISS = 3 : + // This operation identifier must be set when the client wants to leave the comunication with ACServer. + int32_t operationId; +} __attribute__((packed)) handshake; + +typedef struct handshackerResponse { + // is the name of the car that the player is driving on the AC Server + char carName[50]; + + // is the name of the driver running on the AC Server + char driverName[50]; + + // for now is just 4242, this code will identify different status, + // as “NOT AVAILABLE” for connection + int32_t identifier; + + // for now is set to 1, this will identify the version running on the AC Server + int32_t version; + + // is the name of the track on the AC Server + char trackName[50]; + + // is the track configuration on the AC Server + char trackConfig[50]; +} __attribute__((packed)) handshackerResponse; + +typedef struct postion { + float x; + float y; + float z; +} __attribute__((packed)) postion; + +typedef enum flag { + NO_FLAG = 0, + YELLOW_FLAG = 1, + BLUE_FLAG = 2, + BLACK_FLAG = 3, + CHECKERED_FLAG = 4, +} __attribute__((packed)) flag; + +typedef struct carAtributes { + // Related to ACSP_CAR_INFO + u_char isConnected; // 1 = connected, 0 = disconnected + u_char isLoading; // 1 = loading, 0 = not isLoading + + char *car_model; + char *car_skin; + char *driver_name; + char *driver_team; + char *driver_GUID; + + // Related to ACSP_CAR_UPDATE + u_int8_t carID; + postion position; + postion velocity; + u_int8_t carGear; + u_int16_t carRPM; + u_int32_t lap_time; + u_int32_t cuts; + u_int32_t total_cuts; + u_int32_t total_cuts_alltime; + u_int16_t total_laps_completed; + u_int16_t contacts; + u_int16_t total_contacts; + + flag current_flag; // TODO: implement flag status updates + // TAG:3 + + float normalizedSplinePos; +} __attribute__((packed)) carAtributes; + +typedef struct carAtributesAPI { + // Related to ACSP_CAR_INFO + u_char isConnected; // 1 = connected, 0 = disconnected + u_char isLoading; // 1 = loading, 0 = not isLoading + + char car_model[64]; + char car_skin[64]; + char driver_name[64]; + char driver_team[64]; + char driver_GUID[64]; + + // Related to ACSP_CAR_UPDATE + u_int8_t carID; + postion position; + postion velocity; + u_int8_t carGear; + u_int16_t carRPM; + u_int32_t lap_time; + u_int32_t cuts; + u_int32_t total_cuts; + u_int32_t total_cuts_alltime; + u_int16_t total_laps_completed; + u_int16_t contacts; + u_int16_t total_contacts; + + flag current_flag; // TODO: implement flag status updates + // TAG:3 + + float normalizedSplinePos; +} __attribute__((packed)) carAtributesAPI; + + +typedef enum SessionType { + PRACTICE = 0, + RACE = 1, + QUALIFYING = 2, +} __attribute__((packed)) SessionType; + +typedef struct trackAtributes { + u_int8_t protocol_version; + + u_int8_t session_index; + u_int8_t current_session_index; + u_int8_t session_count; + SessionType session_type; + + char *server_name; + char *track; + char *track_config; + char *session_name; + + u_int8_t typ; + u_int16_t time; + u_int16_t laps; + u_int16_t wait_time; + u_int8_t ambient_temp; + u_int8_t road_temp; + + char *weather_graphics; + u_int32_t elapsed_ms; +} __attribute__((packed)) trackAtributes; + +typedef struct trackAtributesAPI { + u_int8_t protocol_version; + + u_int8_t session_index; + u_int8_t current_session_index; + u_int8_t session_count; + SessionType session_type; + + char server_name[128]; + char track[64]; + char track_config[64]; + char session_name[64]; + + u_int8_t typ; + u_int16_t time; + u_int16_t laps; + u_int16_t wait_time; + u_int8_t ambient_temp; + u_int8_t road_temp; + + char weather_graphics[64]; + u_int32_t elapsed_ms; +} __attribute__((packed)) trackAtributesAPI; + +typedef struct api_packet { + u_int8_t tracker_id; + u_int8_t last_updated_carid; + u_int8_t connected_cars; + + carAtributesAPI cars[64]; + trackAtributesAPI track_info; + +} __attribute__((packed)) api_packet; + +enum ACSP_MessageType { + // ============================ + // PROTOCOL VERSION + // ============================ + PROTOCOL_VERSION = 4, + + // ============================ + // SERVER → CLIENT MESSAGES + // ============================ + ACSP_NEW_SESSION = 50, + ACSP_NEW_CONNECTION = 51, + ACSP_CONNECTION_CLOSED = 52, + ACSP_CAR_UPDATE = 53, + ACSP_CAR_INFO = 54, // Response to ACSP_GET_CAR_INFO + ACSP_END_SESSION = 55, + ACSP_VERSION = 56, + ACSP_CHAT = 57, + ACSP_CLIENT_LOADED = 58, + ACSP_SESSION_INFO = 59, + ACSP_ERROR = 60, + ACSP_LAP_COMPLETED = 73, + + // ============================ + // EVENTS + // ============================ + ACSP_CLIENT_EVENT = 130, + + // ============================ + // EVENT TYPES + // ============================ + ACSP_CE_COLLISION_WITH_CAR = 10, + ACSP_CE_COLLISION_WITH_ENV = 11, + + // ============================ + // CLIENT → SERVER COMMANDS + // ============================ + ACSP_REALTIMEPOS_INTERVAL = 200, + ACSP_GET_CAR_INFO = 201, + ACSP_SEND_CHAT = 202, // Sends chat to one car + ACSP_BROADCAST_CHAT = 203, // Sends chat to everybody + ACSP_GET_SESSION_INFO = 204, + ACSP_SET_SESSION_INFO = 205, + ACSP_KICK_USER = 206, + ACSP_NEXT_SESSION = 207, + ACSP_RESTART_SESSION = 208, + ACSP_ADMIN_COMMAND = 209 // Send message plus a string +}; + +#endif // SERVER_STRUCTS_H diff --git a/include/stack.cpp b/include/stack.cpp new file mode 100644 index 0000000..6f417c1 --- /dev/null +++ b/include/stack.cpp @@ -0,0 +1,48 @@ +#include "stack.h" +#include + +Stack::Stack(size_t size) { + capacity = size; + data = new api_packet[capacity]; + top = -1; +} + +Stack::~Stack() { + delete[] data; +} + +bool Stack::isEmpty() { + return top == -1; +} + +bool Stack::isFull() { + return top == capacity - 1; +} + +bool Stack::push(const api_packet &item) { + if (isFull()) { + return false; // Stack overflow + } + data[++top] = item; + return true; +} + +bool Stack::pop(api_packet &item) { + if (isEmpty()) { + return false; // Stack underflow + } + item = data[top--]; + return true; +} + +bool Stack::peek(api_packet &item) { + if (isEmpty()) { + return false; // Stack is empty + } + item = data[top]; + return true; +} + +size_t Stack::size() { + return top + 1; +} diff --git a/include/stack.h b/include/stack.h new file mode 100644 index 0000000..d9236c9 --- /dev/null +++ b/include/stack.h @@ -0,0 +1,25 @@ +#ifndef STACK_H +#define STACK_H // STACK_H + +#include +#include +#include "server_structs.h" + +class Stack { + private: + api_packet *data; + size_t capacity; + int top; + + public: + Stack(size_t size); + ~Stack(); + bool isEmpty(); + bool isFull(); + bool push(const api_packet &item); + bool pop(api_packet &item); + bool peek(api_packet &item); + size_t size(); +}; + +#endif // STACK_H diff --git a/source/main.cpp b/source/main.cpp index df28b27..7dde4bf 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,7 +1,257 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -int main() -{ - std::cout << "Hello, World!" << std::endl; - return 0; -} \ No newline at end of file +// SERVER STRUCTS +#include "server_structs.h" +#include "stack.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 *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)) { + + 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); + + 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_cars); + + 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); + PQfinish(conn); + break; + } + + // Time for users table insert/UPDATE + + if (packet.connected_cars == 0) { + PQclear(res); + continue; // No connected cars to process + } + + for (int i = 0; i < packet.connected_cars; i++) { + + if (packet.cars[i].isConnected == 0) { + continue; // Skip disconnected cars + } + + 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[i].driver_GUID; + const char *driver_name = packet.cars[i].driver_name; + const char *driver_team = packet.cars[i].driver_team; + const char *car_model = packet.cars[i].car_model; + const char *car_skin = packet.cars[i].car_skin; + + std::string cuts_alltime_s = std::to_string(packet.cars[i].total_cuts_alltime); + std::string contacts_alltime_s = std::to_string(packet.cars[i].total_contacts); + std::string laps_completed_s = std::to_string(packet.cars[i].total_laps_completed); + std::string is_connect_s = std::to_string(packet.cars[i].isConnected); + std::string is_loading_s = std::to_string(packet.cars[i].isLoading); + std::string current_server_s = server_id_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); + + PQclear(user_res); + } + + PQclear(res); + } else { + usleep(1000); // Sleep for 1ms if no packets are available + } + } + + PQfinish(conn); + return NULL; +} + +int main(void) { + + int server_fd, client_fd; + struct sockaddr_un addr; + socklen_t addr_len; + ssize_t n; + + api_packet api_packet_storage; + + // 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; + strcpy(addr.sun_path, SERVER_SOCKET_PATH); + 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); + + 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"); + + 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; + } + + // Print info + 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(), + STACK_SIZE); + 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 0; +}