open5gs/lib/sbi/client.c

889 lines
24 KiB
C

/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
*
* This file is part of Open5GS.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ogs-app.h"
#include "ogs-sbi.h"
#include "curl/curl.h"
typedef struct sockinfo_s {
ogs_poll_t *poll;
curl_socket_t sockfd;
int action;
CURL *easy;
ogs_sbi_client_t *client;
} sockinfo_t;
typedef struct connection_s {
ogs_lnode_t lnode;
void *data;
char *method;
int num_of_header;
char **headers;
struct curl_slist *header_list;
char *content;
char *memory;
size_t size;
char *location;
ogs_timer_t *timer;
CURL *easy;
char error[CURL_ERROR_SIZE];
ogs_sbi_client_t *client;
ogs_sbi_client_cb_f client_cb;
} connection_t;
static OGS_POOL(client_pool, ogs_sbi_client_t);
static OGS_POOL(sockinfo_pool, sockinfo_t);
static OGS_POOL(connection_pool, connection_t);
static size_t write_cb(void *contents, size_t size, size_t nmemb, void *data);
static size_t header_cb(void *ptr, size_t size, size_t nmemb, void *data);
static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp);
static int multi_timer_cb(CURLM *multi, long timeout_ms, void *cbp);
static void multi_timer_expired(void *data);
static void connection_timer_expired(void *data);
static void connection_remove_all(ogs_sbi_client_t *client);
void ogs_sbi_client_init(int num_of_sockinfo_pool, int num_of_connection_pool)
{
curl_global_init(CURL_GLOBAL_DEFAULT);
ogs_list_init(&ogs_sbi_self()->client_list);
ogs_pool_init(&client_pool, ogs_app()->pool.nf);
ogs_pool_init(&sockinfo_pool, num_of_sockinfo_pool);
ogs_pool_init(&connection_pool, num_of_connection_pool);
}
void ogs_sbi_client_final(void)
{
ogs_sbi_client_remove_all();
ogs_pool_final(&client_pool);
ogs_pool_final(&sockinfo_pool);
ogs_pool_final(&connection_pool);
curl_global_cleanup();
}
ogs_sbi_client_t *ogs_sbi_client_add(ogs_sockaddr_t *addr)
{
ogs_sbi_client_t *client = NULL;
CURLM *multi = NULL;
ogs_assert(addr);
ogs_pool_alloc(&client_pool, &client);
ogs_assert(client);
memset(client, 0, sizeof(ogs_sbi_client_t));
client->reference_count++;
ogs_trace("ogs_sbi_client_add()");
ogs_assert(OGS_OK == ogs_copyaddrinfo(&client->node.addr, addr));
ogs_list_init(&client->connection_list);
client->t_curl = ogs_timer_add(
ogs_app()->timer_mgr, multi_timer_expired, client);
multi = client->multi = curl_multi_init();
ogs_assert(multi);
curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, client);
curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
curl_multi_setopt(multi, CURLMOPT_TIMERDATA, client);
ogs_list_add(&ogs_sbi_self()->client_list, client);
return client;
}
void ogs_sbi_client_remove(ogs_sbi_client_t *client)
{
ogs_assert(client);
/* ogs_sbi_client_t is always created with reference context */
ogs_assert(client->reference_count > 0);
ogs_trace("client->reference_count = %d", client->reference_count);
client->reference_count--;
if (client->reference_count > 0)
return;
ogs_trace("ogs_sbi_client_remove()");
ogs_list_remove(&ogs_sbi_self()->client_list, client);
connection_remove_all(client);
ogs_assert(client->t_curl);
ogs_timer_delete(client->t_curl);
client->t_curl = NULL;
ogs_assert(client->multi);
curl_multi_cleanup(client->multi);
ogs_assert(client->node.addr);
ogs_freeaddrinfo(client->node.addr);
ogs_pool_free(&client_pool, client);
}
void ogs_sbi_client_remove_all(void)
{
ogs_sbi_client_t *client = NULL, *next_client = NULL;
ogs_list_for_each_safe(&ogs_sbi_self()->client_list, next_client, client)
ogs_sbi_client_remove(client);
}
ogs_sbi_client_t *ogs_sbi_client_find(ogs_sockaddr_t *addr)
{
ogs_sbi_client_t *client = NULL;
ogs_assert(addr);
ogs_list_for_each(&ogs_sbi_self()->client_list, client) {
if (ogs_sockaddr_is_equal(client->node.addr, addr) == true)
break;
}
return client;
}
void ogs_sbi_client_stop(ogs_sbi_client_t *client)
{
connection_t *conn = NULL;
ogs_assert(client);
ogs_list_for_each(&client->connection_list, conn) {
ogs_assert(conn->client_cb);
conn->client_cb(OGS_DONE, NULL, conn->data);
}
}
void ogs_sbi_client_stop_all(void)
{
ogs_sbi_client_t *client = NULL;
ogs_list_for_each(&ogs_sbi_self()->client_list, client)
ogs_sbi_client_stop(client);
}
#define mycase(code) \
case code: s = OGS_STRINGIFY(code)
static void mcode_or_die(const char *where, CURLMcode code)
{
if(CURLM_OK != code) {
const char *s;
switch(code) {
mycase(CURLM_BAD_HANDLE); break;
mycase(CURLM_BAD_EASY_HANDLE); break;
mycase(CURLM_OUT_OF_MEMORY); break;
mycase(CURLM_INTERNAL_ERROR); break;
mycase(CURLM_UNKNOWN_OPTION); break;
mycase(CURLM_LAST); break;
default: s = "CURLM_unknown"; break;
mycase(CURLM_BAD_SOCKET);
ogs_error("ERROR: %s returns %s", where, s);
/* ignore this error */
return;
}
ogs_fatal("ERROR: %s returns %s", where, s);
ogs_assert_if_reached();
}
}
static char *add_params_to_uri(CURL *easy, char *uri, ogs_hash_t *params)
{
ogs_hash_index_t *hi;
int has_params = 0;
const char *fp = "?", *np = "&";
ogs_assert(easy);
ogs_assert(uri);
ogs_assert(params);
ogs_assert(ogs_hash_count(params));
has_params = (strchr(uri, '?') != NULL);
for (hi = ogs_hash_first(params); hi; hi = ogs_hash_next(hi)) {
const char *key = NULL;
char *key_esc = NULL;
char *val = NULL;
char *val_esc = NULL;
key = ogs_hash_this_key(hi);
ogs_assert(key);
val = ogs_hash_this_val(hi);
ogs_assert(val);
key_esc = curl_easy_escape(easy, key, 0);
ogs_assert(key_esc);
val_esc = curl_easy_escape(easy, val, 0);
ogs_assert(val_esc);
if (!has_params) {
uri = ogs_mstrcatf(uri, "%s%s=%s", fp, key_esc, val_esc);
ogs_expect(uri);
has_params = 1;
} else {
uri = ogs_mstrcatf(uri, "%s%s=%s", np, key_esc, val_esc);
ogs_expect(uri);
}
curl_free(val_esc);
curl_free(key_esc);
}
return uri;
}
static void _connection_remove(connection_t *conn);
static connection_t *connection_add(
ogs_sbi_client_t *client, ogs_sbi_client_cb_f client_cb,
ogs_sbi_request_t *request, void *data)
{
ogs_hash_index_t *hi;
int i;
connection_t *conn = NULL;
CURLMcode rc;
ogs_assert(client);
ogs_assert(client_cb);
ogs_assert(request);
ogs_assert(request->h.method);
ogs_pool_alloc(&connection_pool, &conn);
ogs_expect_or_return_val(conn, NULL);
memset(conn, 0, sizeof(connection_t));
conn->client = client;
conn->client_cb = client_cb;
conn->data = data;
conn->method = ogs_strdup(request->h.method);
if (!conn->method) {
ogs_error("conn->method is NULL");
_connection_remove(conn);
return NULL;
}
conn->num_of_header = ogs_hash_count(request->http.headers);
if (conn->num_of_header) {
conn->headers = ogs_calloc(conn->num_of_header, sizeof(char *));
if (!conn->headers) {
ogs_error("conn->headers is NULL");
_connection_remove(conn);
return NULL;
}
for (hi = ogs_hash_first(request->http.headers), i = 0;
hi && i < conn->num_of_header; hi = ogs_hash_next(hi), i++) {
const char *key = ogs_hash_this_key(hi);
char *val = ogs_hash_this_val(hi);
conn->headers[i] = ogs_msprintf("%s: %s", key, val);
if (!conn->headers[i]) {
ogs_error("conn->headers[i=%d] is NULL", i);
_connection_remove(conn);
return NULL;
}
conn->header_list = curl_slist_append(
conn->header_list, conn->headers[i]);
}
}
conn->timer = ogs_timer_add(
ogs_app()->timer_mgr, connection_timer_expired, conn);
if (!conn->timer) {
ogs_error("conn->timer is NULL");
_connection_remove(conn);
return NULL;
}
/* If http response is not received within deadline,
* Open5GS will discard this request. */
ogs_timer_start(conn->timer,
ogs_app()->time.message.sbi.connection_deadline);
conn->easy = curl_easy_init();
if (!conn->easy) {
ogs_error("conn->easy is NULL");
_connection_remove(conn);
return NULL;
}
if (ogs_hash_count(request->http.params)) {
char *uri = add_params_to_uri(conn->easy,
request->h.uri, request->http.params);
if (!uri) {
ogs_error("add_params_to_uri() failed");
_connection_remove(conn);
return NULL;
}
request->h.uri = uri;
}
/* HTTP Method */
if (strcmp(request->h.method, OGS_SBI_HTTP_METHOD_PUT) == 0 ||
strcmp(request->h.method, OGS_SBI_HTTP_METHOD_PATCH) == 0 ||
strcmp(request->h.method, OGS_SBI_HTTP_METHOD_DELETE) == 0 ||
strcmp(request->h.method, OGS_SBI_HTTP_METHOD_POST) == 0) {
curl_easy_setopt(conn->easy,
CURLOPT_CUSTOMREQUEST, request->h.method);
if (request->http.content) {
conn->content = ogs_memdup(
request->http.content, request->http.content_length);
if (!conn->content) {
ogs_error("conn->content is NULL");
_connection_remove(conn);
return NULL;
}
curl_easy_setopt(conn->easy,
CURLOPT_POSTFIELDS, conn->content);
curl_easy_setopt(conn->easy,
CURLOPT_POSTFIELDSIZE, request->http.content_length);
#if 1 /* Disable HTTP/1.1 100 Continue : Use "Expect:" in libcurl */
conn->header_list = curl_slist_append(
conn->header_list, "Expect:");
#else
curl_easy_setopt(conn->easy, CURLOPT_EXPECT_100_TIMEOUT_MS, 0L);
#endif
ogs_debug("SENDING...[%d]", (int)request->http.content_length);
if (request->http.content_length)
ogs_debug("%s", request->http.content);
}
}
curl_easy_setopt(conn->easy, CURLOPT_HTTPHEADER, conn->header_list);
#if 1 /* Use HTTP2 */
curl_easy_setopt(conn->easy,
CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
#endif
ogs_list_add(&client->connection_list, conn);
curl_easy_setopt(conn->easy, CURLOPT_URL, request->h.uri);
curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
curl_easy_setopt(conn->easy, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(conn->easy, CURLOPT_WRITEDATA, conn);
curl_easy_setopt(conn->easy, CURLOPT_HEADERFUNCTION, header_cb);
curl_easy_setopt(conn->easy, CURLOPT_HEADERDATA, conn);
curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
ogs_assert(client->multi);
rc = curl_multi_add_handle(client->multi, conn->easy);
mcode_or_die("connection_add: curl_multi_add_handle", rc);
return conn;
}
static void _connection_remove(connection_t *conn)
{
int i;
ogs_assert(conn);
if (conn->content)
ogs_free(conn->content);
if (conn->location)
ogs_free(conn->location);
if (conn->memory)
ogs_free(conn->memory);
if (conn->easy)
curl_easy_cleanup(conn->easy);
if (conn->timer)
ogs_timer_delete(conn->timer);
if (conn->num_of_header) {
for (i = 0; i < conn->num_of_header; i++)
if (conn->headers[i])
ogs_free(conn->headers[i]);
ogs_free(conn->headers);
}
curl_slist_free_all(conn->header_list);
if (conn->method)
ogs_free(conn->method);
ogs_pool_free(&connection_pool, conn);
}
static void connection_remove(connection_t *conn)
{
ogs_sbi_client_t *client = NULL;
ogs_assert(conn);
client = conn->client;
ogs_assert(client);
ogs_list_remove(&client->connection_list, conn);
ogs_assert(client->multi);
curl_multi_remove_handle(client->multi, conn->easy);
_connection_remove(conn);
}
static void connection_remove_all(ogs_sbi_client_t *client)
{
connection_t *conn = NULL, *next_conn = NULL;
ogs_assert(client);
ogs_list_for_each_safe(&client->connection_list, next_conn, conn)
connection_remove(conn);
}
static void connection_timer_expired(void *data)
{
connection_t *conn = NULL;
conn = data;
ogs_assert(conn);
ogs_error("Connection timer expired");
ogs_assert(conn->client_cb);
conn->client_cb(OGS_TIMEUP, NULL, conn->data);
connection_remove(conn);
}
static void check_multi_info(ogs_sbi_client_t *client)
{
CURLM *multi = NULL;
CURLMsg *resource;
int pending;
CURL *easy = NULL;
CURLcode res;
connection_t *conn = NULL;
ogs_sbi_response_t *response = NULL;
ogs_assert(client);
multi = client->multi;
ogs_assert(multi);
while ((resource = curl_multi_info_read(multi, &pending))) {
char *url;
char *content_type = NULL;
long res_status;
ogs_assert(resource);
switch (resource->msg) {
case CURLMSG_DONE:
easy = resource->easy_handle;
ogs_assert(easy);
curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
ogs_assert(conn);
curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &url);
curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &res_status);
curl_easy_getinfo(easy, CURLINFO_CONTENT_TYPE, &content_type);
res = resource->data.result;
if (res == CURLE_OK) {
response = ogs_sbi_response_new();
ogs_assert(response);
response->status = res_status;
ogs_assert(conn->method);
response->h.method = ogs_strdup(conn->method);
ogs_assert(response->h.method);
/* remove https://localhost:8000 */
response->h.uri = ogs_strdup(url);
ogs_assert(response->h.uri);
ogs_debug("[%d:%s] %s",
response->status, response->h.method, response->h.uri);
if (conn->memory) {
response->http.content =
ogs_memdup(conn->memory, conn->size + 1);
ogs_assert(response->http.content);
response->http.content_length = conn->size;
ogs_assert(response->http.content_length);
}
ogs_debug("RECEIVED[%d]", (int)response->http.content_length);
if (response->http.content_length && response->http.content)
ogs_debug("%s", response->http.content);
if (content_type)
ogs_sbi_header_set(response->http.headers,
OGS_SBI_CONTENT_TYPE, content_type);
if (conn->location)
ogs_sbi_header_set(response->http.headers,
OGS_SBI_LOCATION, conn->location);
} else
ogs_warn("[%d] %s", res, conn->error);
ogs_assert(conn->client_cb);
conn->client_cb(res == CURLE_OK ? OGS_OK : OGS_ERROR,
response, conn->data);
connection_remove(conn);
break;
default:
ogs_error("Unknown CURL resource[%d]", resource->msg);
break;
}
}
}
bool ogs_sbi_client_send_reqmem_persistent(
ogs_sbi_client_t *client, ogs_sbi_client_cb_f client_cb,
ogs_sbi_request_t *request, void *data)
{
connection_t *conn = NULL;
ogs_assert(client);
ogs_assert(request);
if (request->h.uri == NULL) {
request->h.uri = ogs_sbi_client_uri(client, &request->h);
ogs_expect_or_return_val(request->h.uri, false);
}
ogs_debug("[%s] %s", request->h.method, request->h.uri);
conn = connection_add(client, client_cb, request, data);
ogs_expect_or_return_val(conn, false);
return true;
}
bool ogs_sbi_client_send_request(
ogs_sbi_client_t *client, ogs_sbi_client_cb_f client_cb,
ogs_sbi_request_t *request, void *data)
{
bool rc;
ogs_assert(client);
ogs_assert(request);
rc = ogs_sbi_client_send_reqmem_persistent(
client, client_cb, request, data);
ogs_expect(rc == true);
ogs_sbi_request_free(request);
return rc;
}
bool ogs_sbi_scp_send_reqmem_persistent(
ogs_sbi_client_t *client, ogs_sbi_client_cb_f client_cb,
ogs_sbi_request_t *request, void *data)
{
ogs_sbi_nf_instance_t *scp_instance = NULL;
connection_t *conn = NULL;
char *apiroot = NULL;
ogs_assert(client);
ogs_assert(request);
scp_instance = ogs_sbi_self()->scp_instance;
if (scp_instance) {
/*
* In case of indirect communication using SCP,
* add 3gpp-Sbi-Target-apiRoot to HTTP header and
* change CLIENT instance to SCP.
*/
apiroot = ogs_sbi_client_apiroot(client);
ogs_assert(apiroot);
ogs_sbi_header_set(request->http.headers,
OGS_SBI_CUSTOM_TARGET_APIROOT, apiroot);
ogs_free(apiroot);
client = scp_instance->client;
ogs_assert(client);
}
if (request->h.uri == NULL) {
/*
* Regardless of direct or indirect communication,
* if there is no URI, we automatically creates a URI
* with Client Address and request->h
*/
request->h.uri = ogs_sbi_client_uri(client, &request->h);
ogs_assert(request->h.uri);
ogs_debug("[%s] %s", request->h.method, request->h.uri);
} else if (scp_instance) {
/*
* In case of indirect communication using SCP,
* If the full URI is already defined, change full URI to SCP as below.
*
* OLD: http://127.0.0.5:7777/nnrf-nfm/v1/nf-status-notify
* NEW: https://scp.open5gs.org/nnrf-nfm/v1/nf-status-notify
*/
char *path = NULL;
char *old = NULL;
old = request->h.uri;
apiroot = ogs_sbi_client_apiroot(client);
ogs_assert(apiroot);
path = ogs_sbi_getpath_from_uri(request->h.uri);
ogs_assert(path);
request->h.uri = ogs_msprintf("%s/%s", apiroot, path);
ogs_assert(request->h.uri);
ogs_free(apiroot);
ogs_free(path);
ogs_free(old);
ogs_debug("[%s] %s", request->h.method, request->h.uri);
}
conn = connection_add(client, client_cb, request, data);
ogs_expect_or_return_val(conn, false);
return true;
}
bool ogs_sbi_scp_send_request(
ogs_sbi_client_t *client, ogs_sbi_client_cb_f client_cb,
ogs_sbi_request_t *request, void *data)
{
bool rc;
ogs_assert(client);
ogs_assert(request);
rc = ogs_sbi_scp_send_reqmem_persistent(client, client_cb, request, data);
ogs_expect(rc == true);
ogs_sbi_request_free(request);
return rc;
}
static size_t write_cb(void *contents, size_t size, size_t nmemb, void *data)
{
size_t realsize = 0;
connection_t *conn = NULL;
char *ptr = NULL;
conn = data;
ogs_assert(conn);
realsize = size * nmemb;
ptr = ogs_realloc(conn->memory, conn->size + realsize + 1);
if(!ptr) {
ogs_fatal("not enough memory (realloc returned NULL)");
ogs_assert_if_reached();
return 0;
}
conn->memory = ptr;
memcpy(&(conn->memory[conn->size]), contents, realsize);
conn->size += realsize;
conn->memory[conn->size] = 0;
return realsize;
}
static size_t header_cb(void *ptr, size_t size, size_t nmemb, void *data)
{
connection_t *conn = NULL;
conn = data;
ogs_assert(conn);
if (ogs_strncasecmp(ptr, OGS_SBI_LOCATION, strlen(OGS_SBI_LOCATION)) == 0) {
/* ptr : "Location: http://xxx/xxx/xxx\r\n"
We need to truncate "Location" + ": " + "\r\n" in 'ptr' string */
int len = strlen(ptr) - strlen(OGS_SBI_LOCATION) - 2 - 2;
if (len) {
/* Only copy http://xxx/xxx/xxx" from 'ptr' string */
conn->location = ogs_memdup(
(char *)ptr + strlen(OGS_SBI_LOCATION) + 2, len+1);
ogs_assert(conn->location);
conn->location[len] = 0;
}
}
return (nmemb*size);
}
static void event_cb(short when, ogs_socket_t fd, void *data)
{
sockinfo_t *sockinfo = NULL;
ogs_sbi_client_t *client = NULL;
CURLM *multi = NULL;
CURLMcode rc;
int action = ((when & OGS_POLLIN) ? CURL_CSELECT_IN : 0) |
((when & OGS_POLLOUT) ? CURL_CSELECT_OUT : 0);
sockinfo = data;
ogs_assert(sockinfo);
client = sockinfo->client;
ogs_assert(client);
multi = client->multi;
ogs_assert(multi);
rc = curl_multi_socket_action(multi, fd, action, &client->still_running);
mcode_or_die("event_cb: curl_multi_socket_action", rc);
check_multi_info(client);
if (client->still_running <= 0) {
ogs_timer_t *timer;
timer = client->t_curl;
if (timer)
ogs_timer_stop(timer);
}
}
/* Assign information to a sockinfo_t structure */
static void sock_set(sockinfo_t *sockinfo, curl_socket_t s,
CURL *e, int act, ogs_sbi_client_t *client)
{
int kind = ((act & CURL_POLL_IN) ? OGS_POLLIN : 0) |
((act & CURL_POLL_OUT) ? OGS_POLLOUT : 0);
if (sockinfo->sockfd)
ogs_pollset_remove(sockinfo->poll);
sockinfo->sockfd = s;
sockinfo->action = act;
sockinfo->easy = e;
sockinfo->poll = ogs_pollset_add(
ogs_app()->pollset, kind, s, event_cb, sockinfo);
ogs_assert(sockinfo->poll);
}
/* Initialize a new sockinfo_t structure */
static void sock_new(curl_socket_t s,
CURL *easy, int action, ogs_sbi_client_t *client)
{
sockinfo_t *sockinfo = NULL;
CURLM *multi = NULL;
ogs_assert(client);
multi = client->multi;
ogs_assert(multi);
ogs_pool_alloc(&sockinfo_pool, &sockinfo);
ogs_assert(sockinfo);
memset(sockinfo, 0, sizeof(sockinfo_t));
sockinfo->client = client;
sock_set(sockinfo, s, easy, action, client);
curl_multi_assign(multi, s, sockinfo);
}
/* Clean up the sockinfo_t structure */
static void sock_free(sockinfo_t *sockinfo, ogs_sbi_client_t *client)
{
ogs_assert(sockinfo);
ogs_assert(sockinfo->poll);
ogs_pollset_remove(sockinfo->poll);
ogs_pool_free(&sockinfo_pool, sockinfo);
}
/* CURLMOPT_SOCKETFUNCTION */
static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
{
ogs_sbi_client_t *client = (ogs_sbi_client_t *)cbp;
sockinfo_t *sockinfo = (sockinfo_t *) sockp;
if (what == CURL_POLL_REMOVE) {
sock_free(sockinfo, client);
} else {
if (!sockinfo) {
sock_new(s, e, what, client);
} else {
sock_set(sockinfo, s, e, what, client);
}
}
return 0;
}
static void multi_timer_expired(void *data)
{
CURLMcode rc;
ogs_sbi_client_t *client = NULL;
CURLM *multi = NULL;
client = data;
ogs_assert(client);
multi = client->multi;
ogs_assert(multi);
rc = curl_multi_socket_action(
multi, CURL_SOCKET_TIMEOUT, 0, &client->still_running);
mcode_or_die("multi_timer_expired: curl_multi_socket_action", rc);
check_multi_info(client);
}
static int multi_timer_cb(CURLM *multi, long timeout_ms, void *cbp)
{
ogs_sbi_client_t *client = NULL;
ogs_timer_t *timer = NULL;
client = cbp;
ogs_assert(client);
timer = client->t_curl;
ogs_assert(timer);
if (timeout_ms > 0) {
ogs_timer_start(timer, ogs_time_from_msec(timeout_ms));
} else if (timeout_ms == 0) {
/* libcurl wants us to timeout now.
* The closest we can do is to schedule the timer to fire in 1 us. */
ogs_timer_start(timer, 1);
} else {
ogs_timer_stop(timer);
}
return 0;
}