1 1.1 christos /* 2 1.1 christos * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved. 3 1.1 christos * 4 1.1 christos * Licensed under the Apache License 2.0 (the "License"). You may not use 5 1.1 christos * this file except in compliance with the License. You can obtain a copy 6 1.1 christos * in the file LICENSE in the source distribution or at 7 1.1 christos * https://www.openssl.org/source/license.html 8 1.1 christos */ 9 1.1 christos 10 1.1 christos #include "internal/common.h" 11 1.1 christos #include "internal/quic_ssl.h" 12 1.1 christos #include "internal/quic_reactor_wait_ctx.h" 13 1.1 christos #include <openssl/ssl.h> 14 1.1 christos #include <openssl/err.h> 15 1.1 christos #include "../ssl_local.h" 16 1.1 christos #include "poll_builder.h" 17 1.1 christos 18 1.1 christos #if defined(_AIX) 19 1.1 christos /* 20 1.1 christos * Some versions of AIX define macros for events and revents for use when 21 1.1 christos * accessing pollfd structures (see Github issue #24236). That interferes 22 1.1 christos * with our use of these names here. We simply undef them. 23 1.1 christos */ 24 1.1.1.2 christos #undef revents 25 1.1.1.2 christos #undef events 26 1.1 christos #endif 27 1.1 christos 28 1.1 christos #define ITEM_N(items, stride, n) \ 29 1.1.1.2 christos (*(SSL_POLL_ITEM *)((char *)(items) + (n) * (stride))) 30 1.1 christos 31 1.1.1.2 christos #define FAIL_FROM(n) \ 32 1.1.1.2 christos do { \ 33 1.1.1.2 christos size_t j; \ 34 1.1.1.2 christos \ 35 1.1.1.2 christos for (j = (n); j < num_items; ++j) \ 36 1.1.1.2 christos ITEM_N(items, stride, j).revents = 0; \ 37 1.1.1.2 christos \ 38 1.1.1.2 christos ok = 0; \ 39 1.1.1.2 christos goto out; \ 40 1.1 christos } while (0) 41 1.1 christos 42 1.1.1.2 christos #define FAIL_ITEM(idx) \ 43 1.1.1.2 christos do { \ 44 1.1.1.2 christos size_t idx_ = (idx); \ 45 1.1.1.2 christos \ 46 1.1.1.2 christos ITEM_N(items, stride, idx_).revents = SSL_POLL_EVENT_F; \ 47 1.1.1.2 christos ++result_count; \ 48 1.1.1.2 christos FAIL_FROM(idx_ + 1); \ 49 1.1 christos } while (0) 50 1.1 christos 51 1.1 christos #ifndef OPENSSL_NO_QUIC 52 1.1 christos static int poll_translate_ssl_quic(SSL *ssl, 53 1.1.1.2 christos QUIC_REACTOR_WAIT_CTX *wctx, 54 1.1.1.2 christos RIO_POLL_BUILDER *rpb, 55 1.1.1.2 christos uint64_t events, 56 1.1.1.2 christos int *abort_blocking) 57 1.1 christos { 58 1.1 christos BIO_POLL_DESCRIPTOR rd, wd; 59 1.1 christos int fd1 = -1, fd2 = -1, fd_nfy = -1; 60 1.1 christos int fd1_r = 0, fd1_w = 0, fd2_w = 0; 61 1.1 christos 62 1.1 christos if (SSL_net_read_desired(ssl)) { 63 1.1 christos if (!SSL_get_rpoll_descriptor(ssl, &rd)) { 64 1.1 christos ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED, 65 1.1.1.2 christos "SSL_poll requires the network BIOs underlying " 66 1.1.1.2 christos "a QUIC SSL object provide poll descriptors"); 67 1.1 christos return 0; 68 1.1 christos } 69 1.1 christos 70 1.1 christos if (rd.type != BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD) { 71 1.1 christos ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED, 72 1.1.1.2 christos "SSL_poll requires the poll descriptors of the " 73 1.1.1.2 christos "network BIOs underlying a QUIC SSL object be " 74 1.1.1.2 christos "of socket type"); 75 1.1 christos return 0; 76 1.1 christos } 77 1.1 christos 78 1.1.1.2 christos fd1 = rd.value.fd; 79 1.1 christos fd1_r = 1; 80 1.1 christos } 81 1.1 christos 82 1.1 christos if (SSL_net_write_desired(ssl)) { 83 1.1 christos if (!SSL_get_wpoll_descriptor(ssl, &wd)) { 84 1.1 christos ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED, 85 1.1.1.2 christos "SSL_poll requires the network BIOs underlying " 86 1.1.1.2 christos "a QUIC SSL object provide poll descriptors"); 87 1.1 christos return 0; 88 1.1 christos } 89 1.1 christos 90 1.1 christos if (wd.type != BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD) { 91 1.1 christos ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED, 92 1.1.1.2 christos "SSL_poll requires the poll descriptors of the " 93 1.1.1.2 christos "network BIOs underlying a QUIC SSL object be " 94 1.1.1.2 christos "of socket type"); 95 1.1 christos return 0; 96 1.1 christos } 97 1.1 christos 98 1.1.1.2 christos fd2 = wd.value.fd; 99 1.1 christos fd2_w = 1; 100 1.1 christos } 101 1.1 christos 102 1.1 christos if (fd2 == fd1) { 103 1.1 christos fd2 = -1; 104 1.1 christos fd1_w = fd2_w; 105 1.1 christos } 106 1.1 christos 107 1.1 christos if (fd1 != -1) 108 1.1 christos if (!ossl_rio_poll_builder_add_fd(rpb, fd1, fd1_r, fd1_w)) 109 1.1 christos return 0; 110 1.1 christos 111 1.1 christos if (fd2 != -1 && fd2_w) 112 1.1.1.2 christos if (!ossl_rio_poll_builder_add_fd(rpb, fd2, /*r = */ 0, fd2_w)) 113 1.1 christos return 0; 114 1.1 christos 115 1.1 christos /* 116 1.1 christos * Add the notifier FD for the QUIC domain this SSL object is a part of (if 117 1.1 christos * there is one). This ensures we get woken up if another thread calls into 118 1.1 christos * that QUIC domain and some readiness event relevant to the SSL_poll call 119 1.1 christos * on this thread arises without the underlying network socket ever becoming 120 1.1 christos * readable. 121 1.1 christos */ 122 1.1 christos fd_nfy = ossl_quic_get_notifier_fd(ssl); 123 1.1 christos if (fd_nfy != -1) { 124 1.1 christos uint64_t revents = 0; 125 1.1 christos 126 1.1.1.2 christos if (!ossl_rio_poll_builder_add_fd(rpb, fd_nfy, /*r = */ 1, /*w = */ 0)) 127 1.1 christos return 0; 128 1.1 christos 129 1.1 christos /* Tell QUIC domain we need to receive notifications. */ 130 1.1 christos ossl_quic_enter_blocking_section(ssl, wctx); 131 1.1 christos 132 1.1 christos /* 133 1.1 christos * Only after the above call returns is it guaranteed that any readiness 134 1.1 christos * events will cause the above notifier to become readable. Therefore, 135 1.1 christos * it is possible the object became ready after our initial 136 1.1 christos * poll_readout() call (before we determined that nothing was ready and 137 1.1 christos * we needed to block). We now need to do another readout, in which case 138 1.1 christos * blocking is to be aborted. 139 1.1 christos */ 140 1.1.1.2 christos if (!ossl_quic_conn_poll_events(ssl, events, /*do_tick = */ 0, &revents)) { 141 1.1 christos ossl_quic_leave_blocking_section(ssl, wctx); 142 1.1 christos return 0; 143 1.1 christos } 144 1.1 christos 145 1.1 christos if (revents != 0) { 146 1.1 christos ossl_quic_leave_blocking_section(ssl, wctx); 147 1.1 christos *abort_blocking = 1; 148 1.1 christos return 1; 149 1.1 christos } 150 1.1 christos } 151 1.1 christos 152 1.1 christos return 1; 153 1.1 christos } 154 1.1 christos 155 1.1 christos static void postpoll_translation_cleanup_ssl_quic(SSL *ssl, 156 1.1.1.2 christos QUIC_REACTOR_WAIT_CTX *wctx) 157 1.1 christos { 158 1.1 christos if (ossl_quic_get_notifier_fd(ssl) != -1) 159 1.1 christos ossl_quic_leave_blocking_section(ssl, wctx); 160 1.1 christos } 161 1.1 christos 162 1.1 christos static void postpoll_translation_cleanup(SSL_POLL_ITEM *items, 163 1.1.1.2 christos size_t num_items, 164 1.1.1.2 christos size_t stride, 165 1.1.1.2 christos QUIC_REACTOR_WAIT_CTX *wctx) 166 1.1 christos { 167 1.1 christos SSL_POLL_ITEM *item; 168 1.1 christos SSL *ssl; 169 1.1 christos size_t i; 170 1.1 christos 171 1.1 christos for (i = 0; i < num_items; ++i) { 172 1.1 christos item = &ITEM_N(items, stride, i); 173 1.1 christos 174 1.1 christos switch (item->desc.type) { 175 1.1 christos case BIO_POLL_DESCRIPTOR_TYPE_SSL: 176 1.1 christos ssl = item->desc.value.ssl; 177 1.1 christos if (ssl == NULL) 178 1.1 christos break; 179 1.1 christos 180 1.1 christos switch (ssl->type) { 181 1.1.1.2 christos #ifndef OPENSSL_NO_QUIC 182 1.1 christos case SSL_TYPE_QUIC_LISTENER: 183 1.1 christos case SSL_TYPE_QUIC_CONNECTION: 184 1.1 christos case SSL_TYPE_QUIC_XSO: 185 1.1 christos postpoll_translation_cleanup_ssl_quic(ssl, wctx); 186 1.1 christos break; 187 1.1.1.2 christos #endif 188 1.1 christos default: 189 1.1 christos break; 190 1.1 christos } 191 1.1 christos break; 192 1.1 christos default: 193 1.1 christos break; 194 1.1 christos } 195 1.1 christos } 196 1.1 christos } 197 1.1 christos 198 1.1 christos static int poll_translate(SSL_POLL_ITEM *items, 199 1.1.1.2 christos size_t num_items, 200 1.1.1.2 christos size_t stride, 201 1.1.1.2 christos QUIC_REACTOR_WAIT_CTX *wctx, 202 1.1.1.2 christos RIO_POLL_BUILDER *rpb, 203 1.1.1.2 christos OSSL_TIME *p_earliest_wakeup_deadline, 204 1.1.1.2 christos int *abort_blocking, 205 1.1.1.2 christos size_t *p_result_count) 206 1.1 christos { 207 1.1 christos int ok = 1; 208 1.1 christos SSL_POLL_ITEM *item; 209 1.1 christos size_t result_count = 0; 210 1.1 christos SSL *ssl; 211 1.1 christos OSSL_TIME earliest_wakeup_deadline = ossl_time_infinite(); 212 1.1 christos struct timeval timeout; 213 1.1 christos int is_infinite = 0; 214 1.1 christos size_t i; 215 1.1 christos 216 1.1 christos for (i = 0; i < num_items; ++i) { 217 1.1 christos item = &ITEM_N(items, stride, i); 218 1.1 christos 219 1.1 christos switch (item->desc.type) { 220 1.1 christos case BIO_POLL_DESCRIPTOR_TYPE_SSL: 221 1.1 christos ssl = item->desc.value.ssl; 222 1.1 christos if (ssl == NULL) 223 1.1 christos /* NULL items are no-ops and have revents reported as 0 */ 224 1.1 christos break; 225 1.1 christos 226 1.1 christos switch (ssl->type) { 227 1.1.1.2 christos #ifndef OPENSSL_NO_QUIC 228 1.1 christos case SSL_TYPE_QUIC_LISTENER: 229 1.1 christos case SSL_TYPE_QUIC_CONNECTION: 230 1.1 christos case SSL_TYPE_QUIC_XSO: 231 1.1 christos if (!poll_translate_ssl_quic(ssl, wctx, rpb, item->events, 232 1.1.1.2 christos abort_blocking)) 233 1.1 christos FAIL_ITEM(i); 234 1.1 christos 235 1.1 christos if (*abort_blocking) 236 1.1 christos return 1; 237 1.1 christos 238 1.1 christos if (!SSL_get_event_timeout(ssl, &timeout, &is_infinite)) 239 1.1 christos FAIL_ITEM(i++); /* need to clean up this item too */ 240 1.1 christos 241 1.1 christos if (!is_infinite) 242 1.1 christos earliest_wakeup_deadline 243 1.1 christos = ossl_time_min(earliest_wakeup_deadline, 244 1.1.1.2 christos ossl_time_add(ossl_time_now(), 245 1.1.1.2 christos ossl_time_from_timeval(timeout))); 246 1.1 christos 247 1.1 christos break; 248 1.1.1.2 christos #endif 249 1.1 christos 250 1.1 christos default: 251 1.1 christos ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED, 252 1.1.1.2 christos "SSL_poll currently only supports QUIC SSL " 253 1.1.1.2 christos "objects"); 254 1.1 christos FAIL_ITEM(i); 255 1.1 christos } 256 1.1 christos break; 257 1.1 christos 258 1.1 christos case BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD: 259 1.1 christos ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED, 260 1.1.1.2 christos "SSL_poll currently does not support polling " 261 1.1.1.2 christos "sockets"); 262 1.1 christos FAIL_ITEM(i); 263 1.1 christos 264 1.1 christos default: 265 1.1 christos ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED, 266 1.1.1.2 christos "SSL_poll does not support unknown poll descriptor " 267 1.1.1.2 christos "type %d", 268 1.1.1.2 christos item->desc.type); 269 1.1 christos FAIL_ITEM(i); 270 1.1 christos } 271 1.1 christos } 272 1.1 christos 273 1.1 christos out: 274 1.1 christos if (!ok) 275 1.1 christos postpoll_translation_cleanup(items, i, stride, wctx); 276 1.1 christos 277 1.1 christos *p_earliest_wakeup_deadline = earliest_wakeup_deadline; 278 1.1 christos *p_result_count = result_count; 279 1.1 christos return ok; 280 1.1 christos } 281 1.1 christos 282 1.1 christos static int poll_block(SSL_POLL_ITEM *items, 283 1.1.1.2 christos size_t num_items, 284 1.1.1.2 christos size_t stride, 285 1.1.1.2 christos OSSL_TIME user_deadline, 286 1.1.1.2 christos size_t *p_result_count) 287 1.1 christos { 288 1.1 christos int ok = 0, abort_blocking = 0; 289 1.1 christos RIO_POLL_BUILDER rpb; 290 1.1 christos QUIC_REACTOR_WAIT_CTX wctx; 291 1.1 christos OSSL_TIME earliest_wakeup_deadline; 292 1.1 christos 293 1.1 christos /* 294 1.1 christos * Blocking is somewhat involved and involves the following steps: 295 1.1 christos * 296 1.1 christos * - Translation, in which the various logical items (SSL objects, etc.) to 297 1.1 christos * be polled are translated into items an OS polling API understands. 298 1.1 christos * 299 1.1 christos * - Synchronisation bookkeeping. This ensures that we can be woken up 300 1.1 christos * not just by readiness of any underlying file descriptor distilled from 301 1.1 christos * the provided items but also by other threads, which might do work 302 1.1 christos * on a relevant QUIC object to cause the object to be ready without the 303 1.1 christos * underlying file descriptor ever becoming ready from our perspective. 304 1.1 christos * 305 1.1 christos * - The blocking call to the OS polling API. 306 1.1 christos * 307 1.1 christos * - Currently we do not do reverse translation but simply call 308 1.1 christos * poll_readout() again to read out all readiness state for all 309 1.1 christos * descriptors which the user passed. 310 1.1 christos * 311 1.1 christos * TODO(QUIC POLLING): In the future we will do reverse translation here 312 1.1 christos * also to facilitate a more efficient readout. 313 1.1 christos */ 314 1.1 christos ossl_quic_reactor_wait_ctx_init(&wctx); 315 1.1 christos ossl_rio_poll_builder_init(&rpb); 316 1.1 christos 317 1.1 christos if (!poll_translate(items, num_items, stride, &wctx, &rpb, 318 1.1.1.2 christos &earliest_wakeup_deadline, 319 1.1.1.2 christos &abort_blocking, 320 1.1.1.2 christos p_result_count)) 321 1.1 christos goto out; 322 1.1 christos 323 1.1 christos if (abort_blocking) 324 1.1 christos goto out; 325 1.1 christos 326 1.1 christos earliest_wakeup_deadline = ossl_time_min(earliest_wakeup_deadline, 327 1.1.1.2 christos user_deadline); 328 1.1 christos 329 1.1 christos ok = ossl_rio_poll_builder_poll(&rpb, earliest_wakeup_deadline); 330 1.1 christos 331 1.1 christos postpoll_translation_cleanup(items, num_items, stride, &wctx); 332 1.1 christos 333 1.1 christos out: 334 1.1 christos ossl_rio_poll_builder_cleanup(&rpb); 335 1.1 christos ossl_quic_reactor_wait_ctx_cleanup(&wctx); 336 1.1 christos return ok; 337 1.1 christos } 338 1.1 christos #endif 339 1.1 christos 340 1.1 christos static int poll_readout(SSL_POLL_ITEM *items, 341 1.1.1.2 christos size_t num_items, 342 1.1.1.2 christos size_t stride, 343 1.1.1.2 christos int do_tick, 344 1.1.1.2 christos size_t *p_result_count) 345 1.1 christos { 346 1.1 christos int ok = 1; 347 1.1 christos size_t i, result_count = 0; 348 1.1 christos SSL_POLL_ITEM *item; 349 1.1 christos SSL *ssl; 350 1.1 christos #ifndef OPENSSL_NO_QUIC 351 1.1 christos uint64_t events; 352 1.1 christos #endif 353 1.1 christos uint64_t revents; 354 1.1 christos 355 1.1 christos for (i = 0; i < num_items; ++i) { 356 1.1.1.2 christos item = &ITEM_N(items, stride, i); 357 1.1 christos #ifndef OPENSSL_NO_QUIC 358 1.1.1.2 christos events = item->events; 359 1.1 christos #endif 360 1.1 christos revents = 0; 361 1.1 christos 362 1.1 christos switch (item->desc.type) { 363 1.1 christos case BIO_POLL_DESCRIPTOR_TYPE_SSL: 364 1.1 christos ssl = item->desc.value.ssl; 365 1.1 christos if (ssl == NULL) 366 1.1 christos /* NULL items are no-ops and have revents reported as 0 */ 367 1.1 christos break; 368 1.1 christos 369 1.1 christos switch (ssl->type) { 370 1.1 christos #ifndef OPENSSL_NO_QUIC 371 1.1 christos case SSL_TYPE_QUIC_LISTENER: 372 1.1 christos case SSL_TYPE_QUIC_CONNECTION: 373 1.1 christos case SSL_TYPE_QUIC_XSO: 374 1.1 christos if (!ossl_quic_conn_poll_events(ssl, events, do_tick, &revents)) 375 1.1 christos /* above call raises ERR */ 376 1.1 christos FAIL_ITEM(i); 377 1.1 christos 378 1.1 christos if (revents != 0) 379 1.1 christos ++result_count; 380 1.1 christos 381 1.1 christos break; 382 1.1 christos #endif 383 1.1 christos 384 1.1 christos default: 385 1.1 christos ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED, 386 1.1.1.2 christos "SSL_poll currently only supports QUIC SSL " 387 1.1.1.2 christos "objects"); 388 1.1 christos FAIL_ITEM(i); 389 1.1 christos } 390 1.1 christos break; 391 1.1 christos case BIO_POLL_DESCRIPTOR_TYPE_SOCK_FD: 392 1.1 christos ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED, 393 1.1.1.2 christos "SSL_poll currently does not support polling " 394 1.1.1.2 christos "sockets"); 395 1.1 christos FAIL_ITEM(i); 396 1.1 christos default: 397 1.1 christos ERR_raise_data(ERR_LIB_SSL, SSL_R_POLL_REQUEST_NOT_SUPPORTED, 398 1.1.1.2 christos "SSL_poll does not support unknown poll descriptor " 399 1.1.1.2 christos "type %d", 400 1.1.1.2 christos item->desc.type); 401 1.1 christos FAIL_ITEM(i); 402 1.1 christos } 403 1.1 christos 404 1.1 christos item->revents = revents; 405 1.1 christos } 406 1.1 christos 407 1.1 christos out: 408 1.1 christos if (p_result_count != NULL) 409 1.1 christos *p_result_count = result_count; 410 1.1 christos 411 1.1 christos return ok; 412 1.1 christos } 413 1.1 christos 414 1.1 christos int SSL_poll(SSL_POLL_ITEM *items, 415 1.1.1.2 christos size_t num_items, 416 1.1.1.2 christos size_t stride, 417 1.1.1.2 christos const struct timeval *timeout, 418 1.1.1.2 christos uint64_t flags, 419 1.1.1.2 christos size_t *p_result_count) 420 1.1 christos { 421 1.1 christos int ok = 1; 422 1.1 christos size_t result_count = 0; 423 1.1 christos ossl_unused int do_tick = ((flags & SSL_POLL_FLAG_NO_HANDLE_EVENTS) == 0); 424 1.1 christos OSSL_TIME deadline; 425 1.1 christos 426 1.1 christos /* Trivial case. */ 427 1.1 christos if (num_items == 0) { 428 1.1 christos if (timeout == NULL) 429 1.1 christos goto out; 430 1.1 christos OSSL_sleep(ossl_time2ms(ossl_time_from_timeval(*timeout))); 431 1.1 christos goto out; 432 1.1 christos } 433 1.1 christos 434 1.1 christos /* Convert timeout to deadline. */ 435 1.1 christos if (timeout == NULL) 436 1.1 christos deadline = ossl_time_infinite(); 437 1.1 christos else if (timeout->tv_sec == 0 && timeout->tv_usec == 0) 438 1.1 christos deadline = ossl_time_zero(); 439 1.1 christos else 440 1.1 christos deadline = ossl_time_add(ossl_time_now(), 441 1.1.1.2 christos ossl_time_from_timeval(*timeout)); 442 1.1 christos 443 1.1 christos /* Loop until we have something to report. */ 444 1.1 christos for (;;) { 445 1.1 christos /* Readout phase - poll current state of each item. */ 446 1.1 christos if (!poll_readout(items, num_items, stride, do_tick, &result_count)) { 447 1.1 christos ok = 0; 448 1.1 christos goto out; 449 1.1 christos } 450 1.1 christos 451 1.1 christos /* 452 1.1 christos * If we got anything, or we are in immediate mode (zero timeout), or 453 1.1 christos * the deadline has expired, we're done. 454 1.1 christos */ 455 1.1 christos if (result_count > 0 456 1.1 christos || ossl_time_is_zero(deadline) /* (avoids now call) */ 457 1.1 christos || ossl_time_compare(ossl_time_now(), deadline) >= 0) 458 1.1 christos goto out; 459 1.1 christos 460 1.1 christos /* 461 1.1 christos * Block until something is ready. Ignore NO_HANDLE_EVENTS from this 462 1.1 christos * point onwards. 463 1.1 christos */ 464 1.1 christos do_tick = 1; 465 1.1 christos #ifndef OPENSSL_NO_QUIC 466 1.1 christos if (!poll_block(items, num_items, stride, deadline, &result_count)) { 467 1.1 christos ok = 0; 468 1.1 christos goto out; 469 1.1 christos } 470 1.1 christos #endif 471 1.1 christos } 472 1.1 christos 473 1.1 christos /* TODO(QUIC POLLING): Support for polling FDs */ 474 1.1 christos 475 1.1 christos out: 476 1.1 christos if (p_result_count != NULL) 477 1.1 christos *p_result_count = result_count; 478 1.1 christos 479 1.1 christos return ok; 480 1.1 christos } 481