| 2884 | } |
| 2885 | |
| 2886 | cbm_daemon_runtime_client_t *cbm_daemon_runtime_client_connect( |
| 2887 | const cbm_daemon_ipc_endpoint_t *endpoint, const cbm_daemon_build_identity_t *identity, |
| 2888 | uint32_t timeout_ms, cbm_daemon_runtime_connect_result_t *result_out) { |
| 2889 | if (result_out) { |
| 2890 | memset(result_out, 0, sizeof(*result_out)); |
| 2891 | } |
| 2892 | uint8_t request[CBM_DAEMON_RENDEZVOUS_REQUEST_SIZE]; |
| 2893 | if (!endpoint || !identity || !result_out || timeout_ms == CBM_DAEMON_IPC_WAIT_FOREVER || |
| 2894 | !cbm_daemon_runtime_hello_request_encode(request, identity)) { |
| 2895 | return NULL; |
| 2896 | } |
| 2897 | cbm_daemon_ipc_connection_t *connection = cbm_daemon_ipc_connect(endpoint, timeout_ms); |
| 2898 | if (!connection || !cbm_daemon_ipc_send_frame(connection, CBM_DAEMON_FRAME_REQUEST, |
| 2899 | CBM_DAEMON_RUNTIME_OP_HELLO, request, |
| 2900 | (uint32_t)sizeof(request))) { |
| 2901 | cbm_daemon_ipc_connection_close(connection); |
| 2902 | return NULL; |
| 2903 | } |
| 2904 | cbm_daemon_frame_t frame = {0}; |
| 2905 | uint8_t *payload = NULL; |
| 2906 | int received = cbm_daemon_ipc_receive_frame_bounded( |
| 2907 | connection, timeout_ms, CBM_DAEMON_RENDEZVOUS_RESPONSE_SIZE, &frame, &payload); |
| 2908 | bool valid = received == 1 && frame.type == CBM_DAEMON_FRAME_RESPONSE && |
| 2909 | frame.flags == CBM_DAEMON_RUNTIME_OP_HELLO && |
| 2910 | frame.length == CBM_DAEMON_RENDEZVOUS_RESPONSE_SIZE && |
| 2911 | runtime_hello_response_decode(payload, result_out); |
| 2912 | free(payload); |
| 2913 | if (received != 1) { |
| 2914 | /* The kernel completed the pipe/socket connect, so a server process |
| 2915 | * exists, yet the HELLO went unanswered. Name that holder: a dead |
| 2916 | * runtime behind a live endpoint is otherwise indistinguishable from |
| 2917 | * absence, and the 2026-08-29 zombie hid behind exactly that gap. */ |
| 2918 | result_out->muted_endpoint_holder_pid = cbm_daemon_ipc_connection_peer_pid(connection); |
| 2919 | } |
| 2920 | if (!valid || result_out->status != CBM_DAEMON_RUNTIME_CONNECT_ACCEPTED) { |
| 2921 | cbm_daemon_ipc_connection_close(connection); |
| 2922 | return NULL; |
| 2923 | } |
| 2924 | |
| 2925 | cbm_daemon_runtime_client_t *client = calloc(1, sizeof(*client)); |
| 2926 | if (!client) { |
| 2927 | cbm_daemon_ipc_connection_close(connection); |
| 2928 | memset(result_out, 0, sizeof(*result_out)); |
| 2929 | return NULL; |
| 2930 | } |
| 2931 | cbm_mutex_init(&client->exchange_mutex); |
| 2932 | cbm_mutex_init(&client->send_mutex); |
| 2933 | cbm_mutex_init(&client->state_mutex); |
| 2934 | client->connection = connection; |
| 2935 | client->client_id = result_out->client_id; |
| 2936 | client->authenticated_process_id = result_out->authenticated_process_id; |
| 2937 | client->usable = true; |
| 2938 | return client; |
| 2939 | } |
| 2940 | |
| 2941 | cbm_daemon_client_id_t cbm_daemon_runtime_client_id(const cbm_daemon_runtime_client_t *client) { |
| 2942 | return client ? client->client_id : CBM_DAEMON_CLIENT_ID_INVALID; |