/* BSD-2-Clause license * * Copyright (c) 2018-2023 NST , sss . * */ #include #include #include #include #include #include #include #include #include "base64_url.h" #include #include #include "wrdp_thpool.h" #include "wrdp_thpool_internals.h" #include "ws_session.h" #include "ws_protocol.h" #include "globals.h" #include "task.h" #include "thread_impl.h" #include "json_helpers.h" #include "curl_helpers.h" #include "backend_helpers.h" #include "utilities.h" #include "log.h" static bool token_check(ws_session *session) { char *token = session->token_base64; size_t token_len = strlen(token), raw_token_len = 0; unsigned int raw_token_signature_len = 0; /* used additional 2 bytes in buffer * required for base64_url_decode */ uint8_t *raw_token = malloc(96 + 2), *raw_token_signature = 0; if (!raw_token) { perror("malloc"); goto error; } { char buf[128]; snprintf(buf, 127, "recieved token: %s", token); log_msg( (const uint8_t *)buf, strlen(buf), wrdp_log_level_trace, 0); } errno = base64_url_decode( (uint8_t *)token, token_len, raw_token, token_len, &raw_token_len); if (errno) { perror("token_check: base64_url_decode"); free(raw_token); goto error; } if (raw_token_len != 96) { const char *msg = "token_check: raw_roken length != 96"; log_msg( (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); free(raw_token); goto error; } raw_token_signature = malloc(32); if (!raw_token_signature) { perror("malloc"); free(raw_token); goto error; } /* verify token */ { HMAC_CTX *ctx = HMAC_CTX_new(); HMAC_Init_ex(ctx, g_globals.settings.secret_key_verify, 64, EVP_sha256(), NULL); HMAC_Update(ctx, raw_token, raw_token_len - 32); HMAC_Final(ctx, raw_token_signature, &raw_token_signature_len); HMAC_CTX_free(ctx); if (raw_token_signature_len != 32) { const char *msg = "token signature validation failed" " (incorrect result hash size)"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); free(raw_token); free(raw_token_signature); goto error; } #ifdef DEBUG { log_msg((const uint8_t *)"old hash", strlen("old hash"), wrdp_log_level_trace, wrdp_log_flag_binary); log_msg(raw_token + 64, 32, wrdp_log_level_trace, wrdp_log_flag_binary); log_msg((const uint8_t *)"new hash", strlen("new hash"), wrdp_log_level_trace, wrdp_log_flag_binary); log_msg(raw_token_signature, 32, wrdp_log_level_trace, wrdp_log_flag_binary); } #endif if (memcmp(raw_token + 64, raw_token_signature, 32)) { const char *msg = "token signature validation failed (result" " hash differs)"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); free(raw_token); free(raw_token_signature); goto error; } } /* extract and store base64-urlsafe encoded sid into session */ { /* TODO: check if size calculation correct and optimal */ size_t sid_len = (4 * (64 / 3)) + 4 + 2, encoded_len = 0; char *sid_base64 = calloc(sid_len, sizeof(char)), *ptr; base64_url_encode(raw_token, 64, (uint8_t *)sid_base64, sid_len, &encoded_len); sid_base64[encoded_len] = 0; ptr = realloc(sid_base64, encoded_len + 1); if (!ptr) { //if realloc failed we still can use preveously //allocated memory perror("realloc"); } else { sid_base64 = ptr; } session->sid_base64 = sid_base64; } /* reencrypt token */ { HMAC_CTX *ctx = HMAC_CTX_new(); uint8_t resigned_raw_token[96] = {0}; size_t base64_len = 0; HMAC_Init_ex(ctx, g_globals.settings.secret_key_sign, 64, EVP_sha256(), NULL); HMAC_Update(ctx, raw_token, raw_token_len - 32); HMAC_Final(ctx, raw_token_signature, &raw_token_signature_len); HMAC_CTX_free(ctx); if (raw_token_signature_len != 32) { const char *msg = "token signature generation failed (incorrect" " result hash size)"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); free(raw_token); free(raw_token_signature); goto error; } memcpy(resigned_raw_token, raw_token, 64); memcpy(resigned_raw_token + 64, raw_token_signature, 32); errno = base64_url_encode( resigned_raw_token, 96, (uint8_t *)token, 255, &base64_len); if (errno) { free(raw_token); free(raw_token_signature); perror("token_verify: base64_url_encode"); goto error; } token[base64_len] = 0; } session->token_verified = true; free(raw_token); free(raw_token_signature); return true; error: session->session_state = ws_session_error; free(session->token_base64); session->token_base64 = 0; free(session->backend_module_name); session->backend_module_name = 0; return false; } static bool handle_session_setting_element( struct json_object_element_s *json_option, ws_session *session) { task_info *info = session->task_info; /* TODO: move timeouts to session ? * cache timeouts settings until task_info created ? */ /* if (!strncmp(json_option->name->string, "session_time_limit", json_option->name->string_size)) { info->settings.session_time_limit = json_option_extract_int64 (json_option); return true; } else if (!strncmp(json_option->name->string, "session_idle_timeout", json_option->name->string_size)) { info->settings.session_idle_timeout = json_option_extract_int64 (json_option); return true; } else */ if (!strncmp(json_option->name->string, "token", json_option->name->string_size)) { struct json_string_s *jsopt = (struct json_string_s *)json_option->value->payload; char *token_base64; if (!jsopt->string_size) { const char *msg = "zero size token received"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); return true; } token_base64 = malloc(jsopt->string_size + 1); memcpy(token_base64, jsopt->string, jsopt->string_size); token_base64[jsopt->string_size] = 0; if (session->token_base64) free(session->token_base64); session->token_base64 = token_base64; return true; } else if (!strncmp(json_option->name->string, "attach_sid", json_option->name->string_size)) { /* if (session->sid_base64) { free (session->sid_base64); } */ struct json_string_s *jsopt = (struct json_string_s *)json_option->value->payload; char *attach_sid = malloc(jsopt->string_size + 1); memcpy(attach_sid, jsopt->string, jsopt->string_size); attach_sid[jsopt->string_size] = 0; if (session->attach_sid_base64) free(session->attach_sid_base64); session->attach_sid_base64 = attach_sid; return true; } else if (!strncmp(json_option->name->string, "proto", json_option->name->string_size)) { if (info && info->backend) { /* backend already created, possible by "proto" setting * from web ui */ return true; } struct json_string_s *jsopt = (struct json_string_s *)json_option->value->payload; char *var = malloc(jsopt->string_size + 1); memcpy(var, jsopt->string, jsopt->string_size); var[jsopt->string_size] = 0; if (session->backend_module_name) free(session->backend_module_name); session->backend_module_name = var; return true; } { char buf[128]; snprintf(buf, 127, "handle_session_setting:" " unhandled option: %.*s", (int)(json_option->name->string_size), json_option->name->string); log_msg((const uint8_t *)buf, strlen(buf), wrdp_log_level_warning, 0); } return false; } static bool handle_backend_setting_element( struct json_object_element_s *json_option, ws_session *session) { char option_name[json_option->name->string_size + 1]; strncpy(option_name, json_option->name->string, json_option->name->string_size); option_name[json_option->name->string_size] = 0; switch (json_option->value->type) { case json_type_null: case json_type_false: { if (!handle_backend_setting_int( option_name, 0, session)) { return false; } return true; } break; case json_type_true: { if (!handle_backend_setting_int( option_name, 1, session)) { return false; } return true; } break; case json_type_number: { int64_t num = json_option_extract_int64(json_option); if (!handle_backend_setting_int( option_name, num, session)) { return false; } return true; } break; case json_type_string: { struct json_string_s *jsopt = (struct json_string_s *) json_option->value->payload; char value[jsopt->string_size + 1]; strncpy(value, jsopt->string, jsopt->string_size); value[jsopt->string_size] = 0; if (!strcmp(option_name, "dtsize")) { /* extract resolution width and height */ char *ptr = strchr(value, 'x'); if (!ptr) return false; char w[jsopt->string_size], h[jsopt->string_size]; { char *ptr2 = value; int i; for (i = 0; ptr2 != ptr; ++i, ++ptr2) { w[i] = ptr2[0]; } w[i] = 0; ptr++; for (i = 0; ptr[i]; ++i) { h[i] = ptr[i]; } h[i] = 0; { if (!handle_backend_setting_int( "width", atoll(w), session)) { return false; } if (!handle_backend_setting_int( "height", atoll(h), session)) { return false; } return true; } } } else { if (!handle_backend_setting_string( option_name, value, session)) { return false; } return true; } } break; default: { const char *msg = "handle_backend_setting:" " unsupported json value type"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); return true; } break; } { char buf[128]; snprintf(buf, 127, "handle_backend_setting:" " unhandled option: %s", option_name); log_msg((const uint8_t *)buf, strlen(buf), wrdp_log_level_warning, 0); } return true; } static void handle_json_session_option( struct json_object_element_s *json_option, ws_session *session) { switch (json_option->value->type) { case json_type_object: { if (memmem(json_option->name->string, json_option->name->string_size, "session_settings", strlen("session_settings"))) { struct json_object_s *obj = json_option->value->payload; struct json_object_element_s *jsoption = obj->start; bool unhandled_option = false; while (jsoption && !unhandled_option) { unhandled_option = !handle_session_setting_element( jsoption, session); if (unhandled_option) break; jsoption = jsoption->next; } return; } } break; default: { const char *msg = "handle_json_option: unsupported json value type" " (not object)"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); return; } break; } } static void handle_json_session_options(struct json_value_s *root, ws_session *session) { struct json_object_s *object = (struct json_object_s *)root->payload; struct json_object_element_s *option = object->start; while (option) { handle_json_session_option(option, session); option = option->next; } } static void handle_json_backend_option( struct json_object_element_s *json_option, ws_session *session) { switch (json_option->value->type) { case json_type_object: { if (memmem(json_option->name->string, json_option->name->string_size, "backend_settings", strlen("backend_settings"))) { struct json_object_s *obj = json_option->value->payload; struct json_object_element_s *jsoption = obj->start; bool unhandled_option = false; while (jsoption && !unhandled_option) { unhandled_option = !handle_backend_setting_element( jsoption, session); if (unhandled_option) break; jsoption = jsoption->next; } } } break; default: { const char *msg = "handle_json_option: unsupported json value type" " (not object)"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); } break; } } static void handle_json_backend_options(struct json_value_s *root, ws_session *session) { struct json_object_s *object = (struct json_object_s *)root->payload; struct json_object_element_s *option = object->start; while (option) { handle_json_backend_option(option, session); option = option->next; } } static bool ws_handle_json_settings_array( char *json_buf, size_t json_buf_len, ws_session *session) { struct json_parse_result_s res = {0}; struct json_value_s *root = json_parse_ex( json_buf, json_buf_len, json_parse_flags_allow_json5, 0, 0, &res); #ifdef DEBUG { const size_t msg_len = json_buf_len + 256; char msg[msg_len]; snprintf(msg, msg_len, "%s: %s\n", "received json data:\n", json_buf); log_msg( (const uint8_t *)msg, strlen(msg), wrdp_log_level_trace, 0); } #endif if (!root) { const char *msg = "Failed to parse auth data json"; log_msg( (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); return false; } /* handle json data * assume what this is one level array * of json options for backend or session*/ handle_json_session_options(root, session); handle_json_backend_options(root, session); if (!session->backend_module_name) { free(session->token_base64); session->token_base64 = 0; free(root); return false; } if (!(session->token_verified) && session->token_base64) { if (!token_check(session)) { free(root); return false; } } free(root); return true; } bool ws_handle_token_reply_json( uint8_t *_json_buf, ws_session *session, void *userdata) { char *json_buf = (char *)_json_buf; task_info *info = 0, *old_info = session->task_info; if (!ws_handle_json_settings_array(json_buf, strlen(json_buf), session)) { return false; } if (!backend_get(session->backend_module_name, session)) { free(session->backend_module_name); session->backend_module_name = 0; return false; } else { backend_fill_settings(session); } free(session->attach_sid_base64); session->attach_sid_base64 = 0; info = session->task_info; if (!info->backend) { const char *msg = "backend type(proto)setting does not set"; log_msg( (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); return false; } if (info == old_info && !info->backend->callbacks_module->init( info->backend->backend_internals)) { return false; } session->session_state = ws_session_started; { uint8_t *data = 0; size_t data_size = curl_prepare_post_request_data(&data, session); curl_request_info *request = curl_init_request( session, curl_request_type_post, data, data_size, 0, 0, 0); free(data); curl_request(request); } return true; } static bool validate_msg_size( size_t msg_size, size_t expected_size, ws_input_codes msg_code) { if (msg_size > expected_size) { char buf[128]; snprintf(buf, 127, "ws_protocol: message size is: %ld, expected size" " is: %ld for message type %d", msg_size, expected_size, msg_code); log_msg((const uint8_t *)buf, strlen(buf), wrdp_log_level_warning, 0); return true; } else if (msg_size < expected_size) { char buf[128]; snprintf(buf, 127, "ws_protocol: message size is: %ld, expected size" " is: %ld for message type %d", msg_size, expected_size, msg_code); log_msg( (const uint8_t *)buf, strlen(buf), wrdp_log_level_error, 0); return false; } return true; } static void generate_random_session_sid(ws_session *session) { size_t sid_len = (4 * (64 / 3)) + 4 + 2, encoded_len = 0; char *sid_base64 = calloc(sid_len, sizeof(char)), *ptr; uint8_t random_data[64]; random_bytes(random_data, 64); base64_url_encode( random_data, 64, (uint8_t *)sid_base64, sid_len, &encoded_len); sid_base64[encoded_len] = 0; ptr = realloc(sid_base64, encoded_len + 1); if (!ptr) { /* if realloc failed we still can use preveously allocated * memory */ perror("realloc"); } else { sid_base64 = ptr; } session->sid_base64 = sid_base64; } bool ws_handle_message( const struct wslay_event_on_msg_recv_arg *msg, ws_session *session) { uint32_t hcode1 = 0; task_info *info = session->task_info; if (msg->msg_length < 4) { const char *msg = "Error: websocket message is too short"; log_msg( (const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); return false; } memcpy(&hcode1, msg->msg, 4); if (session->session_state == ws_session_initial) { switch (hcode1) { case ws_in_credential_json: { size_t str_len = (msg->msg_length - 4) / 4; char *str = malloc(str_len + 1); if (!str) { perror("malloc"); return false; } { size_t i, pos = 4; for (i = 0; i < str_len; ++i, pos += 4) { str[i] = msg->msg[pos]; } } str[str_len] = 0; if (!ws_handle_json_settings_array( str, str_len, session)) { free(str); return false; } free(str); /* backend and task_info created/set inside * "ws_handle_json_settings_array", refresh * pointer here */ info = session->task_info; /* TODO: DEBUG BEGIN*/ //use_token = true; /* DEBUG END */ if (!session->token_base64) { if (!info || !info->backend) { const char *msg = "backend " "type(proto)setting" "does not set"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); return false; } if (!info->backend->callbacks_module ->init( info->backend ->backend_internals)) { return false; } /* token is not used, we need to * generate random sid */ generate_random_session_sid(session); session->session_state = ws_session_started; if (session->token_base64) { uint8_t *data = 0; size_t data_size = curl_prepare_post_request_data( &data, session); curl_request_info *request = curl_init_request(session, curl_request_type_post, data, data_size, 0, 0, 0); free(data); curl_request(request); } } else { curl_request_info *request = curl_init_request(session, curl_request_type_get, 0, 0, ws_handle_token_reply_json, 0, 0); bool ok = curl_request(request); if (!ok) { return false; } return true; } } break; default: break; } } else if (session->session_state == ws_session_started) { switch (hcode1) { case ws_in_specialcomb: { uint32_t *hcode2 = (uint32_t *)msg->msg + 1; ws_input_keycomb comb; if (!info->backend->callbacks_input->kcomb) break; if (!validate_msg_size(msg->msg_length - 4, sizeof(uint32_t), ws_in_specialcomb)) { return false; } switch (*hcode2) { case 0: comb = ws_keycomb_ctrlaltdel_press; break; case 1: comb = ws_keycomb_alttab_press; break; case 2: comb = ws_keycomb_alttab_release; break; default: /* this should never happen */ comb = ws_keycomb_ctrlaltdel_press; break; } if (!info->backend->callbacks_input->kcomb( comb, info->backend->backend_internals)) { return false; } } break; case ws_in_mouse: { if (!info->backend->callbacks_input->mouse) break; typedef struct { uint32_t op; uint32_t flags; uint32_t x; uint32_t y; } wsmsg; if (!validate_msg_size(msg->msg_length, sizeof(wsmsg), ws_in_mouse)) { return false; } const wsmsg *m = (const wsmsg *)msg->msg; ws_input_mouse mi; mi.flags = m->flags; mi.x = m->x; mi.y = m->y; if (!info->backend->callbacks_input->mouse( mi, info->backend->backend_internals)) { return false; } } break; case ws_in_kupdown: { if (!info->backend->callbacks_input->kupdown) break; typedef struct { uint32_t op; uint32_t down; uint32_t code; } wsmsg; if (!validate_msg_size(msg->msg_length, sizeof(wsmsg), ws_in_kupdown)) { return false; } const wsmsg *m = (const wsmsg *)msg->msg; ws_input_kupdown mi; mi.code = m->code; mi.down = m->down; if (!info->backend->callbacks_input->kupdown( mi, info->backend->backend_internals)) { return false; } } break; case ws_in_kpress: { if (!info->backend->callbacks_input->kpress) break; typedef struct { uint32_t op; uint32_t shiftstate; uint32_t code; } wsmsg; if (!validate_msg_size(msg->msg_length, sizeof(wsmsg), ws_in_kpress)) { return false; } const wsmsg *m = (const wsmsg *)msg->msg; ws_input_kpress mi; mi.code = m->code; mi.shiftstate = m->shiftstate; if (!info->backend->callbacks_input->kpress( mi, info->backend->backend_internals)) { return false; } } break; case ws_in_unicode: { if (!info->backend->callbacks_input->unicode) break; ws_input_unicode mu; mu.length = msg->msg_length - 4; mu.input = (const uint32_t *)msg->msg + 4; if (!info->backend->callbacks_input->unicode( mu, info->backend->backend_internals)) { return false; } } break; case ws_in_clipbrd_data_request: { if (!info->backend->callbacks_clipbrd ->request_data) { const char *msg = "wrdp_backend_cb_clipboard->" "request_data" " not set"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); break; } if (!validate_msg_size(msg->msg_length - 4, sizeof(uint8_t), ws_in_clipbrd_data_request)) { return false; } wrdp_backend_clipbrd_data_request req; req.format = *((uint8_t *)(msg->msg + 4)); if (!info->backend->callbacks_clipbrd ->request_data(&req, info->backend->backend_internals)) { return false; } } break; case ws_in_clipbrd_changed: { if (!info->backend->callbacks_clipbrd ->data_changed) { const char *msg = "wrdp_backend_cb_clipboard->" "data_changed not set"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); break; } wrdp_backend_clipbrd_fmts fmts; fmts.count = msg->msg_length - 4; fmts.formats = (uint8_t *)msg->msg + 4; if (!info->backend->callbacks_clipbrd ->data_changed(&fmts, info->backend->backend_internals)) { return false; } } break; case ws_in_clipbrd_data: { if (!info->backend->callbacks_clipbrd ->send_data) { const char *msg = "wrdp_backend_cb_" "clipboard->send_data" " not set"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); break; } wrdp_backend_clipbrd_data data; data.size = msg->msg_length - 4; data.data = (uint8_t *)msg->msg + 4; if (!info->backend->callbacks_clipbrd ->send_data(&data, info->backend->backend_internals)) { return false; } } break; case ws_in_ft_request: { if (!info->backend->callbacks_ft->request) { const char *msg = "wrdp_backend_cb_filetransfer->" "request not set"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); break; } if ((msg->msg_length - 4) < sizeof(uint16_t)) { const char *msg = "ws_protocol: ws_in_ft_request " "message" " is too small"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); return false; } wrdp_backend_ft_file_request req; if (!validate_msg_size(msg->msg_length - 4, 4 + 8 + 8, ws_in_ft_request)) { return false; } memcpy(&(req.file_id), msg->msg + 4, 4); memcpy(&(req.req_size), msg->msg + 4 + 4, 8); memcpy(&(req.file_offset), msg->msg + 4 + 4 + 8, 8); if (!info->backend->callbacks_ft->request( &req, info->backend->backend_internals)) { return false; } } break; case ws_in_ft_chunk: { if (!info->backend->callbacks_ft->chunk) { const char *msg = "wrdp_backend_cb_" "filetransfer->chunk" " not set"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); break; } if ((msg->msg_length - 4) < sizeof(wrdp_backend_ft_chunk)) { const char *msg = "ws_protocol: ws_in_ft_chunk " "message" " is too small"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_error, 0); return false; } const wrdp_backend_ft_chunk *c = (wrdp_backend_ft_chunk *)(msg->msg + 4); if (!validate_msg_size(msg->msg_length - 4, sizeof(wrdp_backend_ft_chunk) + c->size, ws_in_ft_chunk)) { return false; } const uint8_t *data = (const uint8_t *)(msg->msg + 4 + sizeof(wrdp_backend_ft_chunk)); if (!info->backend->callbacks_ft->chunk(c, data, info->backend->backend_internals)) { return false; } } break; case ws_in_ft_finished: { if (!info->backend->callbacks_ft->finish) { const char *msg = "wrdp_backend_cb_" "filetransfer->finish" " not set"; log_msg((const uint8_t *)msg, strlen(msg), wrdp_log_level_warning, 0); break; } if (!validate_msg_size(msg->msg_length - 4, sizeof(wrdp_backend_ft_status), ws_in_ft_finished)) { return false; } const wrdp_backend_ft_status *f = (wrdp_backend_ft_status *)(msg->msg + 4); if (!info->backend->callbacks_ft->finish( f, info->backend->backend_internals)) { return false; } } break; default: { char buf[128]; snprintf(buf, 127, "protocol error, unsupported message code" " %d", hcode1); log_msg((const uint8_t *)buf, strlen(buf), wrdp_log_level_warning, 0); return false; } break; } } return true; } static void ev_con_w_cb(struct ev_loop *loop, ev_io *w, int revents) { ws_session *s = w->data; if (wslay_event_want_write(s->wslay_ctx)) { wslay_event_send(s->wslay_ctx); } if (!wslay_event_want_write(s->wslay_ctx)) { ev_io_stop(loop, w); } } static void ws_send(const uint8_t *buf, size_t buf_size, uint8_t opcode, void *_ws_session) { ws_session *session = _ws_session; task_info *info = session->task_info; wrdp_thpool_task *t = info->wrdp_thpool_task; struct wslay_event_msg msgarg; if (info->stopped) { return; } msgarg.opcode = opcode; msgarg.msg = buf; msgarg.msg_length = buf_size; wslay_event_queue_msg(session->wslay_ctx, &msgarg); if (wslay_event_want_write(session->wslay_ctx) && !ev_is_active(&(session->ev_con_fd_w))) { ev_io *io = &(session->ev_con_fd_w); ev_io_init(io, ev_con_w_cb, session->connection_fd, EV_WRITE); io->data = session; ev_io_start(t->thread->ev_th_loop, io); } } static void ws_send_impl( const uint8_t *buf, size_t buf_size, void *_task_info, uint8_t type) { task_info *info = _task_info; SLIST_HEAD(sessions_head, ws_session_list_entry_s) *sessions_list_head_p = info->backend->sessions_list_head; if (!SLIST_EMPTY(sessions_list_head_p)) { for (struct ws_session_list_entry_s *s = SLIST_FIRST(sessions_list_head_p); s; s = SLIST_NEXT(s, entries)) { if (s->session) ws_send(buf, buf_size, type, s->session); } } } void ws_send_text(const uint8_t *buf, size_t buf_size, void *_task_info) { ws_send_impl(buf, buf_size, _task_info, 1); } void ws_send_binary(const uint8_t *buf, size_t buf_size, void *_task_info) { ws_send_impl(buf, buf_size, _task_info, 2); } uint8_t * ws_pack_msg(const uint8_t *buf, size_t buf_size, uint32_t msg_code) { /* TODO: avoid copying data somehow */ uint8_t *msg = malloc(buf_size + 4); if (!msg) { /* TODO: handle error */ perror("malloc"); return 0; } memcpy(msg, &msg_code, 4); if (buf) { memcpy(msg + 4, buf, buf_size); } return msg; }