/*****************************************************************************
* h2conn.c: HTTP/2 connection handling
*****************************************************************************
* Copyright (C) 2015 Rémi Denis-Courmont
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#ifdef HAVE_POLL_H
# include <poll.h>
#endif
#ifdef HAVE_SYS_UIO_H
# include <sys/uio.h>
#endif
#include <vlc_common.h>
#include <vlc_threads.h>
#include <vlc_poll.h>
#include <vlc_block.h>
#include <vlc_interrupt.h>
#include <vlc_tls.h>
#include "h2frame.h"
#include "h2output.h"
#include "conn.h"
#include "message.h"
#define CO(c) ((c)->opaque)
#define SO(s) CO((s)->conn)
/** HTTP/2 connection */
struct vlc_h2_conn
{
struct vlc_http_conn conn;
struct vlc_h2_output *out; /**< Send thread */
void *opaque;
struct vlc_h2_stream *streams; /**< List of open streams */
uint32_t next_id; /**< Next free stream identifier */
bool released; /**< Connection released by owner */
uint32_t max_send_frame; /**< Maximum sent frame size */
uint32_t init_send_cwnd; /**< Initial send congestion window */
uint64_t send_cwnd; /**< Send congestion window */
vlc_cond_t send_wait;
vlc_mutex_t lock; /**< State machine lock */
vlc_thread_t thread; /**< Receive thread */
};
static void vlc_h2_conn_destroy(struct vlc_h2_conn *conn);
/** HTTP/2 stream */
struct vlc_h2_stream
{
struct vlc_http_stream stream; /**< Base class */
struct vlc_h2_conn *conn; /**< Underlying HTTP/2 connection */
struct vlc_h2_stream *older; /**< Previous open stream in connection */
struct vlc_h2_stream *newer; /**< Next open stream in connection */
uint32_t id; /**< Stream 31-bits identifier */
bool interrupted;
bool recv_end; /**< End-of-stream flag */
int recv_err; /**< Standard C error code */
struct vlc_http_msg *recv_hdr; /**< Latest received headers (or NULL) */
size_t recv_cwnd; /**< Free space in receive congestion window */
struct vlc_h2_frame *recv_head; /**< Earliest pending received buffer */
struct vlc_h2_frame **recv_tailp; /**< Tail of receive queue */
vlc_cond_t recv_wait;
uint64_t send_cwnd; /**< Send congestion window */
vlc_cond_t send_wait;
};
static int vlc_h2_conn_queue(struct vlc_h2_conn *conn, struct vlc_h2_frame *f)
{
vlc_h2_frame_dump(conn->opaque, f, "out");
return vlc_h2_output_send(conn->out, f);
}
static int vlc_h2_conn_queue_prio(struct vlc_h2_conn *conn,
struct vlc_h2_frame *f)
{
vlc_h2_frame_dump(conn->opaque, f, "out (priority)");
return vlc_h2_output_send_prio(conn->out, f);
}
/* Stream callbacks */
/** Looks a stream up by ID. */
static void *vlc_h2_stream_lookup(void *ctx, uint_fast32_t id)
{
struct vlc_h2_conn *conn = ctx;
for (struct vlc_h2_stream *s = conn->streams; s != NULL; s = s->older)
if (s->id == id)
return s;
return NULL;
}
/** Reports a local stream error */
static int vlc_h2_stream_error(void *ctx, uint_fast32_t id, uint_fast32_t code)
{
struct vlc_h2_conn *conn = ctx;
/* NOTE: This function is used both w/ and w/o conn->lock. Care. */
if (code != VLC_H2_NO_ERROR)
vlc_http_err(CO(conn), "local stream %"PRIuFAST32" error: "
"%s (0x%"PRIXFAST32")", id, vlc_h2_strerror(code), code);
else
vlc_http_dbg(CO(conn), "local stream %"PRIuFAST32" shut down", id);
return vlc_h2_conn_queue(conn, vlc_h2_frame_rst_stream(id, code));
}
static int vlc_h2_stream_fatal(struct vlc_h2_stream *s, uint_fast32_t code)
{
s->recv_end = true;
s->recv_err = EPROTO;
return vlc_h2_stream_error(s->conn, s->id, code);
}
/** Reports received stream headers */
static void vlc_h2_stream_headers(void *ctx, unsigned count,
const char *const hdrs[][2])
{
struct vlc_h2_stream *s = ctx;
/* NOTE: HTTP message trailers are not supported so far. Follow-up headers
* can therefore only be a final response after a 1xx continue response.
* Then it is safe to discard the existing header. */
if (s->recv_hdr != NULL)
{
vlc_http_dbg(SO(s), "stream %"PRIu32" discarding old headers", s->id);
vlc_http_msg_destroy(s->recv_hdr);
s->recv_hdr = NULL;
}
vlc_http_dbg(SO(s), "stream %"PRIu32" %u headers:", s->id, count);
for (unsigned i = 0; i < count; i++)
vlc_http_dbg(SO(s), " %s: \"%s\"", hdrs[i][0], hdrs[i][1]);
s->recv_hdr = vlc_http_msg_h2_headers(count, hdrs);
if (unlikely(s->recv_hdr == NULL))
vlc_h2_stream_fatal(s, VLC_H2_PROTOCOL_ERROR);
vlc_cond_signal(&s->recv_wait);
}
/** Reports received stream data */
static int vlc_h2_stream_data(void *ctx, struct vlc_h2_frame *f)
{
struct vlc_h2_stream *s = ctx;
size_t len;
if (s->recv_end)
{
free(f);
return vlc_h2_stream_error(s->conn, s->id, VLC_H2_STREAM_CLOSED);
}
/* Enforce the congestion window as required by the protocol spec */
vlc_h2_frame_data_get(f, &len);
if (len > s->recv_cwnd)
{
free(f);
return vlc_h2_stream_fatal(s, VLC_H2_FLOW_CONTROL_ERROR);
}
*(s->recv_tailp) = f;
s->recv_tailp = &f->next;
vlc_cond_signal(&s->recv_wait);
return 0;
}
/** Reports received end of stream */
static void vlc_h2_stream_end(void *ctx)
{
struct vlc_h2_stream *s = ctx;
vlc_http_dbg(SO(s), "stream %"PRIu32" closed by peer", s->id);
s->recv_end = true;
vlc_cond_broadcast(&s->recv_wait);
}
/** Reports remote stream error */
static int vlc_h2_stream_reset(void *ctx, uint_fast32_t code)
{
struct vlc_h2_stream *s = ctx;
vlc_http_err(SO(s), "peer stream %"PRIu32" error: %s (0x%"PRIXFAST32")",
s->id, vlc_h2_strerror(code), code);
s->recv_end = true;
s->recv_err = ECONNRESET;
vlc_cond_broadcast(&s->recv_wait);
return 0;
}
/** Reports remote window size increments */
static void vlc_h2_stream_window_update(void *ctx, uint_fast32_t credit)
{
struct vlc_h2_stream *s = ctx;
/* In the extremely unlikely event of an overflow, the window will be
* treated as negative, and we will stop sending: the peer is definitely
* insanely broken anyway.
*/
s->send_cwnd += credit;
vlc_cond_broadcast(&s->send_wait);
vlc_http_dbg(SO(s), "stream %"PRIu32" window update: +%"PRIuFAST32" to "
"%"PRIu64, s->id, credit, s->send_cwnd);
}
static void vlc_h2_stream_wake_up(void *data)
{
struct vlc_h2_stream *s = data;
struct vlc_h2_conn *conn = s->conn;
vlc_mutex_lock(&conn->lock);
s->interrupted = true;
vlc_cond_signal(&s->recv_wait);
vlc_mutex_unlock(&conn->lock);
}
static void vlc_h2_stream_lock(struct vlc_h2_stream *s)
{
s->interrupted = false;
/* When using interrupts, there shall be only one waiter per stream.
* Otherwise, there would be no ways to map the interrupt to a thread. */
vlc_interrupt_register(vlc_h2_stream_wake_up, s);
vlc_mutex_lock(&s->conn->lock);
}
static int vlc_h2_stream_unlock(struct vlc_h2_stream *s)
{
vlc_mutex_unlock(&s->conn->lock);
return vlc_interrupt_unregister();
}
static struct vlc_http_msg *vlc_h2_stream_wait(struct vlc_http_stream *stream)
{
struct vlc_h2_stream *s =
container_of(stream, struct vlc_h2_stream, stream);
struct vlc_h2_conn *conn = s->conn;
struct vlc_http_msg *m;
vlc_h2_stream_lock(s);
while ((m = s->recv_hdr) == NULL && !s->recv_end && !s->interrupted)
vlc_cond_wait(&s->recv_wait, &conn->lock);
s->recv_hdr = NULL;
vlc_h2_stream_unlock(s);
/* TODO? distinguish failed/unknown/ignored */
if (m != NULL)
vlc_http_msg_attach(m, stream);
return m;
}
static ssize_t vlc_h2_stream_write(struct vlc_http_stream *stream,
const void *base, size_t length, bool eos)
{
struct vlc_h2_stream *s =
container_of(stream, struct vlc_h2_stream, stream);
struct vlc_h2_conn *conn = s->conn;
ssize_t total = 0;
int err = 0;
if (unlikely(length == 0 && !eos))
return 0;
vlc_h2_stream_lock(s);
do
{
size_t size = length;
/* This and the following flow control comparison and subtraction all
* assumes that we do *not* use any padding on the data frames.
*/
if (size > conn->max_send_frame)
size = conn->max_send_frame;
/* Stream flow control */
if (size > s->send_cwnd)
size = s->send_cwnd;
if (size == 0 && length > 0)
{
if (s->interrupted)
{
err = EINTR;
break;
}
vlc_cond_wait(&s->send_wait, &conn->lock);
continue;
}
/* Connection flow control */
if (size > conn->send_cwnd)
size = conn->send_cwnd;
if (size == 0 && length > 0)
{
if (s->interrupted)
{
err = EINTR;
break;
}
vlc_cond_wait(&conn->send_wait, &conn->lock);
continue;
}
/* Frame send */
bool end = eos && length == size;
struct vlc_h2_frame *f = vlc_h2_frame_data(s->id, base, size, end);
if (f == NULL)
{
err = ENOMEM;
break;
}
if (vlc_h2_conn_queue(conn, f))
{
err = ECONNRESET;
break;
}
if (likely(size > 0))
base = (const char *)base + size;
length -= size;
total += size;
s->send_cwnd -= size;
conn->send_cwnd -= size;
}
while (length > 0);
vlc_h2_stream_unlock(s);
if (total == 0 && err != 0)
{
errno = err;
total = -1;
}
return total;
}
/**
* Receives stream data.
*
* Dequeues pending incoming data for an HTTP/2 stream. If there is currently
* no data block, wait for one.
*
* \return a VLC data block, NULL on end of stream,
* or vlc_http_error on stream error
*/
static block_t *vlc_h2_stream_read(struct vlc_http_stream *stream)
{
struct vlc_h2_stream *s =
container_of(stream, struct vlc_h2_stream, stream);
struct vlc_h2_conn *conn = s->conn;
struct vlc_h2_frame *f;
vlc_h2_stream_lock(s);
while ((f = s->recv_head) == NULL && !s->recv_end && !s->interrupted)
vlc_cond_wait(&s->recv_wait, &conn->lock);
if (f == NULL)
{
int err = s->recv_err;
vlc_h2_stream_unlock(s);
if (err)
{
errno = err;
return vlc_http_error;
}
return NULL;
}
s->recv_head = f->next;
if (f->next == NULL)
{
assert(s->recv_tailp == &f->next);
s->recv_tailp = &s->recv_head;
}
size_t len;
uint8_t *buf = vlc_h2_frame_data_get(f, &len);
assert(s->recv_cwnd >= len);
s->recv_cwnd -= len;
/* Credit the receive window if missing credit exceeds 50%. */
uint_fast32_t credit = VLC_H2_INIT_WINDOW - s->recv_cwnd;
if (credit >= (VLC_H2_INIT_WINDOW / 2)
&& !vlc_h2_conn_queue(conn, vlc_h2_frame_window_update(s->id, credit)))
s->recv_cwnd += credit;
vlc_h2_stream_unlock(s);
/* This, err, unconventional code to avoid copying data. */
block_t *block = block_heap_Alloc(f, sizeof (*f) + vlc_h2_frame_size(f));
if (unlikely(block == NULL))
{
vlc_h2_stream_error(conn, s->id, VLC_H2_INTERNAL_ERROR);
return vlc_http_error;
}
assert(block->i_buffer >= len);
assert(block->p_buffer <= buf);
assert(block->p_buffer + block->i_buffer >= buf + len);
block->p_buffer = buf;
block->i_buffer = len;
return block;
}
/**
* Terminates a stream.
*
* Sends an HTTP/2 stream reset, removes the stream from the HTTP/2 connection
* and deletes any stream resource.
*/
static void vlc_h2_stream_close(struct vlc_http_stream *stream, bool aborted)
{
struct vlc_h2_stream *s =
container_of(stream, struct vlc_h2_stream, stream);
struct vlc_h2_conn *conn = s->conn;
bool destroy = false;
uint_fast32_t code = VLC_H2_NO_ERROR;
vlc_mutex_lock(&conn->lock);
if (s->older != NULL)
s->older->newer = s->newer;
if (s->newer != NULL)
s->newer->older = s->older;
else
{
assert(conn->streams == s);
conn->streams = s->older;
destroy = (conn->streams == NULL) && conn->released;
}
vlc_mutex_unlock(&conn->lock);
if (s->recv_hdr != NULL || s->recv_head != NULL || !s->recv_end)
code = VLC_H2_CANCEL;
(void) aborted;
vlc_h2_stream_error(conn, s->id, code);
if (s->recv_hdr != NULL)
vlc_http_msg_destroy(s->recv_hdr);
for (struct vlc_h2_frame *f = s->recv_head, *next; f != NULL; f = next)
{
next = f->next;
free(f);
}
free(s);
if (destroy)
vlc_h2_conn_destroy(conn);
}
static const struct vlc_http_stream_cbs vlc_h2_stream_callbacks =
{
vlc_h2_stream_wait,
vlc_h2_stream_write,
vlc_h2_stream_read,
vlc_h2_stream_close,
};
/**
* Creates a stream.
*
* Allocates a locally-initiated stream identifier on an HTTP/2 connection and
* queue stream headers for sending.
*
* Headers are sent asynchronously. To obtain the result and answer from the
* other end, use vlc_http_stream_recv_headers().
*
* \param c the HTTP connection to initialize the stream from
* \param msg HTTP message headers (including response status or request)
* \param has_data whether the HTTP request will have a request payload
* \return an HTTP stream, or NULL on error
*/
static struct vlc_http_stream *vlc_h2_stream_open(struct vlc_http_conn *c,
const struct vlc_http_msg *msg, bool has_data)
{
struct vlc_h2_conn *conn = container_of(c, struct vlc_h2_conn, conn);
struct vlc_h2_stream *s = malloc(sizeof (*s));
if (unlikely(s == NULL))
return NULL;
s->stream.cbs = &vlc_h2_stream_callbacks;
s->conn = conn;
s->newer = NULL;
s->recv_end = false;
s->recv_err = 0;
s->recv_hdr = NULL;
s->recv_cwnd = VLC_H2_INIT_WINDOW;
s->recv_head = NULL;
s->recv_tailp = &s->recv_head;
vlc_cond_init(&s->recv_wait);
vlc_cond_init(&s->send_wait);
s->send_cwnd = conn->init_send_cwnd;
vlc_mutex_lock(&conn->lock);
assert(!conn->released); /* Caller is buggy! */
if (conn->next_id > 0x7ffffff)
{ /* Out of stream identifiers */
vlc_http_dbg(CO(conn), "no more stream identifiers");
goto error;
}
s->id = conn->next_id;
conn->next_id += 2;
struct vlc_h2_frame *f = vlc_http_msg_h2_frame(msg, s->id, !has_data);
if (f == NULL)
goto error;
vlc_h2_conn_queue(conn, f);
s->older = conn->streams;
if (s->older != NULL)
s->older->newer = s;
conn->streams = s;
vlc_mutex_unlock(&conn->lock);
return &s->stream;
error:
vlc_mutex_unlock(&conn->lock);
free(s);
return NULL;
}
/* Global/Connection frame callbacks */
static void vlc_h2_initial_window_update(struct vlc_h2_conn *conn,
uint_fast32_t value)
{
uint64_t delta = (uint64_t)value - conn->init_send_cwnd;
conn->init_send_cwnd = value;
conn->send_cwnd += delta;
vlc_cond_broadcast(&conn->send_wait);
for (struct vlc_h2_stream *s = conn->streams; s != NULL; s = s->older)
{
s->send_cwnd += delta;
vlc_cond_broadcast(&s->send_wait);
}
}
/** Reports an HTTP/2 peer connection setting */
static void vlc_h2_setting(void *ctx, uint_fast16_t id, uint_fast32_t value)
{
struct vlc_h2_conn *conn = ctx;
vlc_http_dbg(CO(conn), "setting: %s (0x%04"PRIxFAST16"): %"PRIuFAST32,
vlc_h2_setting_name(id), id, value);
switch (id)
{
case VLC_H2_SETTING_INITIAL_WINDOW_SIZE:
vlc_h2_initial_window_update(conn, value);
break;
case VLC_H2_SETTING_MAX_FRAME_SIZE:
if (value >= VLC_H2_MIN_MAX_FRAME
&& value <= VLC_H2_MAX_MAX_FRAME)
conn->max_send_frame = value;
break;
}
}
/** Reports end of HTTP/2 peer settings */
static int vlc_h2_settings_done(void *ctx)
{
struct vlc_h2_conn *conn = ctx;
return vlc_h2_conn_queue(conn, vlc_h2_frame_settings_ack());
}
/** Reports a ping received from HTTP/2 peer */
static int vlc_h2_ping(void *ctx, uint_fast64_t opaque)
{
struct vlc_h2_conn *conn = ctx;
return vlc_h2_conn_queue_prio(conn, vlc_h2_frame_pong(opaque));
}
/** Reports a local HTTP/2 connection failure */
static void vlc_h2_error(void *ctx, uint_fast32_t code)
{
struct vlc_h2_conn *conn = ctx;
/* NOTE: This function is used both w/ and w/o conn->lock. Care. */
if (code != VLC_H2_NO_ERROR)
vlc_http_err(CO(conn), "local error: %s (0x%"PRIxFAST32")",
vlc_h2_strerror(code), code);
else
vlc_http_dbg(CO(conn), "local shutdown");
/* NOTE: currently, the peer cannot create a stream, so ID=0 */
vlc_h2_conn_queue(conn, vlc_h2_frame_goaway(0, code));
}
/** Reports a remote HTTP/2 connection error */
static int vlc_h2_reset(void *ctx, uint_fast32_t last_seq, uint_fast32_t code)
{
struct vlc_h2_conn *conn = ctx;
vlc_http_err(CO(conn), "peer error: %s (0x%"PRIxFAST32")",
vlc_h2_strerror(code), code);
vlc_http_dbg(CO(conn), "last stream: %"PRIuFAST32, last_seq);
/* NOTE: currently, the peer cannot create a stream, so ID=0 */
vlc_h2_conn_queue(conn, vlc_h2_frame_goaway(0, VLC_H2_NO_ERROR));
/* Prevent adding new streams on this end. */
conn->next_id = 0x80000000;
/* Reject any stream newer than last_seq */
for (struct vlc_h2_stream *s = conn->streams; s != NULL; s = s->older)
if (s->id > last_seq)
vlc_h2_stream_reset(s, VLC_H2_REFUSED_STREAM);
return 0;
}
static void vlc_h2_window_status(void *ctx, uint32_t *restrict rcwd)
{
struct vlc_h2_conn *conn = ctx;
/* Maintain connection receive window to insanely large values.
* Congestion control is done per stream instead. */
if (*rcwd < (1 << 30)
&& vlc_h2_conn_queue_prio(conn,
vlc_h2_frame_window_update(0, 1 << 30)) == 0)
*rcwd += 1 << 30;
}
static void vlc_h2_window_update(void *ctx, uint_fast32_t credit)
{
struct vlc_h2_conn *conn = ctx;
conn->send_cwnd += credit;
vlc_cond_broadcast(&conn->send_wait);
vlc_http_dbg(CO(conn), "window update: +%"PRIuFAST32" to %"PRIu64,
credit, conn->send_cwnd);
}
/** HTTP/2 frames parser callbacks table */
static const struct vlc_h2_parser_cbs vlc_h2_parser_callbacks =
{
vlc_h2_setting,
vlc_h2_settings_done,
vlc_h2_ping,
vlc_h2_error,
vlc_h2_reset,
vlc_h2_window_status,
vlc_h2_window_update,
vlc_h2_stream_lookup,
vlc_h2_stream_error,
vlc_h2_stream_headers,
vlc_h2_stream_data,
vlc_h2_stream_end,
vlc_h2_stream_reset,
vlc_h2_stream_window_update,
};
/**
* Receives TLS data.
*
* Receives bytes from the peer through a TLS session.
* @note This may be a cancellation point.
* The caller is responsible for serializing reads on a given connection.
*/
static ssize_t vlc_https_recv(vlc_tls_t *tls, void *buf, size_t len)
{
struct iovec iov;
size_t count = 0;
iov.iov_base = buf;
iov.iov_len = len;
while (iov.iov_len > 0)
{
int canc = vlc_savecancel();
ssize_t val = tls->ops->readv(tls, &iov, 1);
vlc_restorecancel(canc);
if (val == 0)
break;
if (val >= 0)
{
iov.iov_base = (char *)iov.iov_base + val;
iov.iov_len -= val;
count += val;
continue;
}
if (errno != EINTR && errno != EAGAIN)
return count ? (ssize_t)count : -1;
struct pollfd ufd;
ufd.events = POLLIN;
ufd.fd = vlc_tls_GetPollFD(tls, &ufd.events);
poll(&ufd, 1, -1);
}
return count;
}
/**
* Receives an HTTP/2 frame through TLS.
*
* This function allocates memory for and receives a whole HTTP/2 frame from a
* TLS session.
*
* The caller must "own" the read side of the TLS session.
*
* @note This is a blocking function and may be a thread cancellation point.
*
* @return a frame or NULL if the connection failed
*/
static struct vlc_h2_frame *vlc_h2_frame_recv(struct vlc_tls *tls)
{
uint8_t header[9];
ssize_t r = vlc_https_recv(tls, header, 9);
/* TODO: actually block only until third byte */
if (r < 3)
return NULL;
uint_fast32_t len = 9 + ((header[0] << 16) | (header[1] << 8) | header[2]);
struct vlc_h2_frame *f = malloc(sizeof (*f) + len);
if (unlikely(f == NULL))
return NULL;
f->next = NULL;
memcpy(f->data, header, r);
len -= r;
if (len > 0)
{
vlc_cleanup_push(free, f);
if (vlc_https_recv(tls, f->data + r, len) < (ssize_t)len)
{
free(f);
f = NULL;
}
vlc_cleanup_pop();
}
return f;
}
static void cleanup_parser(void *data)
{
vlc_h2_parse_destroy(data);
}
/** HTTP/2 receive thread */
static void *vlc_h2_recv_thread(void *data)
{
struct vlc_h2_conn *conn = data;
struct vlc_h2_frame *frame;
struct vlc_h2_parser *parser;
int canc, val;
vlc_thread_set_name("vlc-h2-recv");
canc = vlc_savecancel();
parser = vlc_h2_parse_init(conn, &vlc_h2_parser_callbacks);
if (unlikely(parser == NULL))
goto fail;
vlc_cleanup_push(cleanup_parser, parser);
do
{
vlc_restorecancel(canc);
frame = vlc_h2_frame_recv(conn->conn.tls);
canc = vlc_savecancel();
if (frame == NULL)
{
vlc_http_dbg(CO(conn), "connection shutdown");
break;
}
vlc_h2_frame_dump(CO(conn), frame, "in");
vlc_mutex_lock(&conn->lock);
val = vlc_h2_parse(parser, frame);
vlc_mutex_unlock(&conn->lock);
}
while (val == 0);
vlc_cleanup_pop();
vlc_h2_parse_destroy(parser);
fail:
/* Terminate any remaining stream */
vlc_mutex_lock(&conn->lock);
for (struct vlc_h2_stream *s = conn->streams; s != NULL; s = s->older)
vlc_h2_stream_reset(s, VLC_H2_CANCEL);
vlc_mutex_unlock(&conn->lock);
return NULL;
}
static void vlc_h2_conn_destroy(struct vlc_h2_conn *conn)
{
assert(conn->streams == NULL);
vlc_h2_error(conn, VLC_H2_NO_ERROR);
vlc_cancel(conn->thread);
vlc_join(conn->thread, NULL);
vlc_h2_output_destroy(conn->out);
vlc_tls_Shutdown(conn->conn.tls, true);
vlc_tls_Close(conn->conn.tls);
free(conn);
}
static void vlc_h2_conn_release(struct vlc_http_conn *c)
{
struct vlc_h2_conn *conn = container_of(c, struct vlc_h2_conn, conn);
bool destroy;
vlc_mutex_lock(&conn->lock);
assert(!conn->released);
conn->released = true;
destroy = (conn->streams == NULL);
vlc_mutex_unlock(&conn->lock);
if (destroy)
vlc_h2_conn_destroy(conn);
}
static const struct vlc_http_conn_cbs vlc_h2_conn_callbacks =
{
vlc_h2_stream_open,
vlc_h2_conn_release,
};
struct vlc_http_conn *vlc_h2_conn_create(void *ctx, struct vlc_tls *tls)
{
struct vlc_h2_conn *conn = malloc(sizeof (*conn));
if (unlikely(conn == NULL))
return NULL;
conn->conn.cbs = &vlc_h2_conn_callbacks;
conn->conn.tls = tls;
conn->out = vlc_h2_output_create(tls, true);
conn->opaque = ctx;
conn->streams = NULL;
conn->next_id = 1; /* TODO: server side */
conn->released = false;
conn->max_send_frame = VLC_H2_DEFAULT_MAX_FRAME;
conn->init_send_cwnd = VLC_H2_DEFAULT_INIT_WINDOW;
conn->send_cwnd = VLC_H2_DEFAULT_INIT_WINDOW;
if (unlikely(conn->out == NULL))
goto error;
vlc_mutex_init(&conn->lock);
vlc_cond_init(&conn->send_wait);
if (vlc_h2_conn_queue(conn, vlc_h2_frame_settings())
|| vlc_clone(&conn->thread, vlc_h2_recv_thread, conn))
{
vlc_h2_output_destroy(conn->out);
goto error;
}
return &conn->conn;
error:
free(conn);
return NULL;
}
¤ Dauer der Verarbeitung: 0.12 Sekunden
¤
*© Formatika GbR, Deutschland
|
|