/* * JetKVM Audio IPC Protocol * * Wire protocol for Unix domain socket communication between main process * and audio subprocesses. This protocol is 100% compatible with the Go * implementation in internal/audio/ipc_*.go * * CRITICAL: All multi-byte integers use LITTLE-ENDIAN byte order. */ #ifndef JETKVM_IPC_PROTOCOL_H #define JETKVM_IPC_PROTOCOL_H #include #include // ============================================================================ // PROTOCOL CONSTANTS // ============================================================================ // Magic numbers (ASCII representation when read as little-endian) #define IPC_MAGIC_OUTPUT 0x4A4B4F55 // "JKOU" - JetKVM Output (device → browser) #define IPC_MAGIC_INPUT 0x4A4B4D49 // "JKMI" - JetKVM Microphone Input (browser → device) // Message types (matches Go UnifiedMessageType enum) #define IPC_MSG_TYPE_OPUS_FRAME 0 // Audio frame data (Opus encoded) #define IPC_MSG_TYPE_CONFIG 1 // Basic audio config (12 bytes) #define IPC_MSG_TYPE_OPUS_CONFIG 2 // Complete Opus config (36 bytes) #define IPC_MSG_TYPE_STOP 3 // Shutdown signal #define IPC_MSG_TYPE_HEARTBEAT 4 // Keep-alive ping #define IPC_MSG_TYPE_ACK 5 // Acknowledgment // Size constraints #define IPC_HEADER_SIZE 17 // Fixed header size #define IPC_MAX_FRAME_SIZE 4096 // Maximum payload size (matches Go Config.MaxFrameSize) // Socket paths #define IPC_SOCKET_OUTPUT "/var/run/audio_output.sock" #define IPC_SOCKET_INPUT "/var/run/audio_input.sock" // ============================================================================ // WIRE FORMAT STRUCTURES // ============================================================================ /** * IPC message header (17 bytes, little-endian) * * Byte layout: * [0-3] magic uint32_t LE Magic number (0x4A4B4F55 or 0x4A4B4D49) * [4] type uint8_t Message type (0-5) * [5-8] length uint32_t LE Payload size in bytes * [9-16] timestamp int64_t LE Unix nanoseconds (time.Now().UnixNano()) * [17+] data uint8_t[] Variable payload * * CRITICAL: Must use __attribute__((packed)) to prevent padding. */ typedef struct __attribute__((packed)) { uint32_t magic; // Magic number (LE) uint8_t type; // Message type uint32_t length; // Payload length in bytes (LE) int64_t timestamp; // Unix nanoseconds (LE) } ipc_header_t; /** * Basic audio configuration (12 bytes) * Message type: IPC_MSG_TYPE_CONFIG * * All fields are uint32_t little-endian. */ typedef struct __attribute__((packed)) { uint32_t sample_rate; // Samples per second (e.g., 48000) uint32_t channels; // Number of channels (e.g., 2 for stereo) uint32_t frame_size; // Samples per frame (e.g., 960) } ipc_config_t; /** * Complete Opus encoder/decoder configuration (36 bytes) * Message type: IPC_MSG_TYPE_OPUS_CONFIG * * All fields are uint32_t little-endian. * Note: Negative values (like signal_type=-1000) are stored as two's complement uint32. */ typedef struct __attribute__((packed)) { uint32_t sample_rate; // Samples per second (48000) uint32_t channels; // Number of channels (2) uint32_t frame_size; // Samples per frame (960) uint32_t bitrate; // Bits per second (96000) uint32_t complexity; // Encoder complexity 0-10 (1=fast, 10=best quality) uint32_t vbr; // Variable bitrate: 0=disabled, 1=enabled uint32_t signal_type; // Signal type: -1000=auto, 3001=music, 3002=voice uint32_t bandwidth; // Bandwidth: 1101=narrowband, 1102=mediumband, 1103=wideband uint32_t dtx; // Discontinuous transmission: 0=disabled, 1=enabled } ipc_opus_config_t; /** * Complete IPC message (header + payload) */ typedef struct { ipc_header_t header; uint8_t *data; // Dynamically allocated payload (NULL if length=0) } ipc_message_t; // ============================================================================ // FUNCTION DECLARATIONS // ============================================================================ /** * Read a complete IPC message from socket. * * This function: * 1. Reads exactly 17 bytes (header) * 2. Validates magic number * 3. Validates length <= IPC_MAX_FRAME_SIZE * 4. Allocates and reads payload if length > 0 * 5. Stores result in msg->header and msg->data * * @param sock Socket file descriptor * @param msg Output message (data will be malloc'd if length > 0) * @param expected_magic Expected magic number (IPC_MAGIC_OUTPUT or IPC_MAGIC_INPUT) * @return 0 on success, -1 on error * * CALLER MUST FREE msg->data if non-NULL! */ int ipc_read_message(int sock, ipc_message_t *msg, uint32_t expected_magic); /** * Write a complete IPC message to socket. * * This function writes header + payload atomically (if possible via writev). * Sets timestamp to current time. * * @param sock Socket file descriptor * @param magic Magic number (IPC_MAGIC_OUTPUT or IPC_MAGIC_INPUT) * @param type Message type (IPC_MSG_TYPE_*) * @param data Payload data (can be NULL if length=0) * @param length Payload length in bytes * @return 0 on success, -1 on error */ int ipc_write_message(int sock, uint32_t magic, uint8_t type, const uint8_t *data, uint32_t length); /** * Parse Opus configuration from message data. * * @param data Payload data (must be exactly 36 bytes) * @param length Payload length (must be 36) * @param config Output Opus configuration * @return 0 on success, -1 if length != 36 */ int ipc_parse_opus_config(const uint8_t *data, uint32_t length, ipc_opus_config_t *config); /** * Parse basic audio configuration from message data. * * @param data Payload data (must be exactly 12 bytes) * @param length Payload length (must be 12) * @param config Output audio configuration * @return 0 on success, -1 if length != 12 */ int ipc_parse_config(const uint8_t *data, uint32_t length, ipc_config_t *config); /** * Free message resources. * * @param msg Message to free (frees msg->data if non-NULL) */ void ipc_free_message(ipc_message_t *msg); /** * Get current time in nanoseconds (Unix epoch). * * @return Time in nanoseconds (compatible with Go time.Now().UnixNano()) */ int64_t ipc_get_time_ns(void); /** * Create Unix domain socket server. * * This function: * 1. Creates socket with AF_UNIX, SOCK_STREAM * 2. Removes existing socket file * 3. Binds to specified path * 4. Listens with backlog=1 (single client) * * @param socket_path Path to Unix socket (e.g., "/var/run/audio_output.sock") * @return Socket fd on success, -1 on error */ int ipc_create_server(const char *socket_path); /** * Accept client connection with automatic retry. * * Blocks until client connects or error occurs. * * @param server_sock Server socket fd from ipc_create_server() * @return Client socket fd on success, -1 on error */ int ipc_accept_client(int server_sock); /** * Helper: Read exactly N bytes from socket (loops until complete or error). * * @param sock Socket file descriptor * @param buf Output buffer * @param len Number of bytes to read * @return 0 on success, -1 on error */ int ipc_read_full(int sock, void *buf, size_t len); #endif // JETKVM_IPC_PROTOCOL_H