diff --git a/src/socket.c b/src/socket.c index ab9c852..eef11ee 100644 --- a/src/socket.c +++ b/src/socket.c @@ -11,14 +11,17 @@ #include #include #include +#include #include #include #include #include "external/cJSON.h" #include "configHandler.h" +#include "libopensubsonic/utils.h" +#include "libopensubsonic/httpclient.h" +#include "libopensubsonic/endpoint_getStarred.h" #include "socket.h" -#define SOCKET_PATH "/tmp/ossp_socket" // TODO Make this configurable through the configuration file static int server_fd = -1; static int client_fd = -1; static int rc = -1; @@ -27,9 +30,12 @@ struct sockaddr_un server_addr; struct sockaddr_un client_addr; extern configHandler_config_t* configObj; +bool isSocketHandlerLoopRunning = false; + void socketHandler_read(); +void socketHandler_performAction(int id, char** retDataStr); int SockSig_Length = 4; @@ -37,12 +43,12 @@ const uint32_t OSSP_Sock_ACK = 0x7253FF87; const uint32_t OSSP_Sock_CliConn = 0xE3566C2E; const uint32_t OSSP_Sock_GetConnInfo = 0x8E4F6B01; const uint32_t OSSP_Sock_Size = 0x1F7E8BCF; - const uint32_t OSSP_Sock_ClientGetReq = 0x210829CF; +const uint32_t OSSP_Sock_FragMsgACK = 0x32A093B4; -void socket_setup() { +int socketHandler_init() { printf("[SocketHandler] Initializing.\n"); // Create server socket, and ensure that the socket file is removed @@ -52,12 +58,12 @@ void socket_setup() { // TODO } - unlink(SOCKET_PATH); + unlink(configObj->client_socket_path); // Bind server socket to SOCKET_PATH memset(&server_addr, 0, sizeof(struct sockaddr_un)); server_addr.sun_family = AF_UNIX; - strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1); + strncpy(server_addr.sun_path, configObj->client_socket_path, sizeof(server_addr.sun_path) - 1); rc = bind(server_fd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_un)); if (rc == -1) { @@ -83,27 +89,56 @@ void socket_setup() { } } - - //socketHandler_read(); + // Initialize connection with client socketHandler_initClientConnection(); + // Enter socket handler loop + isSocketHandlerLoopRunning = true; + //while (isSocketHandlerLoopRunning) { + // + //} + printf("------------------------------\n"); + while (1) { + // Receive ClientGetReq with Size + int size = 0; + socketHandler_receiveCliGetReq(&size); + printf("Size to alloc: %d bytes\n", size); + char* reqBuf = malloc(size); - // Receive ClientGetReq with Size - int size = 0; - socketHandler_receiveCliGetReq(&size); - printf("Size to alloc: %d bytes\n", size); - char* reqBuf = malloc(size); + // Send ACK + socketHandler_sendAck(); - // Send ACK - socketHandler_sendAck(); + // Receive JSON data + socketHandler_receiveJson(&reqBuf, size); + printf("Received JSON: %s\n", reqBuf); - // Receive JSON data - socketHandler_receiveJson(&reqBuf, size); - printf("Received JSON: %s\n", reqBuf); + // Parse client request JSON + cJSON* cliReqJson = cJSON_Parse(reqBuf); + int id = 0; + OSS_Pioj(&id, cliReqJson, "id"); + printf("Action ID is %d\n", id); + + // Perform action + char* retData = NULL; + socketHandler_performAction(id, &retData); + int retDataSize = strlen(retData); + + // Send size back to client + socketHandler_sendSize(retDataSize); + + // Wait for ACK + socketHandler_receiveAck(); + + // Send JSON + socketHandler_sendJson(retData, retDataSize); + + // Wait for ACK + socketHandler_receiveAck(); + } } void socketHandler_cleanup() { @@ -111,7 +146,66 @@ void socketHandler_cleanup() { close(client_fd); close(server_fd); - unlink(SOCKET_PATH); + unlink(configObj->client_socket_path); +} + +void socketHandler_performAction(int id, char** retDataStr) { + switch (id) { + case OSSP_SOCKET_ACTION_GETSTARREDSONGS: + printf("[SocketHandler] Client requested Starred Songs.\n"); + + // Fetch /getStarred endpoint + opensubsonic_httpClient_URL_t* url = malloc(sizeof(opensubsonic_httpClient_URL_t)); + opensubsonic_httpClient_URL_prepare(&url); + url->endpoint = OPENSUBSONIC_ENDPOINT_GETSTARRED; + opensubsonic_httpClient_formUrl(&url); + + opensubsonic_getStarred_struct* getStarredStruct; + opensubsonic_httpClient_fetchResponse(&url, (void**)&getStarredStruct); + opensubsonic_httpClient_URL_cleanup(&url); // Free URL + + + + // Format into JSON + cJSON* retData = cJSON_CreateObject(); + cJSON_AddItemToObject(retData, "songCount", cJSON_CreateNumber(getStarredStruct->songCount)); + + cJSON* songArray = cJSON_CreateArray(); + cJSON_AddItemToObject(retData, "songs", songArray); + + for (int i = 0; i < getStarredStruct->songCount; i++) { + + cJSON* song = cJSON_CreateObject(); + cJSON_AddItemToObject(song, "title", cJSON_CreateString(getStarredStruct->songs[i].title)); + cJSON_AddItemToObject(song, "album", cJSON_CreateString(getStarredStruct->songs[i].album)); + cJSON_AddItemToObject(song, "artist", cJSON_CreateString(getStarredStruct->songs[i].artist)); + cJSON_AddItemToObject(song, "id", cJSON_CreateString(getStarredStruct->songs[i].id)); + cJSON_AddItemToObject(song, "size", cJSON_CreateNumber(getStarredStruct->songs[i].size)); + cJSON_AddItemToObject(song, "duration", cJSON_CreateNumber(getStarredStruct->songs[i].duration)); + cJSON_AddItemToObject(song, "albumId", cJSON_CreateString(getStarredStruct->songs[i].albumId)); + cJSON_AddItemToObject(song, "artistId", cJSON_CreateString(getStarredStruct->songs[i].artistId)); + + // Convert cover art ID to URL + opensubsonic_httpClient_URL_t* coverArtUrl = malloc(sizeof(opensubsonic_httpClient_URL_t)); + opensubsonic_httpClient_URL_prepare(&coverArtUrl); + coverArtUrl->endpoint = OPENSUBSONIC_ENDPOINT_GETCOVERART; + coverArtUrl->id = strdup(getStarredStruct->songs[i].coverArt); + opensubsonic_httpClient_formUrl(&coverArtUrl); + cJSON_AddItemToObject(song, "coverArtUrl", cJSON_CreateString(coverArtUrl->formedUrl)); + + cJSON_AddItemToArray(songArray, song); + opensubsonic_httpClient_URL_cleanup(&coverArtUrl); // Free Cover Art URL + } + + *retDataStr = cJSON_PrintUnformatted(retData); + cJSON_Delete(retData); + + opensubsonic_getStarred_struct_free(&getStarredStruct); // Free Struct + break; + default: + printf("[SocketHandler] Unknown action.\n"); + break; + } } @@ -119,20 +213,7 @@ void socketHandler_cleanup() { -// Byte Array to uint32_t Big Endian -uint32_t util_byteArrToUint32BE(char buf[]) { - // NOTE: I could use a combination of memcpy() and htons() here, but bitshifting is a single move - uint32_t retVal = 0x0; - retVal = buf[3] | buf[2] << 8 | buf[1] << 16 | buf[0] << 24; - return retVal; -} -// Byte Array to uint32_t Little Endian -uint32_t util_byteArrToUint32LE(char buf[]) { - uint32_t retVal = 0x0; - retVal = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24; - return retVal; -} int socketHandler_initClientConnection() { /* @@ -214,18 +295,18 @@ int socketHandler_sendAck() { } int socketHandler_receiveAck() { - char buf[4] = { 0x00 }; + uint8_t buf[4] = { 0x00 }; rc = read(client_fd, buf, SockSig_Length); if (rc != SockSig_Length) { printf("[SocketHandler] Failed to receive OSSP_Sock_ACK (Signature is the wrong length).\n"); return 1; } - uint32_t AckBE = util_byteArrToUint32LE(buf); + uint32_t AckLE = socketHandlerUtil_byteArrToUint32LE(buf); - rc = memcmp(&AckBE, &OSSP_Sock_ACK, SockSig_Length); + rc = memcmp(&AckLE, &OSSP_Sock_ACK, SockSig_Length); if (rc != 0) { - printf("[SocketHandler] Failed to receive OSSP_Sock_ACK (Signature is invalid. Expected 0x%.8x, Received 0x%.8x).\n", OSSP_Sock_ACK, AckBE); + printf("[SocketHandler] Failed to receive OSSP_Sock_ACK (Signature is invalid. Expected 0x%.8x, Received 0x%.8x).\n", OSSP_Sock_ACK, AckLE); return 1; } printf("[SocketHandler] Received OSSP_Sock_ACK.\n"); @@ -234,6 +315,27 @@ int socketHandler_receiveAck() { +int socketHandler_receiveFragMsgAck() { + uint8_t buf[4] = { 0x00 }; + rc = read(client_fd, buf, SockSig_Length); + if (rc != SockSig_Length) { + printf("[SocketHandler] Failed to receive OSSP_Sock_FragMsgACK (Signature is the wrong length).\n"); + return 1; + } + + uint32_t AckLE = socketHandlerUtil_byteArrToUint32LE(buf); + + rc = memcmp(&AckLE, &OSSP_Sock_FragMsgACK, SockSig_Length); + if (rc != 0) { + printf("[SocketHandler] Failed to receive OSSP_Sock_FragMsgACK (Signature is invalid. Expected 0x%.8x, Received 0x%.8x).\n", OSSP_Sock_FragMsgACK, AckLE); + return 1; + } + printf("[SocketHandler] Received OSSP_Sock_FragMsgACK.\n"); + return 0; +} + + + int socketHandler_receiveCliConn() { char buf[4] = { 0x00 }; rc = read(client_fd, buf, SockSig_Length); @@ -242,12 +344,11 @@ int socketHandler_receiveCliConn() { return 1; } - //uint32_t CliConnBE = util_byteArrToUint32BE(buf); - uint32_t CliConnBE = util_byteArrToUint32LE(buf); + uint32_t CliConnLE = socketHandlerUtil_byteArrToUint32LE(buf); - rc = memcmp(&CliConnBE, &OSSP_Sock_CliConn, SockSig_Length); + rc = memcmp(&CliConnLE, &OSSP_Sock_CliConn, SockSig_Length); if (rc != 0) { - printf("[SocketHandler] Failed to receive OSSP_Sock_CliConn (Signature is invalid. Expected 0x%.8x, Received 0x%.8x).\n", OSSP_Sock_CliConn, CliConnBE); + printf("[SocketHandler] Failed to receive OSSP_Sock_CliConn (Signature is invalid. Expected 0x%.8x, Received 0x%.8x).\n", OSSP_Sock_CliConn, CliConnLE); return 1; } printf("[SocketHandler] Received OSSP_Sock_CliConn.\n"); @@ -264,11 +365,11 @@ int socketHandler_receiveGetConnInfo() { return 1; } - uint32_t GetConnInfoBE = util_byteArrToUint32LE(buf); + uint32_t GetConnInfoLE = socketHandlerUtil_byteArrToUint32LE(buf); - rc = memcmp(&GetConnInfoBE, &OSSP_Sock_GetConnInfo, SockSig_Length); + rc = memcmp(&GetConnInfoLE, &OSSP_Sock_GetConnInfo, SockSig_Length); if (rc != 0) { - printf("[SocketHandler] Failed to receive OSSP_Sock_GetConnInfo (Signature is invalid. Expected 0x%.8x, Received 0x%.8x).\n", OSSP_Sock_GetConnInfo, GetConnInfoBE); + printf("[SocketHandler] Failed to receive OSSP_Sock_GetConnInfo (Signature is invalid. Expected 0x%.8x, Received 0x%.8x).\n", OSSP_Sock_GetConnInfo, GetConnInfoLE); return 1; } printf("[SocketHandler] Received OSSP_Sock_GetConnInfo.\n"); @@ -322,7 +423,7 @@ int socketHandler_receiveSize(uint32_t* size) { int socketHandler_receiveCliGetReq(int* size) { - char buf[8] = { 0x00 }; + uint8_t buf[8] = { 0x00 }; rc = read(client_fd, buf, 8); if (rc != 8) { printf("[SocketHandler] Failed to receive OSSP_Sock_ClientGetReq (Invalid size).\n"); @@ -355,12 +456,53 @@ int socketHandler_receiveJson(char** data, int size) { int socketHandler_sendJson(char* json, int size) { printf("[SocketHandler] Sending JSON.\n"); + + /* + * Notes on IPC messaging sizes and fragmented sending + * - Able to get macOS max IPC message size with sysctl kern.ipc.maxsockbuf + * - Sticking with 8192 bytes for safety + * - IPC messages REQUIRES contiguous memory + */ - rc = send(client_fd, json, size, 0); - if (rc != size) { - printf("[SocketHandler] Failed to send JSON.\n"); - return 1; + float msgCount_intermediate = (float)size / 8192; + int msgCount = (int)ceil(msgCount_intermediate); + int msgCount_remainer = size % 8192; // If using fragmented messaging, find remaining bytes in last message + + if (msgCount > 1) { + // Fragmented messaging required + printf("[SocketHandler] Sending fragmented payload in %d chunks.\n", msgCount); + for (int i = 0; i < msgCount; i++) { + printf("[SocketHandler] Sending fragment %d.\n", i); + + if (i == msgCount - 1) { + // Last fragment, use remainder bytes as message length + rc = send(client_fd, json + (i * 8192), msgCount_remainer, 0); + if (rc != msgCount_remainer) { + printf("[SocketHandler] Failed to send JSON.\n"); + return 1; + } + } else { + // Use 8192 bytes as the message length + rc = send(client_fd, json + (i * 8192), 8192, 0); + if (rc != 8192) { + printf("[SocketHandler] Failed to send JSON.\n"); + return 1; + } + } + + // Wait for OSSP_Sock_FragMsgACK + socketHandler_receiveFragMsgAck(); + } + } else { + // Payload fits inside single message + printf("[SocketHandler] Sending non-fragmented payload.\n"); + rc = send(client_fd, json, size, 0); + if (rc != size) { + printf("[SocketHandler] Failed to send JSON.\n"); + return 1; + } } + return 0; } @@ -378,3 +520,22 @@ int socketHandler_processClientGetReq() { // Step 4 - -- } + + +/* + * Utility Functions + */ +// Byte Array to uint32_t Big Endian +uint32_t socketHandlerUtil_byteArrToUint32BE(uint8_t buf[]) { + // NOTE: I could use a combination of memcpy() and htons() here, but bitshifting is a single move + uint32_t retVal = 0x0; + retVal = buf[3] | buf[2] << 8 | buf[1] << 16 | buf[0] << 24; + return retVal; +} + +// Byte Array to uint32_t Little Endian +uint32_t socketHandlerUtil_byteArrToUint32LE(uint8_t buf[]) { + uint32_t retVal = 0x0; + retVal = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24; + return retVal; +} \ No newline at end of file diff --git a/src/socket.h b/src/socket.h index a7ee50d..6fae4b3 100644 --- a/src/socket.h +++ b/src/socket.h @@ -2,6 +2,10 @@ #define _SOCKET_H #include +#define OSSP_SOCKET_ACTION_GETSTARREDSONGS 101 +#define OSSP_SOCKET_ACTION_NOW_PLAYING 201 +#define OSSP_SOCKET_ACTION_STATS 202 + typedef struct { uint32_t signature; uint32_t size; @@ -12,11 +16,13 @@ typedef struct { uint32_t size; } __attribute__((packed)) OSSP_Sock_ClientGetReq_t; -void socket_setup(); +int socketHandler_init(); +void socketHandler_cleanup(); int socketHandler_initClientConnection(); int socketHandler_sendAck(); int socketHandler_receiveAck(); +int socketHandler_receiveFragMsgAck(); int socketHandler_receiveCliConn(); int socketHandler_receiveGetConnInfo(); @@ -27,4 +33,7 @@ int socketHandler_receiveCliGetReq(int* size); int socketHandler_receiveJson(char** data, int size); int socketHandler_sendJson(char* json, int size); +uint32_t socketHandlerUtil_byteArrToUint32BE(uint8_t buf[]); +uint32_t socketHandlerUtil_byteArrToUint32LE(uint8_t buf[]); + #endif