1281 lines
38 KiB
C
1281 lines
38 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 "yuarel.h"
|
|
|
|
#include <netinet/tcp.h>
|
|
#include <nghttp2/nghttp2.h>
|
|
|
|
#define USE_SEND_DATA_WITH_NO_COPY 1
|
|
|
|
static void server_init(int num_of_session_pool, int num_of_stream_pool);
|
|
static void server_final(void);
|
|
|
|
static int server_start(ogs_sbi_server_t *server,
|
|
int (*cb)(ogs_sbi_request_t *request, void *data));
|
|
static void server_stop(ogs_sbi_server_t *server);
|
|
|
|
static bool server_send_response(
|
|
ogs_sbi_stream_t *stream, ogs_sbi_response_t *response);
|
|
|
|
static ogs_sbi_server_t *server_from_stream(ogs_sbi_stream_t *data);
|
|
|
|
const ogs_sbi_server_actions_t ogs_nghttp2_server_actions = {
|
|
server_init,
|
|
server_final,
|
|
|
|
server_start,
|
|
server_stop,
|
|
|
|
server_send_response,
|
|
server_from_stream,
|
|
};
|
|
|
|
struct h2_settings {
|
|
uint32_t max_concurrent_streams;
|
|
bool enable_push;
|
|
};
|
|
|
|
typedef struct ogs_sbi_session_s {
|
|
ogs_lnode_t lnode;
|
|
|
|
ogs_sock_t *sock;
|
|
ogs_sockaddr_t *addr;
|
|
struct {
|
|
ogs_poll_t *read;
|
|
ogs_poll_t *write;
|
|
} poll;
|
|
|
|
nghttp2_session *session;
|
|
ogs_list_t write_queue;
|
|
|
|
ogs_sbi_server_t *server;
|
|
ogs_list_t stream_list;
|
|
int32_t last_stream_id;
|
|
|
|
struct h2_settings settings;
|
|
} ogs_sbi_session_t;
|
|
|
|
typedef struct ogs_sbi_stream_s {
|
|
ogs_lnode_t lnode;
|
|
|
|
int32_t stream_id;
|
|
ogs_sbi_request_t *request;
|
|
|
|
ogs_sbi_session_t *session;
|
|
} ogs_sbi_stream_t;
|
|
|
|
static void session_remove(ogs_sbi_session_t *sbi_sess);
|
|
static void session_remove_all(ogs_sbi_server_t *server);
|
|
|
|
static void stream_remove(ogs_sbi_stream_t *stream);
|
|
|
|
static void accept_handler(short when, ogs_socket_t fd, void *data);
|
|
static void recv_handler(short when, ogs_socket_t fd, void *data);
|
|
|
|
static int session_set_callbacks(ogs_sbi_session_t *sbi_sess);
|
|
static int session_send_preface(ogs_sbi_session_t *sbi_sess);
|
|
static int session_send(ogs_sbi_session_t *sbi_sess);
|
|
static void session_write_to_buffer(
|
|
ogs_sbi_session_t *sbi_sess, ogs_pkbuf_t *pkbuf);
|
|
|
|
static OGS_POOL(session_pool, ogs_sbi_session_t);
|
|
static OGS_POOL(stream_pool, ogs_sbi_stream_t);
|
|
|
|
static void server_init(int num_of_session_pool, int num_of_stream_pool)
|
|
{
|
|
ogs_pool_init(&session_pool, num_of_session_pool);
|
|
ogs_pool_init(&stream_pool, num_of_stream_pool);
|
|
}
|
|
|
|
static void server_final(void)
|
|
{
|
|
ogs_pool_final(&stream_pool);
|
|
ogs_pool_final(&session_pool);
|
|
}
|
|
|
|
static int server_start(ogs_sbi_server_t *server,
|
|
int (*cb)(ogs_sbi_request_t *request, void *data))
|
|
{
|
|
char buf[OGS_ADDRSTRLEN];
|
|
ogs_sock_t *sock = NULL;
|
|
ogs_sockaddr_t *addr = NULL;
|
|
char *hostname = NULL;
|
|
|
|
addr = server->node.addr;
|
|
ogs_assert(addr);
|
|
|
|
sock = ogs_tcp_server(addr, server->node.option);
|
|
if (!sock) {
|
|
ogs_error("Cannot start SBI server");
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
server->node.sock = sock;
|
|
|
|
/* Setup callback function */
|
|
server->cb = cb;
|
|
|
|
/* Setup poll for server listening socket */
|
|
server->node.poll = ogs_pollset_add(ogs_app()->pollset,
|
|
OGS_POLLIN, sock->fd, accept_handler, server);
|
|
ogs_assert(server->node.poll);
|
|
|
|
hostname = ogs_gethostname(addr);
|
|
if (hostname)
|
|
ogs_info("nghttp2_server() [%s]:%d", hostname, OGS_PORT(addr));
|
|
else
|
|
ogs_info("nghttp2_server() [%s]:%d",
|
|
OGS_ADDR(addr, buf), OGS_PORT(addr));
|
|
|
|
return OGS_OK;
|
|
}
|
|
|
|
static void server_stop(ogs_sbi_server_t *server)
|
|
{
|
|
ogs_assert(server);
|
|
|
|
if (server->node.poll)
|
|
ogs_pollset_remove(server->node.poll);
|
|
|
|
if (server->node.sock)
|
|
ogs_sock_destroy(server->node.sock);
|
|
|
|
session_remove_all(server);
|
|
}
|
|
|
|
static void add_header(nghttp2_nv *nv, const char *key, const char *value)
|
|
{
|
|
nv->name = (uint8_t *)key;
|
|
nv->namelen = strlen(key);
|
|
nv->value = (uint8_t *)value;
|
|
nv->valuelen = strlen(value);
|
|
nv->flags = NGHTTP2_NV_FLAG_NONE;
|
|
}
|
|
|
|
static char status_string[600][4] = {
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"100", "101", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"200", "201", "202", "203", "204", "205", "206", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"300", "301", "302", "303", "304", "305", "306", "307", "308", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"400", "401", "402", "403", "404", "405", "406", "407", "408", "409",
|
|
"410", "411", "412", "413", "414", "415", "416", "417", "", "",
|
|
"", "421", "", "", "", "", "426", "", "428", "429", "", "431", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "451", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"500", "501", "502", "503", "504", "505", "", "", "", "", "", "511", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
|
|
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
|
|
};
|
|
|
|
#define DATE_STRLEN 128
|
|
static char *get_date_string(char *date)
|
|
{
|
|
static const char *const days[] = {
|
|
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
|
|
};
|
|
static const char *const mons[] = {
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
};
|
|
|
|
ogs_assert(date);
|
|
|
|
struct tm tm;
|
|
ogs_gmtime(ogs_time_sec(ogs_time_now()), &tm);
|
|
|
|
ogs_snprintf(date, DATE_STRLEN, "%3s, %02u %3s %04u %02u:%02u:%02u GMT",
|
|
days[tm.tm_wday % 7],
|
|
(unsigned int)tm.tm_mday,
|
|
mons[tm.tm_mon % 12],
|
|
(unsigned int)(1900 + tm.tm_year),
|
|
(unsigned int)tm.tm_hour,
|
|
(unsigned int)tm.tm_min,
|
|
(unsigned int)tm.tm_sec);
|
|
|
|
return date;
|
|
}
|
|
|
|
static ssize_t response_read_callback(nghttp2_session *session,
|
|
int32_t stream_id,
|
|
uint8_t *buf, size_t length,
|
|
uint32_t *data_flags,
|
|
nghttp2_data_source *source,
|
|
void *user_data)
|
|
{
|
|
#if USE_SEND_DATA_WITH_NO_COPY
|
|
int rv;
|
|
#endif
|
|
|
|
ogs_sbi_response_t *response = NULL;
|
|
ogs_sbi_stream_t *stream = NULL;
|
|
|
|
ogs_assert(session);
|
|
|
|
stream = nghttp2_session_get_stream_user_data(session, stream_id);
|
|
if (!stream) {
|
|
ogs_error("no stream [%d]", stream_id);
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
}
|
|
|
|
ogs_assert(source);
|
|
response = source->ptr;
|
|
ogs_assert(response);
|
|
|
|
ogs_assert(response->http.content);
|
|
ogs_assert(response->http.content_length);
|
|
|
|
#if USE_SEND_DATA_WITH_NO_COPY
|
|
*data_flags |= NGHTTP2_DATA_FLAG_NO_COPY;
|
|
#else
|
|
memcpy(buf, response->http.content, response->http.content_length);
|
|
#endif
|
|
|
|
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
|
|
|
|
#if USE_SEND_DATA_WITH_NO_COPY
|
|
rv = nghttp2_session_get_stream_remote_close(session, stream_id);
|
|
if (rv == 0) {
|
|
ogs_warn("nghttp2_session_get_stream_remote_close() failed");
|
|
nghttp2_submit_rst_stream(
|
|
session, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_NO_ERROR);
|
|
} else if (rv != 1) {
|
|
ogs_error("nghttp2_session_get_stream_remote_close() failed[%d]", rv);
|
|
}
|
|
#endif
|
|
|
|
return response->http.content_length;
|
|
}
|
|
|
|
static bool server_send_response(
|
|
ogs_sbi_stream_t *stream, ogs_sbi_response_t *response)
|
|
{
|
|
ogs_sbi_session_t *sbi_sess = NULL;
|
|
ogs_sock_t *sock = NULL;
|
|
ogs_socket_t fd = INVALID_SOCKET;
|
|
|
|
ogs_hash_index_t *hi;
|
|
nghttp2_nv *nva;
|
|
size_t nvlen;
|
|
int i, rv;
|
|
char datebuf[DATE_STRLEN];
|
|
char srv_version[128];
|
|
char clen[128];
|
|
|
|
ogs_assert(stream);
|
|
sbi_sess = stream->session;
|
|
ogs_assert(sbi_sess);
|
|
ogs_assert(sbi_sess->session);
|
|
ogs_assert(response);
|
|
|
|
sock = sbi_sess->sock;
|
|
ogs_assert(sock);
|
|
fd = sock->fd;
|
|
ogs_assert(fd != INVALID_SOCKET); /* Check if session is removed */
|
|
|
|
nvlen = 3; /* :status && server && date */
|
|
|
|
for (hi = ogs_hash_first(response->http.headers);
|
|
hi; hi = ogs_hash_next(hi))
|
|
nvlen++;
|
|
|
|
if (response->http.content && response->http.content_length)
|
|
nvlen++;
|
|
|
|
nva = ogs_calloc(nvlen, sizeof(nghttp2_nv));
|
|
ogs_expect_or_return_val(nva, false);
|
|
|
|
i = 0;
|
|
|
|
ogs_expect_or_return_val(response->status < 600, false);
|
|
ogs_assert(strlen(status_string[response->status]) == 3);
|
|
add_header(&nva[i++], ":status", status_string[response->status]);
|
|
|
|
ogs_snprintf(srv_version, sizeof(srv_version),
|
|
"Open5GS %s", ogs_app()->version ? ogs_app()->version : "TEST");
|
|
add_header(&nva[i++], "server", srv_version);
|
|
add_header(&nva[i++], "date", get_date_string(datebuf));
|
|
|
|
if (response->http.content && response->http.content_length) {
|
|
ogs_snprintf(clen, sizeof(clen),
|
|
"%d", (int)response->http.content_length);
|
|
add_header(&nva[i++], "content-length", clen);
|
|
}
|
|
|
|
for (hi = ogs_hash_first(response->http.headers);
|
|
hi; hi = ogs_hash_next(hi)) {
|
|
add_header(&nva[i++], ogs_hash_this_key(hi), ogs_hash_this_val(hi));
|
|
}
|
|
|
|
ogs_debug("STATUS [%d]", response->status);
|
|
|
|
if (response->http.content && response->http.content_length) {
|
|
nghttp2_data_provider data_prd;
|
|
|
|
data_prd.source.ptr = response;
|
|
data_prd.read_callback = response_read_callback;
|
|
|
|
ogs_debug("SENDING...: %d", (int)response->http.content_length);
|
|
ogs_debug("%s", response->http.content);
|
|
|
|
rv = nghttp2_submit_response(sbi_sess->session,
|
|
stream->stream_id, nva, nvlen, &data_prd);
|
|
} else {
|
|
rv = nghttp2_submit_response(sbi_sess->session,
|
|
stream->stream_id, nva, nvlen, NULL);
|
|
}
|
|
|
|
if (rv != OGS_OK) {
|
|
ogs_error("nghttp2_submit_response(%d) failed (%d:%s)",
|
|
(int)response->http.content_length,
|
|
rv, nghttp2_strerror(rv));
|
|
nghttp2_submit_rst_stream(
|
|
sbi_sess->session, NGHTTP2_FLAG_NONE, stream->stream_id, rv);
|
|
}
|
|
|
|
if (session_send(sbi_sess) != OGS_OK) {
|
|
ogs_error("session_send() failed");
|
|
session_remove(sbi_sess);
|
|
}
|
|
|
|
ogs_sbi_response_free(response);
|
|
ogs_free(nva);
|
|
|
|
return true;
|
|
}
|
|
|
|
static ogs_sbi_server_t *server_from_stream(ogs_sbi_stream_t *stream)
|
|
{
|
|
ogs_sbi_session_t *sbi_sess = NULL;
|
|
|
|
ogs_assert(stream);
|
|
sbi_sess = stream->session;
|
|
ogs_assert(sbi_sess);
|
|
ogs_assert(sbi_sess->server);
|
|
|
|
return sbi_sess->server;
|
|
}
|
|
|
|
static ogs_sbi_stream_t *stream_add(
|
|
ogs_sbi_session_t *sbi_sess, int32_t stream_id)
|
|
{
|
|
ogs_sbi_stream_t *stream = NULL;
|
|
|
|
ogs_assert(sbi_sess);
|
|
|
|
ogs_pool_alloc(&stream_pool, &stream);
|
|
ogs_expect_or_return_val(stream, NULL);
|
|
memset(stream, 0, sizeof(ogs_sbi_stream_t));
|
|
|
|
stream->request = ogs_sbi_request_new();
|
|
ogs_expect_or_return_val(stream->request, NULL);
|
|
|
|
stream->stream_id = stream_id;
|
|
sbi_sess->last_stream_id = stream_id;
|
|
|
|
stream->session = sbi_sess;
|
|
|
|
return stream;
|
|
}
|
|
|
|
static void stream_remove(ogs_sbi_stream_t *stream)
|
|
{
|
|
ogs_sbi_session_t *sbi_sess = NULL;
|
|
|
|
ogs_assert(stream);
|
|
sbi_sess = stream->session;
|
|
ogs_assert(sbi_sess);
|
|
|
|
ogs_list_remove(&sbi_sess->stream_list, stream);
|
|
|
|
ogs_assert(stream->request);
|
|
ogs_sbi_request_free(stream->request);
|
|
|
|
ogs_pool_free(&stream_pool, stream);
|
|
}
|
|
|
|
static void stream_remove_all(ogs_sbi_session_t *sbi_sess)
|
|
{
|
|
ogs_sbi_stream_t *stream = NULL, *next_stream = NULL;
|
|
|
|
ogs_assert(sbi_sess);
|
|
|
|
ogs_list_for_each_safe(&sbi_sess->stream_list, next_stream, stream)
|
|
stream_remove(stream);
|
|
}
|
|
|
|
static ogs_sbi_session_t *session_add(
|
|
ogs_sbi_server_t *server, ogs_sock_t *sock)
|
|
{
|
|
ogs_sbi_session_t *sbi_sess = NULL;
|
|
|
|
ogs_assert(server);
|
|
ogs_assert(sock);
|
|
|
|
ogs_pool_alloc(&session_pool, &sbi_sess);
|
|
ogs_expect_or_return_val(sbi_sess, NULL);
|
|
memset(sbi_sess, 0, sizeof(ogs_sbi_session_t));
|
|
|
|
sbi_sess->server = server;
|
|
sbi_sess->sock = sock;
|
|
|
|
sbi_sess->addr = ogs_calloc(1, sizeof(ogs_sockaddr_t));
|
|
ogs_expect_or_return_val(sbi_sess->addr, NULL);
|
|
memcpy(sbi_sess->addr, &sock->remote_addr, sizeof(ogs_sockaddr_t));
|
|
|
|
ogs_list_add(&server->session_list, sbi_sess);
|
|
|
|
return sbi_sess;
|
|
}
|
|
|
|
static void session_remove(ogs_sbi_session_t *sbi_sess)
|
|
{
|
|
ogs_sbi_server_t *server = NULL;
|
|
ogs_pkbuf_t *pkbuf = NULL, *next_pkbuf = NULL;
|
|
|
|
ogs_assert(sbi_sess);
|
|
server = sbi_sess->server;
|
|
ogs_assert(server);
|
|
|
|
ogs_list_remove(&server->session_list, sbi_sess);
|
|
|
|
stream_remove_all(sbi_sess);
|
|
|
|
ogs_assert(sbi_sess->poll.read);
|
|
ogs_pollset_remove(sbi_sess->poll.read);
|
|
|
|
if (sbi_sess->poll.write)
|
|
ogs_pollset_remove(sbi_sess->poll.write);
|
|
|
|
ogs_list_for_each_safe(&sbi_sess->write_queue, next_pkbuf, pkbuf) {
|
|
ogs_list_remove(&sbi_sess->write_queue, pkbuf);
|
|
ogs_pkbuf_free(pkbuf);
|
|
}
|
|
|
|
ogs_assert(sbi_sess->addr);
|
|
ogs_free(sbi_sess->addr);
|
|
|
|
ogs_assert(sbi_sess->sock);
|
|
ogs_sock_destroy(sbi_sess->sock);
|
|
|
|
ogs_pool_free(&session_pool, sbi_sess);
|
|
}
|
|
|
|
static void session_remove_all(ogs_sbi_server_t *server)
|
|
{
|
|
ogs_sbi_session_t *sbi_sess = NULL, *next_sbi_sess = NULL;
|
|
|
|
ogs_assert(server);
|
|
|
|
ogs_list_for_each_safe(&server->session_list, next_sbi_sess, sbi_sess)
|
|
session_remove(sbi_sess);
|
|
}
|
|
|
|
static void accept_handler(short when, ogs_socket_t fd, void *data)
|
|
{
|
|
ogs_sbi_server_t *server = data;
|
|
ogs_sbi_session_t *sbi_sess = NULL;
|
|
ogs_sock_t *sock = NULL;
|
|
ogs_sock_t *new = NULL;
|
|
|
|
int on;
|
|
|
|
ogs_assert(data);
|
|
ogs_assert(fd != INVALID_SOCKET);
|
|
|
|
sock = server->node.sock;
|
|
|
|
new = ogs_sock_accept(sock);
|
|
if (!new) {
|
|
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno, "accept() failed");
|
|
return;
|
|
}
|
|
ogs_assert(new->fd != INVALID_SOCKET);
|
|
|
|
on = 1;
|
|
if (setsockopt(new->fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) != 0) {
|
|
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
|
|
"setsockopt() for SCTP_NODELAY failed");
|
|
ogs_sock_destroy(new);
|
|
return;
|
|
}
|
|
|
|
sbi_sess = session_add(server, new);
|
|
ogs_assert(sbi_sess);
|
|
|
|
sbi_sess->poll.read = ogs_pollset_add(ogs_app()->pollset,
|
|
OGS_POLLIN, new->fd, recv_handler, sbi_sess);
|
|
ogs_assert(sbi_sess->poll.read);
|
|
|
|
if (session_set_callbacks(sbi_sess) != OGS_OK ||
|
|
session_send_preface(sbi_sess) != OGS_OK) {
|
|
ogs_error("session_add() failed");
|
|
session_remove(sbi_sess);
|
|
}
|
|
}
|
|
|
|
static void recv_handler(short when, ogs_socket_t fd, void *data)
|
|
{
|
|
char buf[OGS_ADDRSTRLEN];
|
|
ogs_sockaddr_t *addr = NULL;
|
|
|
|
ogs_sbi_session_t *sbi_sess = data;
|
|
ogs_pkbuf_t *pkbuf = NULL;
|
|
ssize_t readlen;
|
|
int n;
|
|
|
|
ogs_assert(sbi_sess);
|
|
ogs_assert(fd != INVALID_SOCKET);
|
|
addr = sbi_sess->addr;
|
|
ogs_assert(addr);
|
|
|
|
pkbuf = ogs_pkbuf_alloc(NULL, OGS_MAX_SDU_LEN);
|
|
ogs_assert(pkbuf);
|
|
|
|
n = ogs_recv(fd, pkbuf->data, OGS_MAX_SDU_LEN, 0);
|
|
if (n > 0) {
|
|
ogs_pkbuf_put(pkbuf, n);
|
|
|
|
ogs_assert(sbi_sess->session);
|
|
readlen = nghttp2_session_mem_recv(
|
|
sbi_sess->session, pkbuf->data, pkbuf->len);
|
|
if (readlen < 0) {
|
|
ogs_error("nghttp2_session_mem_recv() failed (%d:%s)",
|
|
(int)readlen, nghttp2_strerror((int)readlen));
|
|
session_remove(sbi_sess);
|
|
}
|
|
} else {
|
|
if (n < 0) {
|
|
if (errno != OGS_ECONNRESET)
|
|
ogs_log_message(OGS_LOG_ERROR, ogs_socket_errno,
|
|
"lost connection [%s]:%d",
|
|
OGS_ADDR(addr, buf), OGS_PORT(addr));
|
|
} else if (n == 0) {
|
|
ogs_debug("connection closed [%s]:%d",
|
|
OGS_ADDR(addr, buf), OGS_PORT(addr));
|
|
}
|
|
|
|
session_remove(sbi_sess);
|
|
}
|
|
|
|
ogs_pkbuf_free(pkbuf);
|
|
}
|
|
|
|
static int on_frame_recv(nghttp2_session *session,
|
|
const nghttp2_frame *frame, void *user_data);
|
|
static int on_stream_close(nghttp2_session *session, int32_t stream_id,
|
|
uint32_t error_code, void *user_data);
|
|
static int on_header(nghttp2_session *session,
|
|
const nghttp2_frame *frame,
|
|
nghttp2_rcbuf *name, nghttp2_rcbuf *value,
|
|
uint8_t flags, void *user_data);
|
|
static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
|
|
int32_t stream_id, const uint8_t *data,
|
|
size_t len, void *user_data);
|
|
static int error_callback(nghttp2_session *session,
|
|
const char *msg, size_t len, void *user_data);
|
|
static int on_invalid_frame_recv(nghttp2_session *session,
|
|
const nghttp2_frame *frame,
|
|
int error_code, void *user_data);
|
|
static int on_invalid_header(nghttp2_session *session,
|
|
const nghttp2_frame *frame,
|
|
const uint8_t *name, size_t namelen,
|
|
const uint8_t *value, size_t valuelen,
|
|
uint8_t flags, void *user_data);
|
|
static int on_begin_frame(nghttp2_session *session,
|
|
const nghttp2_frame_hd *hd, void *user_data);
|
|
static int on_begin_headers(nghttp2_session *session,
|
|
const nghttp2_frame *frame,
|
|
void *user_data);
|
|
#if USE_SEND_DATA_WITH_NO_COPY
|
|
static int on_send_data(nghttp2_session *session, nghttp2_frame *frame,
|
|
const uint8_t *framehd, size_t length,
|
|
nghttp2_data_source *source, void *user_data);
|
|
#else
|
|
static ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
|
|
size_t length, int flags, void *user_data);
|
|
#endif
|
|
|
|
static int session_set_callbacks(ogs_sbi_session_t *sbi_sess)
|
|
{
|
|
int rv;
|
|
nghttp2_session_callbacks *callbacks = NULL;
|
|
|
|
ogs_assert(sbi_sess);
|
|
|
|
rv = nghttp2_session_callbacks_new(&callbacks);
|
|
if (rv != 0) {
|
|
ogs_error("nghttp2_session_callbacks_new() failed (%d:%s)",
|
|
rv, nghttp2_strerror(rv));
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
nghttp2_session_callbacks_set_on_frame_recv_callback(
|
|
callbacks, on_frame_recv);
|
|
|
|
nghttp2_session_callbacks_set_on_stream_close_callback(
|
|
callbacks, on_stream_close);
|
|
|
|
nghttp2_session_callbacks_set_on_header_callback2(callbacks, on_header);
|
|
|
|
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
|
|
callbacks, on_data_chunk_recv);
|
|
|
|
nghttp2_session_callbacks_set_error_callback(callbacks, error_callback);
|
|
|
|
nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
|
|
callbacks, on_invalid_frame_recv);
|
|
|
|
nghttp2_session_callbacks_set_on_invalid_header_callback(
|
|
callbacks, on_invalid_header);
|
|
|
|
nghttp2_session_callbacks_set_on_begin_frame_callback(
|
|
callbacks, on_begin_frame);
|
|
|
|
nghttp2_session_callbacks_set_on_begin_headers_callback(
|
|
callbacks, on_begin_headers);
|
|
|
|
#if USE_SEND_DATA_WITH_NO_COPY
|
|
nghttp2_session_callbacks_set_send_data_callback(callbacks, on_send_data);
|
|
#else
|
|
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
|
|
#endif
|
|
|
|
rv = nghttp2_session_server_new(&sbi_sess->session, callbacks, sbi_sess);
|
|
if (rv != 0) {
|
|
ogs_error("nghttp2_session_callbacks_new() failed (%d:%s)",
|
|
rv, nghttp2_strerror(rv));
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
nghttp2_session_callbacks_del(callbacks);
|
|
|
|
return OGS_OK;
|
|
}
|
|
|
|
static int on_frame_recv(nghttp2_session *session,
|
|
const nghttp2_frame *frame, void *user_data)
|
|
{
|
|
int rv;
|
|
ogs_sbi_session_t *sbi_sess = user_data;
|
|
|
|
ogs_sbi_server_t *server = NULL;
|
|
ogs_sbi_stream_t *stream = NULL;
|
|
ogs_sbi_request_t *request = NULL;
|
|
|
|
ogs_assert(sbi_sess);
|
|
server = sbi_sess->server;
|
|
ogs_assert(server);
|
|
ogs_assert(server->cb);
|
|
|
|
ogs_assert(session);
|
|
ogs_assert(frame);
|
|
|
|
stream = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
|
|
if (!stream) {
|
|
if (frame->hd.type == NGHTTP2_SETTINGS) {
|
|
sbi_sess->settings.max_concurrent_streams =
|
|
nghttp2_session_get_remote_settings(
|
|
session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
|
|
sbi_sess->settings.enable_push =
|
|
nghttp2_session_get_remote_settings(
|
|
session, NGHTTP2_SETTINGS_ENABLE_PUSH);
|
|
ogs_debug("MAX_CONCURRENT_STREAMS = %d",
|
|
sbi_sess->settings.max_concurrent_streams);
|
|
ogs_debug("ENABLE_PUSH = %s",
|
|
sbi_sess->settings.enable_push ? "TRUE" : "false");
|
|
|
|
} else if (frame->hd.type == NGHTTP2_GOAWAY) {
|
|
rv = nghttp2_submit_goaway(
|
|
session, NGHTTP2_FLAG_NONE, sbi_sess->last_stream_id,
|
|
NGHTTP2_NO_ERROR, NULL, 0);
|
|
if (rv != 0) {
|
|
ogs_error("nghttp2_submit_goaway() failed (%d:%s)",
|
|
rv, nghttp2_strerror(rv));
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
session_send(sbi_sess);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
request = stream->request;
|
|
ogs_assert(request);
|
|
|
|
switch (frame->hd.type) {
|
|
case NGHTTP2_HEADERS:
|
|
if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
|
|
const char *expect100 =
|
|
ogs_sbi_header_get(request->http.headers, OGS_SBI_EXPECT);
|
|
if (expect100 && ogs_strcasecmp(expect100, "100-continue") == 0) {
|
|
nghttp2_nv nva;
|
|
|
|
add_header(&nva, ":status", status_string[100]);
|
|
rv = nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE,
|
|
stream->stream_id, NULL, &nva, 1, NULL);
|
|
if (rv != 0) {
|
|
ogs_error("nghttp2_submit_headers() failed (%d:%s)",
|
|
rv, nghttp2_strerror(rv));
|
|
nghttp2_submit_rst_stream(
|
|
session, NGHTTP2_FLAG_NONE, stream->stream_id, rv);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
/* fallthrough */
|
|
OGS_GNUC_FALLTHROUGH;
|
|
|
|
case NGHTTP2_DATA:
|
|
/* HEADERS or DATA frame with +END_STREAM flag */
|
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
|
|
|
ogs_debug("[%s] %s", request->h.method, request->h.uri);
|
|
|
|
if (request->http.content_length && request->http.content) {
|
|
ogs_debug("RECEIVED: %d", (int)request->http.content_length);
|
|
ogs_debug("%s", request->http.content);
|
|
}
|
|
|
|
if (server->cb(request, stream) != OGS_OK) {
|
|
ogs_warn("server callback error");
|
|
ogs_assert(true ==
|
|
ogs_sbi_server_send_error(stream,
|
|
OGS_SBI_HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL,
|
|
"server callback error", NULL));
|
|
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_stream_close(nghttp2_session *session, int32_t stream_id,
|
|
uint32_t error_code, void *user_data)
|
|
{
|
|
ogs_sbi_stream_t *stream = NULL;
|
|
|
|
ogs_assert(session);
|
|
|
|
stream = nghttp2_session_get_stream_user_data(session, stream_id);
|
|
if (!stream) {
|
|
ogs_error("no stream [%d]", stream_id);
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
}
|
|
|
|
if (error_code) {
|
|
ogs_error("on_stream_close_callback() failed (%d:%s)",
|
|
error_code, nghttp2_http2_strerror(error_code));
|
|
nghttp2_submit_rst_stream(
|
|
session, NGHTTP2_FLAG_NONE, stream_id, error_code);
|
|
}
|
|
|
|
ogs_debug("STREAM closed [%d]", stream_id);
|
|
stream_remove(stream);
|
|
return 0;
|
|
}
|
|
|
|
static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
|
|
nghttp2_rcbuf *name, nghttp2_rcbuf *value,
|
|
uint8_t flags, void *user_data)
|
|
{
|
|
ogs_sbi_session_t *sbi_sess = user_data;
|
|
ogs_sbi_stream_t *stream = NULL;
|
|
ogs_sbi_request_t *request = NULL;
|
|
|
|
const char PATH[] = ":path";
|
|
const char METHOD[] = ":method";
|
|
|
|
nghttp2_vec namebuf, valuebuf;
|
|
char *namestr = NULL, *valuestr = NULL;
|
|
|
|
ogs_assert(session);
|
|
ogs_assert(frame);
|
|
|
|
if (frame->hd.type != NGHTTP2_HEADERS ||
|
|
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
|
|
return 0;
|
|
}
|
|
|
|
stream = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
|
|
if (!stream) {
|
|
ogs_error("no stream [%d]", frame->hd.stream_id);
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
}
|
|
|
|
ogs_assert(sbi_sess);
|
|
|
|
request = stream->request;
|
|
ogs_assert(request);
|
|
|
|
ogs_assert(name);
|
|
namebuf = nghttp2_rcbuf_get_buf(name);
|
|
ogs_assert(namebuf.base);
|
|
ogs_assert(namebuf.len);
|
|
|
|
ogs_assert(value);
|
|
valuebuf = nghttp2_rcbuf_get_buf(value);
|
|
ogs_assert(valuebuf.base);
|
|
|
|
if (valuebuf.len == 0) return 0;
|
|
|
|
namestr = ogs_strndup((const char *)namebuf.base, namebuf.len);
|
|
ogs_assert(namestr);
|
|
|
|
valuestr = ogs_strndup((const char *)valuebuf.base, valuebuf.len);
|
|
ogs_assert(valuestr);
|
|
|
|
if (namebuf.len == sizeof(PATH) - 1 &&
|
|
memcmp(PATH, namebuf.base, namebuf.len) == 0) {
|
|
char *saveptr = NULL, *query;
|
|
#define MAX_NUM_OF_PARAM_IN_QUERY 16
|
|
struct yuarel_param params[MAX_NUM_OF_PARAM_IN_QUERY+2];
|
|
int j;
|
|
|
|
ogs_assert(request->h.uri == NULL);
|
|
request->h.uri = ogs_sbi_parse_uri(valuestr, "?", &saveptr);
|
|
ogs_assert(request->h.uri);
|
|
|
|
memset(params, 0, sizeof(params));
|
|
|
|
query = ogs_sbi_parse_uri(NULL, "?", &saveptr);
|
|
if (query && *query && strlen(query))
|
|
yuarel_parse_query(query, '&', params, MAX_NUM_OF_PARAM_IN_QUERY+1);
|
|
|
|
j = 0;
|
|
while(params[j].key && params[j].val) {
|
|
ogs_sbi_header_set(request->http.params,
|
|
params[j].key, params[j].val);
|
|
j++;
|
|
}
|
|
|
|
if (j >= MAX_NUM_OF_PARAM_IN_QUERY+1) {
|
|
ogs_fatal("Maximum number(%d) of query params reached",
|
|
MAX_NUM_OF_PARAM_IN_QUERY);
|
|
ogs_assert_if_reached();
|
|
}
|
|
|
|
ogs_free(query);
|
|
|
|
} else if (namebuf.len == sizeof(METHOD) - 1 &&
|
|
memcmp(METHOD, namebuf.base, namebuf.len) == 0) {
|
|
|
|
ogs_assert(request->h.method == NULL);
|
|
request->h.method = ogs_strdup(valuestr);
|
|
ogs_assert(request->h.method);
|
|
|
|
} else {
|
|
|
|
ogs_sbi_header_set(request->http.headers, namestr, valuestr);
|
|
|
|
}
|
|
|
|
ogs_free(namestr);
|
|
ogs_free(valuestr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
|
|
int32_t stream_id, const uint8_t *data,
|
|
size_t len, void *user_data)
|
|
{
|
|
ogs_sbi_stream_t *stream = NULL;
|
|
ogs_sbi_request_t *request = NULL;
|
|
|
|
size_t offset = 0;
|
|
|
|
ogs_assert(session);
|
|
|
|
stream = nghttp2_session_get_stream_user_data(session, stream_id);
|
|
if (!stream) {
|
|
ogs_error("no stream [%d]", stream_id);
|
|
return 0;
|
|
}
|
|
|
|
request = stream->request;
|
|
ogs_assert(request);
|
|
|
|
ogs_assert(data);
|
|
ogs_assert(len);
|
|
|
|
if (request->http.content == NULL) {
|
|
request->http.content_length = len;
|
|
request->http.content =
|
|
(char*)ogs_malloc(request->http.content_length + 1);
|
|
ogs_assert(request->http.content);
|
|
} else {
|
|
offset = request->http.content_length;
|
|
if ((request->http.content_length + len) > OGS_HUGE_LEN) {
|
|
ogs_error("Overflow : Content-Length[%d], len[%d]",
|
|
(int)request->http.content_length, (int)len);
|
|
ogs_assert_if_reached();
|
|
}
|
|
request->http.content_length += len;
|
|
request->http.content = (char *)ogs_realloc(
|
|
request->http.content, request->http.content_length + 1);
|
|
ogs_assert(request->http.content);
|
|
}
|
|
|
|
memcpy(request->http.content + offset, data, len);
|
|
request->http.content[request->http.content_length] = '\0';
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int error_callback(nghttp2_session *session,
|
|
const char *msg, size_t len, void *user_data)
|
|
{
|
|
char buf[OGS_ADDRSTRLEN];
|
|
ogs_sockaddr_t *addr = NULL;
|
|
ogs_sbi_session_t *sbi_sess = user_data;
|
|
|
|
ogs_assert(sbi_sess);
|
|
addr = sbi_sess->addr;
|
|
ogs_assert(addr);
|
|
|
|
ogs_assert(msg);
|
|
|
|
ogs_error("[%s]:%d http2 error: %.*s",
|
|
OGS_ADDR(addr, buf), OGS_PORT(addr), (int)len, msg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_invalid_frame_recv(nghttp2_session *session,
|
|
const nghttp2_frame *frame,
|
|
int error_code, void *user_data)
|
|
{
|
|
char buf[OGS_ADDRSTRLEN];
|
|
ogs_sockaddr_t *addr = NULL;
|
|
|
|
ogs_sbi_session_t *sbi_sess = user_data;
|
|
|
|
ogs_assert(sbi_sess);
|
|
addr = sbi_sess->addr;
|
|
ogs_assert(addr);
|
|
|
|
ogs_error("[%s]:%d invalid frame (%d:%s)",
|
|
OGS_ADDR(addr, buf), OGS_PORT(addr),
|
|
error_code, nghttp2_strerror(error_code));
|
|
return 0;
|
|
}
|
|
|
|
static int on_invalid_header(nghttp2_session *session,
|
|
const nghttp2_frame *frame,
|
|
const uint8_t *name, size_t namelen,
|
|
const uint8_t *value, size_t valuelen,
|
|
uint8_t flags, void *user_data)
|
|
{
|
|
char buf[OGS_ADDRSTRLEN];
|
|
ogs_sockaddr_t *addr = NULL;
|
|
char *namestr = NULL, *valuestr = NULL;
|
|
|
|
ogs_sbi_session_t *sbi_sess = user_data;
|
|
|
|
ogs_assert(sbi_sess);
|
|
addr = sbi_sess->addr;
|
|
ogs_assert(addr);
|
|
|
|
namestr = ogs_strndup((const char *)name, namelen);
|
|
ogs_assert(namestr);
|
|
|
|
valuestr = ogs_strndup((const char *)value, valuelen);
|
|
ogs_assert(valuestr);
|
|
|
|
ogs_error("[%s]:%d invalid header (%s:%s)",
|
|
OGS_ADDR(addr, buf), OGS_PORT(addr), namestr, valuestr);
|
|
|
|
ogs_free(namestr);
|
|
ogs_free(valuestr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_begin_frame(nghttp2_session *session, const nghttp2_frame_hd *hd,
|
|
void *user_data)
|
|
{
|
|
char buf[OGS_ADDRSTRLEN];
|
|
ogs_sockaddr_t *addr = NULL;
|
|
ogs_sbi_session_t *sbi_sess = user_data;
|
|
|
|
ogs_assert(sbi_sess);
|
|
addr = sbi_sess->addr;
|
|
ogs_assert(addr);
|
|
|
|
ogs_assert(hd);
|
|
|
|
if ((hd->type == NGHTTP2_HEADERS) &&
|
|
(hd->stream_id < sbi_sess->last_stream_id)) {
|
|
ogs_error("[%s]:%d invalid stream id(%d) >= last stream id(%d)",
|
|
OGS_ADDR(addr, buf), OGS_PORT(addr),
|
|
hd->stream_id, sbi_sess->last_stream_id);
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int on_begin_headers(nghttp2_session *session,
|
|
const nghttp2_frame *frame, void *user_data)
|
|
{
|
|
ogs_sbi_session_t *sbi_sess = user_data;
|
|
ogs_sbi_stream_t *stream = NULL;
|
|
|
|
ogs_assert(sbi_sess);
|
|
ogs_assert(session);
|
|
ogs_assert(frame);
|
|
|
|
if (frame->hd.type != NGHTTP2_HEADERS ||
|
|
frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
|
|
return 0;
|
|
}
|
|
|
|
stream = stream_add(sbi_sess, frame->hd.stream_id);
|
|
ogs_assert(stream);
|
|
ogs_debug("STREAM added [%d]", frame->hd.stream_id);
|
|
|
|
nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, stream);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int session_send_preface(ogs_sbi_session_t *sbi_sess)
|
|
{
|
|
int rv;
|
|
nghttp2_settings_entry iv[1] = {
|
|
{ NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 }
|
|
};
|
|
|
|
ogs_assert(sbi_sess);
|
|
ogs_assert(sbi_sess->session);
|
|
|
|
rv = nghttp2_submit_settings(
|
|
sbi_sess->session, NGHTTP2_FLAG_NONE, iv, OGS_ARRAY_SIZE(iv));
|
|
if (rv != 0) {
|
|
ogs_error("nghttp2_submit_settings() failed (%d:%s)",
|
|
rv, nghttp2_strerror(rv));
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
return session_send(sbi_sess);
|
|
}
|
|
|
|
#if USE_SEND_DATA_WITH_NO_COPY
|
|
static int on_send_data(nghttp2_session *session, nghttp2_frame *frame,
|
|
const uint8_t *framehd, size_t length,
|
|
nghttp2_data_source *source, void *user_data)
|
|
{
|
|
ogs_sbi_session_t *sbi_sess = user_data;
|
|
|
|
ogs_sbi_response_t *response = NULL;
|
|
ogs_sbi_stream_t *stream = NULL;
|
|
ogs_pkbuf_t *pkbuf = NULL;
|
|
size_t padlen = 0;
|
|
|
|
ogs_assert(session);
|
|
ogs_assert(frame);
|
|
|
|
stream = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
|
|
if (!stream) {
|
|
ogs_error("no stream [%d]", frame->hd.stream_id);
|
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
|
}
|
|
|
|
ogs_assert(sbi_sess);
|
|
|
|
ogs_assert(source);
|
|
response = source->ptr;
|
|
ogs_assert(response);
|
|
|
|
ogs_assert(response->http.content);
|
|
ogs_assert(response->http.content_length);
|
|
|
|
ogs_assert(framehd);
|
|
ogs_assert(length);
|
|
|
|
pkbuf = ogs_pkbuf_alloc(NULL, OGS_MAX_SDU_LEN);
|
|
ogs_assert(pkbuf);
|
|
ogs_pkbuf_put_data(pkbuf, framehd, 9);
|
|
|
|
padlen = frame->data.padlen;
|
|
|
|
if (padlen > 0) {
|
|
ogs_pkbuf_put_u8(pkbuf, padlen-1);
|
|
}
|
|
|
|
ogs_pkbuf_put_data(pkbuf,
|
|
response->http.content, response->http.content_length);
|
|
|
|
if (padlen > 0) {
|
|
memset(pkbuf->tail, 0, padlen-1);
|
|
ogs_pkbuf_put(pkbuf, padlen-1);
|
|
}
|
|
|
|
session_write_to_buffer(sbi_sess, pkbuf);
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
static ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
|
|
size_t length, int flags, void *user_data)
|
|
{
|
|
ogs_sbi_session_t *sbi_sess = user_data;
|
|
ogs_sock_t *sock = NULL;
|
|
ogs_socket_t fd = INVALID_SOCKET;
|
|
|
|
ogs_pkbuf_t *pkbuf = NULL;
|
|
|
|
ogs_assert(sbi_sess);
|
|
sock = sbi_sess->sock;
|
|
ogs_assert(sock);
|
|
fd = sock->fd;
|
|
ogs_assert(fd != INVALID_SOCKET);
|
|
|
|
ogs_assert(data);
|
|
ogs_assert(length);
|
|
|
|
pkbuf = ogs_pkbuf_alloc(NULL, length);
|
|
ogs_assert(pkbuf);
|
|
ogs_pkbuf_put_data(pkbuf, data, length);
|
|
|
|
session_write_to_buffer(sbi_sess, pkbuf);
|
|
|
|
return length;
|
|
}
|
|
#endif
|
|
|
|
static int session_send(ogs_sbi_session_t *sbi_sess)
|
|
{
|
|
#if USE_SEND_DATA_WITH_NO_COPY
|
|
ogs_pkbuf_t *pkbuf = NULL;
|
|
#else
|
|
int rv;
|
|
#endif
|
|
|
|
ogs_assert(sbi_sess);
|
|
ogs_assert(sbi_sess->session);
|
|
|
|
#if USE_SEND_DATA_WITH_NO_COPY
|
|
for (;;) {
|
|
const uint8_t *data = NULL;
|
|
ssize_t data_len;
|
|
|
|
data_len = nghttp2_session_mem_send(sbi_sess->session, &data);
|
|
if (data_len < 0) {
|
|
ogs_error("nghttp2_session_mem_send() failed (%d:%s)",
|
|
(int)data_len, nghttp2_strerror((int)data_len));
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
if (data_len == 0) {
|
|
break;
|
|
}
|
|
|
|
pkbuf = ogs_pkbuf_alloc(NULL, data_len);
|
|
ogs_assert(pkbuf);
|
|
ogs_pkbuf_put_data(pkbuf, data, data_len);
|
|
|
|
session_write_to_buffer(sbi_sess, pkbuf);
|
|
}
|
|
#else
|
|
rv = nghttp2_session_send(sbi_sess->session);
|
|
if (rv != 0) {
|
|
ogs_error("nghttp_session_send() failed (%d:%s)",
|
|
rv, nghttp2_strerror(rv));
|
|
return OGS_ERROR;
|
|
}
|
|
#endif
|
|
|
|
return OGS_OK;
|
|
}
|
|
|
|
static void session_write_callback(short when, ogs_socket_t fd, void *data)
|
|
{
|
|
ogs_sbi_session_t *sbi_sess = data;
|
|
ogs_pkbuf_t *pkbuf = NULL;
|
|
|
|
ogs_assert(sbi_sess);
|
|
|
|
if (ogs_list_empty(&sbi_sess->write_queue) == true) {
|
|
ogs_assert(sbi_sess->poll.write);
|
|
ogs_pollset_remove(sbi_sess->poll.write);
|
|
sbi_sess->poll.write = NULL;
|
|
return;
|
|
}
|
|
|
|
pkbuf = ogs_list_first(&sbi_sess->write_queue);
|
|
ogs_assert(pkbuf);
|
|
ogs_list_remove(&sbi_sess->write_queue, pkbuf);
|
|
|
|
ogs_send(fd, pkbuf->data, pkbuf->len, 0);
|
|
ogs_log_hexdump(OGS_LOG_DEBUG, pkbuf->data, pkbuf->len);
|
|
|
|
ogs_pkbuf_free(pkbuf);
|
|
}
|
|
|
|
static void session_write_to_buffer(
|
|
ogs_sbi_session_t *sbi_sess, ogs_pkbuf_t *pkbuf)
|
|
{
|
|
ogs_sock_t *sock = NULL;
|
|
ogs_socket_t fd = INVALID_SOCKET;
|
|
|
|
ogs_assert(pkbuf);
|
|
|
|
ogs_assert(sbi_sess);
|
|
sock = sbi_sess->sock;
|
|
ogs_assert(sock);
|
|
fd = sock->fd;
|
|
ogs_assert(fd != INVALID_SOCKET);
|
|
|
|
ogs_list_add(&sbi_sess->write_queue, pkbuf);
|
|
|
|
if (!sbi_sess->poll.write) {
|
|
sbi_sess->poll.write = ogs_pollset_add(ogs_app()->pollset,
|
|
OGS_POLLOUT, fd, session_write_callback, sbi_sess);
|
|
ogs_assert(sbi_sess->poll.write);
|
|
}
|
|
}
|