1
0
Fork 0
mirror of https://github.com/albfan/miraclecast.git synced 2025-03-09 23:38:56 +00:00
miraclecast/src/shared/rtsp.c
Andrey Gusakov ca07b34f3b RTSP: support reading hex and skiping
Signed-off-by: Andrey Gusakov <andrey.gusakov@cogentembedded.com>
2015-03-30 12:06:38 +02:00

3268 lines
65 KiB
C

/*
* MiracleCast - Wifi-Display/Miracast Implementation
*
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
*
* MiracleCast 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.
*
* MiracleCast 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 MiracleCast; If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <systemd/sd-event.h>
#include <time.h>
#include <unistd.h>
#include "rtsp.h"
#include "shl_dlist.h"
#include "shl_htable.h"
#include "shl_macro.h"
#include "shl_ring.h"
#include "shl_util.h"
/* 5s default timeout for messages */
#define RTSP_DEFAULT_TIMEOUT (5ULL * 1000ULL * 1000ULL)
/* CSeq numbers have separate namespaces for locally and remotely generated
* messages. We use a single lookup-table, so mark all remotely generated
* cookies as such to avoid conflicts with local cookies. */
#define RTSP_FLAG_REMOTE_COOKIE 0x8000000000000000ULL
struct rtsp {
unsigned long ref;
uint64_t cookies;
int fd;
sd_event_source *fd_source;
sd_event *event;
int64_t priority;
struct shl_dlist matches;
/* outgoing messages */
struct shl_dlist outgoing;
size_t outgoing_cnt;
/* waiting messages */
struct shl_htable waiting;
size_t waiting_cnt;
/* ring parser */
struct rtsp_parser {
struct rtsp_message *m;
struct shl_ring buf;
size_t buflen;
enum {
STATE_NEW,
STATE_HEADER,
STATE_HEADER_QUOTE,
STATE_HEADER_NL,
STATE_BODY,
STATE_DATA_HEAD,
STATE_DATA_BODY,
} state;
char last_chr;
size_t remaining_body;
size_t data_size;
uint8_t data_channel;
bool quoted : 1;
bool dead : 1;
} parser;
bool is_dead : 1;
bool is_calling : 1;
};
struct rtsp_match {
struct shl_dlist list;
rtsp_callback_fn cb_fn;
void *data;
bool is_removed : 1;
};
struct rtsp_header {
char *key;
char *value;
size_t token_cnt;
size_t token_used;
char **tokens;
char *line;
size_t line_len;
};
struct rtsp_message {
unsigned long ref;
struct rtsp *bus;
struct shl_dlist list;
unsigned int type;
uint64_t cookie;
unsigned int major;
unsigned int minor;
/* unknown specific */
char *unknown_head;
/* request specific */
char *request_method;
char *request_uri;
/* reply specific */
unsigned int reply_code;
char *reply_phrase;
/* data specific */
unsigned int data_channel;
uint8_t *data_payload;
size_t data_size;
/* iterators */
bool iter_body;
struct rtsp_header *iter_header;
size_t iter_token;
/* headers */
size_t header_cnt;
size_t header_used;
struct rtsp_header *headers;
struct rtsp_header *header_clen;
struct rtsp_header *header_ctype;
struct rtsp_header *header_cseq;
/* body */
uint8_t *body;
size_t body_size;
size_t body_cnt;
size_t body_used;
struct rtsp_header *body_headers;
/* transmission */
sd_event_source *timer_source;
rtsp_callback_fn cb_fn;
void *fn_data;
uint64_t timeout;
uint8_t *raw;
size_t raw_size;
size_t sent;
bool is_used : 1;
bool is_sealed : 1;
bool is_outgoing : 1;
bool is_waiting : 1;
bool is_sending : 1;
};
#define rtsp_message_from_htable(_p) \
shl_htable_entry((_p), struct rtsp_message, cookie)
#define RTSP_FOREACH_WAITING(_i, _bus) \
SHL_HTABLE_FOREACH_MACRO(_i, &(_bus)->waiting, rtsp_message_from_htable)
#define RTSP_FIRST_WAITING(_bus) \
SHL_HTABLE_FIRST_MACRO(&(_bus)->waiting, rtsp_message_from_htable)
static void rtsp_free_match(struct rtsp_match *match);
static void rtsp_drop_message(struct rtsp_message *m);
static int rtsp_incoming_message(struct rtsp_message *m);
/*
* Helpers
* Some helpers that don't really belong into a specific group.
*/
static const char *code_descriptions[] = {
[RTSP_CODE_CONTINUE] = "Continue",
[RTSP_CODE_OK] = "OK",
[RTSP_CODE_CREATED] = "Created",
[RTSP_CODE_LOW_ON_STORAGE_SPACE] = "Low on Storage Space",
[RTSP_CODE_MULTIPLE_CHOICES] = "Multiple Choices",
[RTSP_CODE_MOVED_PERMANENTLY] = "Moved Permanently",
[RTSP_CODE_MOVED_TEMPORARILY] = "Moved Temporarily",
[RTSP_CODE_SEE_OTHER] = "See Other",
[RTSP_CODE_NOT_MODIFIED] = "Not Modified",
[RTSP_CODE_USE_PROXY] = "Use Proxy",
[RTSP_CODE_BAD_REQUEST] = "Bad Request",
[RTSP_CODE_UNAUTHORIZED] = "Unauthorized",
[RTSP_CODE_PAYMENT_REQUIRED] = "Payment Required",
[RTSP_CODE_FORBIDDEN] = "Forbidden",
[RTSP_CODE_NOT_FOUND] = "Not Found",
[RTSP_CODE_METHOD_NOT_ALLOWED] = "Method not Allowed",
[RTSP_CODE_NOT_ACCEPTABLE] = "Not Acceptable",
[RTSP_CODE_PROXY_AUTHENTICATION_REQUIRED] = "Proxy Authentication Required",
[RTSP_CODE_REQUEST_TIMEOUT] = "Request Time-out",
[RTSP_CODE_GONE] = "Gone",
[RTSP_CODE_LENGTH_REQUIRED] = "Length Required",
[RTSP_CODE_PRECONDITION_FAILED] = "Precondition Failed",
[RTSP_CODE_REQUEST_ENTITY_TOO_LARGE] = "Request Entity Too Large",
[RTSP_CODE_REQUEST_URI_TOO_LARGE] = "Request-URI too Large",
[RTSP_CODE_UNSUPPORTED_MEDIA_TYPE] = "Unsupported Media Type",
[RTSP_CODE_PARAMETER_NOT_UNDERSTOOD] = "Parameter not Understood",
[RTSP_CODE_CONFERENCE_NOT_FOUND] = "Conference not Found",
[RTSP_CODE_NOT_ENOUGH_BANDWIDTH] = "Not Enough Bandwidth",
[RTSP_CODE_SESSION_NOT_FOUND] = "Session not Found",
[RTSP_CODE_METHOD_NOT_VALID_IN_THIS_STATE] = "Method not Valid in this State",
[RTSP_CODE_HEADER_FIELD_NOT_VALID_FOR_RESOURCE] = "Header Field not Valid for Resource",
[RTSP_CODE_INVALID_RANGE] = "Invalid Range",
[RTSP_CODE_PARAMETER_IS_READ_ONLY] = "Parameter is Read-only",
[RTSP_CODE_AGGREGATE_OPERATION_NOT_ALLOWED] = "Aggregate Operation not Allowed",
[RTSP_CODE_ONLY_AGGREGATE_OPERATION_ALLOWED] = "Only Aggregate Operation Allowed",
[RTSP_CODE_UNSUPPORTED_TRANSPORT] = "Unsupported Transport",
[RTSP_CODE_DESTINATION_UNREACHABLE] = "Destination Unreachable",
[RTSP_CODE_INTERNAL_SERVER_ERROR] = "Internal Server Error",
[RTSP_CODE_NOT_IMPLEMENTED] = "Not Implemented",
[RTSP_CODE_BAD_GATEWAY] = "Bad Gateway",
[RTSP_CODE_SERVICE_UNAVAILABLE] = "Service Unavailable",
[RTSP_CODE_GATEWAY_TIMEOUT] = "Gateway Time-out",
[RTSP_CODE_RTSP_VERSION_NOT_SUPPORTED] = "RTSP Version not Supported",
[RTSP_CODE_OPTION_NOT_SUPPORTED] = "Option not Supported",
[RTSP_CODE_CNT] = NULL,
};
static const char *get_code_description(unsigned int code)
{
const char *error = "Internal Error";
if (code >= SHL_ARRAY_LENGTH(code_descriptions))
return error;
return code_descriptions[code] ? : error;
}
static size_t sanitize_line(char *line, size_t len)
{
char *src, *dst, c, prev, last_c;
size_t i;
bool quoted, escaped;
src = line;
dst = line;
last_c = 0;
quoted = false;
escaped = false;
for (i = 0; i < len; ++i) {
c = *src++;
prev = last_c;
last_c = c;
if (escaped) {
escaped = false;
/* turn escaped binary zero into "\0" */
if (c == '\0') {
c = '0';
last_c = c;
}
} else if (quoted) {
if (c == '"') {
quoted = false;
} else if (c == '\0') {
/* skip binary 0 */
last_c = prev;
continue;
} else if (c == '\\') {
escaped = true;
}
} else {
/* ignore any binary 0 */
if (c == '\0') {
last_c = prev;
continue;
}
/* turn new-lines/tabs into white-space */
if (c == '\r' || c == '\n' || c == '\t') {
c = ' ';
last_c = c;
}
/* trim whitespace */
if (c == ' ' && prev == ' ')
continue;
/* trim leading whitespace */
if (c == ' ' && dst == line)
continue;
if (c == '"')
quoted = true;
}
*dst++ = c;
}
/* terminate string with binary zero */
*dst = 0;
/* remove trailing whitespace */
if (!quoted) {
while (dst > line && *(dst - 1) == ' ')
*--dst = 0;
}
return dst - line;
}
/*
* Messages
* The message-layer is responsible of message handling for users. It does not
* do the wire-protocol parsing! It is solely responsible for the user API to
* assemble and inspect messages.
*
* We use per-message iterators to allow simply message-assembly and parsing in
* a sequential manner. We do some limited container-formats, so you can dive
* into a header, parse its contents and exit it again.
*
* Note that messages provide sealing-capabilities. Once a message is sealed,
* it can never be modified again. All messages that are submitted to the bus
* layer, or are received from the bus layer, are always sealed.
*/
static int rtsp_message_new(struct rtsp *bus,
struct rtsp_message **out)
{
_rtsp_message_unref_ struct rtsp_message *m = NULL;
if (!bus || !out)
return -EINVAL;
m = calloc(1, sizeof(*m));
if (!m)
return -ENOMEM;
m->ref = 1;
m->bus = bus;
rtsp_ref(bus);
m->type = RTSP_MESSAGE_UNKNOWN;
m->major = 1;
m->minor = 0;
*out = m;
m = NULL;
return 0;
}
static int rtsp_message_new_unknown(struct rtsp *bus,
struct rtsp_message **out,
const char *head)
{
_rtsp_message_unref_ struct rtsp_message *m = NULL;
int r;
if (!bus || !out || !head)
return -EINVAL;
r = rtsp_message_new(bus, &m);
if (r < 0)
return r;
m->type = RTSP_MESSAGE_UNKNOWN;
m->unknown_head = strdup(head);
if (!m->unknown_head)
return -ENOMEM;
*out = m;
m = NULL;
return 0;
}
static int rtsp_message_new_request_n(struct rtsp *bus,
struct rtsp_message **out,
const char *method,
size_t methodlen,
const char *uri,
size_t urilen)
{
_rtsp_message_unref_ struct rtsp_message *m = NULL;
int r;
if (!bus || !out)
return -EINVAL;
if (shl_isempty(method) || shl_isempty(uri) || !methodlen || !urilen)
return -EINVAL;
r = rtsp_message_new(bus, &m);
if (r < 0)
return r;
m->type = RTSP_MESSAGE_REQUEST;
m->request_method = strndup(method, methodlen);
if (!m->request_method)
return -ENOMEM;
m->request_uri = strndup(uri, urilen);
if (!m->request_uri)
return -ENOMEM;
*out = m;
m = NULL;
return 0;
}
int rtsp_message_new_request(struct rtsp *bus,
struct rtsp_message **out,
const char *method,
const char *uri)
{
if (!method || !uri)
return -EINVAL;
return rtsp_message_new_request_n(bus,
out,
method,
strlen(method),
uri,
strlen(uri));
}
static int rtsp_message_new_raw_reply(struct rtsp *bus,
struct rtsp_message **out,
unsigned int code,
const char *phrase)
{
_rtsp_message_unref_ struct rtsp_message *m = NULL;
int r;
if (!bus || !out)
return -EINVAL;
if (code == RTSP_ANY_CODE)
return -EINVAL;
r = rtsp_message_new(bus, &m);
if (r < 0)
return r;
m->type = RTSP_MESSAGE_REPLY;
m->reply_code = code;
if (shl_isempty(phrase))
m->reply_phrase = strdup(get_code_description(code));
else
m->reply_phrase = strdup(phrase);
if (!m->reply_phrase)
return -ENOMEM;
*out = m;
m = NULL;
return 0;
}
int rtsp_message_new_reply(struct rtsp *bus,
struct rtsp_message **out,
uint64_t cookie,
unsigned int code,
const char *phrase)
{
_rtsp_message_unref_ struct rtsp_message *m = NULL;
int r;
if (!bus || !out || !cookie)
return -EINVAL;
r = rtsp_message_new_raw_reply(bus, &m, code, phrase);
if (r < 0)
return r;
m->cookie = cookie | RTSP_FLAG_REMOTE_COOKIE;
*out = m;
m = NULL;
return 0;
}
int rtsp_message_new_reply_for(struct rtsp_message *orig,
struct rtsp_message **out,
unsigned int code,
const char *phrase)
{
_rtsp_message_unref_ struct rtsp_message *m = NULL;
int r;
if (!orig || !out)
return -EINVAL;
/* @orig must be a message received from the remote peer */
if (!orig->is_used || !(orig->cookie & RTSP_FLAG_REMOTE_COOKIE))
return -EINVAL;
r = rtsp_message_new_reply(orig->bus, &m, orig->cookie, code, phrase);
if (r < 0)
return r;
*out = m;
m = NULL;
return 0;
}
int rtsp_message_new_data(struct rtsp *bus,
struct rtsp_message **out,
unsigned int channel,
const void *payload,
size_t size)
{
_rtsp_message_unref_ struct rtsp_message *m = NULL;
int r;
if (!bus || !out)
return -EINVAL;
if (channel == RTSP_ANY_CHANNEL)
return -EINVAL;
if (size > 0 && !payload)
return -EINVAL;
r = rtsp_message_new(bus, &m);
if (r < 0)
return r;
m->type = RTSP_MESSAGE_DATA;
m->data_channel = channel;
m->data_size = size;
if (size > 0) {
m->data_payload = malloc(size);
if (!m->data_payload)
return -ENOMEM;
memcpy(m->data_payload, payload, size);
}
*out = m;
m = NULL;
return 0;
}
void rtsp_message_ref(struct rtsp_message *m)
{
if (!m || !m->ref)
return;
++m->ref;
}
void rtsp_message_unref(struct rtsp_message *m)
{
size_t i;
if (!m || !m->ref || --m->ref)
return;
for (i = 0; i < m->body_used; ++i) {
free(m->body_headers[i].key);
free(m->body_headers[i].value);
shl_strv_free(m->body_headers[i].tokens);
free(m->body_headers[i].line);
}
free(m->body_headers);
for (i = 0; i < m->header_used; ++i) {
free(m->headers[i].key);
free(m->headers[i].value);
shl_strv_free(m->headers[i].tokens);
free(m->headers[i].line);
}
free(m->headers);
free(m->raw);
free(m->body);
free(m->data_payload);
free(m->reply_phrase);
free(m->request_uri);
free(m->request_method);
free(m->unknown_head);
rtsp_unref(m->bus);
free(m);
}
bool rtsp_message_is_request(struct rtsp_message *m,
const char *method,
const char *uri)
{
return m && m->type == RTSP_MESSAGE_REQUEST &&
(!method || !strcasecmp(m->request_method, method)) &&
(!uri || !strcmp(m->request_uri, uri));
}
bool rtsp_message_is_reply(struct rtsp_message *m,
unsigned int code,
const char *phrase)
{
return m && m->type == RTSP_MESSAGE_REPLY &&
(code == RTSP_ANY_CODE || m->reply_code == code) &&
(!phrase || !strcmp(m->reply_phrase, phrase));
}
bool rtsp_message_is_data(struct rtsp_message *m,
unsigned int channel)
{
return m && m->type == RTSP_MESSAGE_DATA &&
(channel == RTSP_ANY_CHANNEL || m->data_channel == channel);
}
unsigned int rtsp_message_get_type(struct rtsp_message *m)
{
if (!m)
return RTSP_MESSAGE_UNKNOWN;
return m->type;
}
const char *rtsp_message_get_method(struct rtsp_message *m)
{
if (!m)
return NULL;
return m->request_method;
}
const char *rtsp_message_get_uri(struct rtsp_message *m)
{
if (!m)
return NULL;
return m->request_uri;
}
unsigned int rtsp_message_get_code(struct rtsp_message *m)
{
if (!m)
return RTSP_ANY_CODE;
return m->reply_code;
}
const char *rtsp_message_get_phrase(struct rtsp_message *m)
{
if (!m)
return NULL;
return m->reply_phrase;
}
unsigned int rtsp_message_get_channel(struct rtsp_message *m)
{
if (!m)
return RTSP_ANY_CHANNEL;
return m->data_channel;
}
const void *rtsp_message_get_payload(struct rtsp_message *m)
{
if (!m)
return NULL;
return m->data_payload;
}
size_t rtsp_message_get_payload_size(struct rtsp_message *m)
{
if (!m)
return 0;
return m->data_size;
}
struct rtsp *rtsp_message_get_bus(struct rtsp_message *m)
{
if (!m)
return NULL;
return m->bus;
}
uint64_t rtsp_message_get_cookie(struct rtsp_message *m)
{
if (!m)
return 0;
return m->cookie & ~RTSP_FLAG_REMOTE_COOKIE;
}
bool rtsp_message_is_sealed(struct rtsp_message *m)
{
return m && m->is_sealed;
}
static int rtsp_header_set_value(struct rtsp_header *h,
const char *value,
size_t valuelen,
bool force)
{
int r;
if (!valuelen || shl_isempty(value))
return -EINVAL;
if (!force) {
if (h->value || h->token_used || h->line)
return -EINVAL;
} else {
shl_strv_free(h->tokens);
h->tokens = NULL;
h->token_used = 0;
h->token_cnt = 0;
free(h->value);
h->value = NULL;
free(h->line);
h->line = NULL;
}
h->value = strndup(value, valuelen);
if (!h->value)
return -ENOMEM;
r = shl_qstr_tokenize(value, &h->tokens);
if (r < 0) {
free(h->value);
h->value = NULL;
return -ENOMEM;
}
h->token_cnt = r + 1;
h->token_used = r;
return 0;
}
static int rtsp_message_append_header(struct rtsp_message *m,
struct rtsp_header **out,
const char *key,
size_t keylen,
const char *value,
size_t valuelen)
{
struct rtsp_header *h;
int r;
if (!m || !out || !key)
return -EINVAL;
if (m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (m->iter_body) {
if (!SHL_GREEDY_REALLOC0_T(m->body_headers,
m->body_cnt,
m->body_used + 1))
return -ENOMEM;
h = &m->body_headers[m->body_used];
} else {
if (!SHL_GREEDY_REALLOC0_T(m->headers,
m->header_cnt,
m->header_used + 1))
return -ENOMEM;
h = &m->headers[m->header_used];
}
h->key = strndup(key, keylen);
if (!h->key)
return -ENOMEM;
if (valuelen) {
r = rtsp_header_set_value(h, value, valuelen, true);
if (r < 0) {
free(h->key);
return -ENOMEM;
}
}
if (m->iter_body) {
++m->body_used;
} else {
if (!strcasecmp(h->key, "Content-Length"))
m->header_clen = h;
if (!strcasecmp(h->key, "Content-Type"))
m->header_ctype = h;
else if (!strcasecmp(h->key, "CSeq"))
m->header_cseq = h;
++m->header_used;
}
*out = h;
return 0;
}
static int rtsp_message_append_header_line(struct rtsp_message *m,
struct rtsp_header **out,
const char *line)
{
struct rtsp_header *h;
const char *value;
char *t;
size_t keylen, valuelen;
int r;
if (!line)
return -EINVAL;
t = malloc(strlen(line) + 3);
if (!t)
return -ENOMEM;
value = strchrnul(line, ':');
keylen = value - line;
if (*value) {
++value;
valuelen = strlen(value);
} else {
value = NULL;
valuelen = 0;
}
while (keylen > 0 && line[keylen - 1] == ' ')
--keylen;
while (valuelen > 0 && value[valuelen - 1] == ' ')
--valuelen;
while (valuelen > 0 && value[0] == ' ') {
++value;
--valuelen;
}
r = rtsp_message_append_header(m,
&h,
line,
keylen,
value,
valuelen);
if (r < 0) {
free(t);
return r;
}
h->line = t;
t = stpcpy(t, line);
*t++ = '\r';
*t++ = '\n';
*t = '\0';
h->line_len = t - h->line;
if (out)
*out = h;
return 0;
}
static int rtsp_header_append_token(struct rtsp_header *h, const char *token)
{
if (!h || !token || h->line || h->value)
return -EINVAL;
if (!SHL_GREEDY_REALLOC0_T(h->tokens,
h->token_cnt,
h->token_used + 2))
return -ENOMEM;
h->tokens[h->token_used] = strdup(token);
if (!h->tokens[h->token_used])
return -ENOMEM;
++h->token_used;
return 0;
}
static int rtsp_header_serialize(struct rtsp_header *h)
{
static char *empty_strv[1] = { NULL };
char *t;
int r;
if (!h)
return -EINVAL;
if (h->line)
return 0;
if (!h->value) {
r = shl_qstr_join(h->tokens ? : empty_strv, &h->value);
if (r < 0)
return r;
}
t = malloc(strlen(h->key) + strlen(h->value) + 5);
if (!t)
return -ENOMEM;
h->line = t;
t = stpcpy(t, h->key);
*t++ = ':';
*t++ = ' ';
t = stpcpy(t, h->value);
*t++ = '\r';
*t++ = '\n';
*t = '\0';
h->line_len = t - h->line;
return 0;
}
int rtsp_message_append_line(struct rtsp_message *m, const char *line)
{
int r;
if (!m || !line || m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (m->is_sealed)
return -EBUSY;
if (m->iter_header)
return -EINVAL;
r = rtsp_message_append_header_line(m, NULL, line);
if (r < 0)
return r;
return 0;
}
int rtsp_message_open_header(struct rtsp_message *m, const char *name)
{
struct rtsp_header *h;
int r;
if (!m || shl_isempty(name) || m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (m->is_sealed)
return -EBUSY;
if (m->iter_header)
return -EINVAL;
r = rtsp_message_append_header(m, &h, name, strlen(name), NULL, 0);
if (r < 0)
return r;
m->iter_header = h;
return 0;
}
int rtsp_message_close_header(struct rtsp_message *m)
{
int r;
if (!m || m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (m->is_sealed)
return -EBUSY;
if (!m->iter_header)
return -EINVAL;
r = rtsp_header_serialize(m->iter_header);
if (r < 0)
return r;
m->iter_header = NULL;
return 0;
}
int rtsp_message_open_body(struct rtsp_message *m)
{
if (!m || m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (m->is_sealed)
return -EBUSY;
if (m->iter_header || m->iter_body)
return -EINVAL;
m->iter_body = true;
return 0;
}
int rtsp_message_close_body(struct rtsp_message *m)
{
if (!m || m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (m->is_sealed)
return -EBUSY;
if (!m->iter_body)
return -EINVAL;
if (m->iter_header)
return -EINVAL;
m->iter_body = false;
return 0;
}
int rtsp_message_append_basic(struct rtsp_message *m,
char type,
...)
{
va_list args;
int r;
va_start(args, type);
r = rtsp_message_appendv_basic(m, type, &args);
va_end(args);
return r;
}
int rtsp_message_appendv_basic(struct rtsp_message *m,
char type,
va_list *args)
{
char buf[128] = { };
const char *orig;
uint32_t u32;
int32_t i32;
if (!m || m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (m->is_sealed)
return -EBUSY;
switch (type) {
case RTSP_TYPE_RAW:
orig = va_arg(*args, const char*);
if (!orig)
orig = "";
if (m->iter_header)
return rtsp_header_set_value(m->iter_header,
orig,
strlen(orig),
false);
else
return rtsp_message_append_line(m, orig);
case RTSP_TYPE_HEADER_START:
orig = va_arg(*args, const char*);
return rtsp_message_open_header(m, orig);
case RTSP_TYPE_HEADER_END:
return rtsp_message_close_header(m);
case RTSP_TYPE_BODY_START:
return rtsp_message_open_body(m);
case RTSP_TYPE_BODY_END:
return rtsp_message_close_body(m);
}
if (!m->iter_header)
return -EINVAL;
switch (type) {
case RTSP_TYPE_STRING:
orig = va_arg(*args, const char*);
if (!orig)
orig = "";
break;
case RTSP_TYPE_INT32:
i32 = va_arg(*args, int32_t);
sprintf(buf, "%" PRId32, i32);
orig = buf;
break;
case RTSP_TYPE_UINT32:
u32 = va_arg(*args, uint32_t);
sprintf(buf, "%" PRIu32, u32);
orig = buf;
break;
default:
return -EINVAL;
}
return rtsp_header_append_token(m->iter_header, orig);
}
int rtsp_message_append(struct rtsp_message *m,
const char *types,
...)
{
va_list args;
int r;
va_start(args, types);
r = rtsp_message_appendv(m, types, &args);
va_end(args);
return r;
}
int rtsp_message_appendv(struct rtsp_message *m,
const char *types,
va_list *args)
{
int r;
if (!m || m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (m->is_sealed)
return -EBUSY;
if (!types)
return 0;
for ( ; *types; ++types) {
r = rtsp_message_appendv_basic(m, *types, args);
if (r < 0)
return r;
}
return 0;
}
static int rtsp_message_serialize_common(struct rtsp_message *m)
{
_shl_free_ char *head = NULL, *headers = NULL, *body = NULL;
char buf[128];
char *raw, *p, *cbody;
size_t rawlen, i, l, body_size;
int r;
switch (m->type) {
case RTSP_MESSAGE_UNKNOWN:
head = shl_strcat(m->unknown_head, "\r\n");
if (!head)
return -ENOMEM;
break;
case RTSP_MESSAGE_REQUEST:
r = asprintf(&head, "%s %s RTSP/%u.%u\r\n",
m->request_method,
m->request_uri,
m->major,
m->minor);
if (r < 0)
return -ENOMEM;
break;
case RTSP_MESSAGE_REPLY:
r = asprintf(&head, "RTSP/%u.%u %u %s\r\n",
m->major,
m->minor,
m->reply_code,
m->reply_phrase);
if (r < 0)
return -ENOMEM;
break;
default:
return -EINVAL;
}
rawlen = strlen(head);
/* concat body */
if (m->body) {
body_size = m->body_size;
cbody = (void*)m->body;
} else {
l = 0;
for (i = 0; i < m->body_used; ++i)
l += m->body_headers[i].line_len;
body = malloc(l + 1);
if (!body)
return -ENOMEM;
p = (char*)body;
for (i = 0; i < m->body_used; ++i)
p = stpcpy(p, m->body_headers[i].line);
*p = 0;
body_size = p - body;
cbody = body;
}
rawlen += body_size;
/* set content-length header */
if (m->header_clen) {
sprintf(buf, "%zu", body_size);
r = rtsp_header_set_value(m->header_clen,
buf,
strlen(buf),
true);
if (r < 0)
return r;
r = rtsp_header_serialize(m->header_clen);
if (r < 0)
return r;
} else if (body_size) {
rtsp_message_close_header(m);
rtsp_message_close_body(m);
r = rtsp_message_append(m, "<u>",
"Content-Length",
(uint32_t)body_size);
if (r < 0)
return r;
}
/* set content-type header */
if (m->body_used && m->header_ctype) {
r = rtsp_header_set_value(m->header_ctype,
"text/parameters",
15,
true);
if (r < 0)
return r;
r = rtsp_header_serialize(m->header_ctype);
if (r < 0)
return r;
} else if (m->body_used) {
rtsp_message_close_header(m);
rtsp_message_close_body(m);
r = rtsp_message_append(m, "<s>",
"Content-Type",
"text/parameters");
if (r < 0)
return r;
}
/* set cseq header */
sprintf(buf, "%llu", m->cookie & ~RTSP_FLAG_REMOTE_COOKIE);
if (m->header_cseq) {
r = rtsp_header_set_value(m->header_cseq,
buf,
strlen(buf),
true);
if (r < 0)
return r;
r = rtsp_header_serialize(m->header_cseq);
if (r < 0)
return r;
} else {
rtsp_message_close_header(m);
rtsp_message_close_body(m);
r = rtsp_message_append(m, "<s>",
"CSeq",
buf);
if (r < 0)
return r;
}
/* concat headers */
l = 0;
for (i = 0; i < m->header_used; ++i)
l += m->headers[i].line_len;
headers = malloc(l + 1);
if (!headers)
return -ENOMEM;
p = headers;
for (i = 0; i < m->header_used; ++i)
p = stpcpy(p, m->headers[i].line);
*p = 0;
rawlen += p - headers;
/* final concat */
rawlen += 2;
raw = malloc(rawlen + 1);
if (!raw)
return -ENOMEM;
p = raw;
p = stpcpy(p, head);
p = stpcpy(p, headers);
*p++ = '\r';
*p++ = '\n';
memcpy(p, cbody, body_size);
p += body_size;
/* for debugging */
*p = 0;
m->raw = (void*)raw;
m->raw_size = rawlen;
m->body = (void*)cbody;
m->body_size = body_size;
body = NULL;
return 0;
}
static int rtsp_message_serialize_data(struct rtsp_message *m)
{
uint8_t *raw;
size_t rawlen;
rawlen = 1 + 1 + 2 + m->data_size;
raw = malloc(rawlen + 1);
if (!raw)
return -ENOMEM;
raw[0] = '$';
raw[1] = m->data_channel;
raw[2] = (m->data_size & 0xff00U) >> 8;
raw[3] = (m->data_size & 0x00ffU);
if (m->data_size)
memcpy(&raw[4], m->data_payload, m->data_size);
/* for debugging */
raw[rawlen] = 0;
m->raw = raw;
m->raw_size = rawlen;
return 0;
}
int rtsp_message_set_cookie(struct rtsp_message *m, uint64_t cookie)
{
if (!m)
return -EINVAL;
if (m->is_sealed)
return -EBUSY;
m->cookie = cookie & ~RTSP_FLAG_REMOTE_COOKIE;
if (m->type == RTSP_MESSAGE_REPLY)
m->cookie |= RTSP_FLAG_REMOTE_COOKIE;
return 0;
}
int rtsp_message_seal(struct rtsp_message *m)
{
int r;
if (!m)
return -EINVAL;
if (m->is_sealed)
return 0;
if (m->iter_body || m->iter_header)
return -EINVAL;
if (!m->cookie)
m->cookie = ++m->bus->cookies ? : ++m->bus->cookies;
if (m->type == RTSP_MESSAGE_REPLY)
m->cookie |= RTSP_FLAG_REMOTE_COOKIE;
switch (m->type) {
case RTSP_MESSAGE_UNKNOWN:
case RTSP_MESSAGE_REQUEST:
case RTSP_MESSAGE_REPLY:
r = rtsp_message_serialize_common(m);
if (r < 0)
return r;
break;
case RTSP_MESSAGE_DATA:
r = rtsp_message_serialize_data(m);
if (r < 0)
return r;
break;
}
m->is_sealed = true;
return 0;
}
int rtsp_message_enter_header(struct rtsp_message *m, const char *name)
{
size_t i;
if (!m || shl_isempty(name) || m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (!m->is_sealed)
return -EBUSY;
if (m->iter_header)
return -EINVAL;
if (m->iter_body) {
for (i = 0; i < m->body_used; ++i) {
if (!strcasecmp(m->body_headers[i].key, name)) {
m->iter_header = &m->body_headers[i];
m->iter_token = 0;
return 0;
}
}
} else {
for (i = 0; i < m->header_used; ++i) {
if (!strcasecmp(m->headers[i].key, name)) {
m->iter_header = &m->headers[i];
m->iter_token = 0;
return 0;
}
}
}
return -ENOENT;
}
void rtsp_message_exit_header(struct rtsp_message *m)
{
if (!m || !m->is_sealed || m->type == RTSP_MESSAGE_DATA)
return;
if (!m->iter_header)
return;
m->iter_header = NULL;
}
int rtsp_message_enter_body(struct rtsp_message *m)
{
if (!m || m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (!m->is_sealed)
return -EBUSY;
if (m->iter_header)
return -EINVAL;
if (m->iter_body)
return -EINVAL;
m->iter_body = true;
return 0;
}
void rtsp_message_exit_body(struct rtsp_message *m)
{
if (!m || !m->is_sealed || m->type == RTSP_MESSAGE_DATA)
return;
if (!m->iter_body)
return;
m->iter_body = false;
m->iter_header = NULL;
}
int rtsp_message_read_basic(struct rtsp_message *m,
char type,
...)
{
va_list args;
int r;
va_start(args, type);
r = rtsp_message_readv_basic(m, type, &args);
va_end(args);
return r;
}
int rtsp_message_readv_basic(struct rtsp_message *m,
char type,
va_list *args)
{
const char *key;
const char **out_str, *entry;
int32_t i32, *out_i32;
uint32_t u32, *out_u32;
if (!m || m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (!m->is_sealed)
return -EBUSY;
switch (type) {
case RTSP_TYPE_RAW:
if (!m->iter_header)
return -EINVAL;
out_str = va_arg(*args, const char**);
if (out_str)
*out_str = m->iter_header->value ? : "";
return 0;
case RTSP_TYPE_HEADER_START:
key = va_arg(*args, const char*);
return rtsp_message_enter_header(m, key);
case RTSP_TYPE_HEADER_END:
rtsp_message_exit_header(m);
return 0;
case RTSP_TYPE_BODY_START:
return rtsp_message_enter_body(m);
case RTSP_TYPE_BODY_END:
rtsp_message_exit_body(m);
return 0;
}
if (!m->iter_header)
return -EINVAL;
if (m->iter_token >= m->iter_header->token_used)
return -ENOENT;
entry = m->iter_header->tokens[m->iter_token];
switch (type) {
case RTSP_TYPE_STRING:
out_str = va_arg(*args, const char**);
if (out_str)
*out_str = entry;
break;
case RTSP_TYPE_INT32:
if (sscanf(entry, "%" SCNd32, &i32) != 1)
return -EINVAL;
out_i32 = va_arg(*args, int32_t*);
if (out_i32)
*out_i32 = i32;
break;
case RTSP_TYPE_UINT32:
if (sscanf(entry, "%" SCNu32, &u32) != 1)
return -EINVAL;
out_u32 = va_arg(*args, uint32_t*);
if (out_u32)
*out_u32 = u32;
break;
case RTSP_TYPE_HEX32:
if (sscanf(entry, "%" SCNx32, &u32) != 1)
return -EINVAL;
out_u32 = va_arg(*args, uint32_t*);
if (out_u32)
*out_u32 = u32;
break;
case RTSP_TYPE_SKIP:
/* just increment token */
break;
default:
return -EINVAL;
}
++m->iter_token;
return 0;
}
int rtsp_message_read(struct rtsp_message *m,
const char *types,
...)
{
va_list args;
int r;
va_start(args, types);
r = rtsp_message_readv(m, types, &args);
va_end(args);
return r;
}
int rtsp_message_readv(struct rtsp_message *m,
const char *types,
va_list *args)
{
int r;
if (!m || m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (!m->is_sealed)
return -EBUSY;
if (!types)
return 0;
for ( ; *types; ++types) {
r = rtsp_message_readv_basic(m, *types, args);
if (r < 0) {
if (m->iter_body)
rtsp_message_exit_body(m);
if (m->iter_header)
rtsp_message_exit_header(m);
return r;
}
}
return 0;
}
int rtsp_message_skip_basic(struct rtsp_message *m, char type)
{
return rtsp_message_read_basic(m, type, NULL, NULL, NULL, NULL);
}
int rtsp_message_skip(struct rtsp_message *m, const char *types)
{
int r;
if (!m || m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (!m->is_sealed)
return -EBUSY;
if (!types)
return 0;
for ( ; *types; ++types) {
r = rtsp_message_skip_basic(m, *types);
if (r < 0)
return r;
}
return 0;
}
int rtsp_message_rewind(struct rtsp_message *m, bool complete)
{
if (!m || m->type == RTSP_MESSAGE_DATA)
return -EINVAL;
if (!m->is_sealed)
return -EBUSY;
m->iter_token = 0;
if (complete) {
m->iter_body = false;
m->iter_header = NULL;
}
return 0;
}
void *rtsp_message_get_body(struct rtsp_message *m)
{
if (!m || m->type == RTSP_MESSAGE_DATA)
return NULL;
if (!m->is_sealed)
return NULL;
return m->body;
}
size_t rtsp_message_get_body_size(struct rtsp_message *m)
{
if (!m || m->type == RTSP_MESSAGE_DATA)
return 0;
if (!m->is_sealed)
return 0;
return m->body_size;
}
void *rtsp_message_get_raw(struct rtsp_message *m)
{
if (!m)
return NULL;
if (!m->is_sealed)
return NULL;
return m->raw;
}
size_t rtsp_message_get_raw_size(struct rtsp_message *m)
{
if (!m)
return 0;
if (!m->is_sealed)
return 0;
return m->raw_size;
}
/*
* Message Assembly
* These helpers take the raw RTSP input strings, parse them line by line to
* assemble an rtsp_message object.
*/
static int rtsp_message_from_request(struct rtsp *bus,
struct rtsp_message **out,
const char *line)
{
struct rtsp_message *m;
unsigned int major, minor;
size_t cmdlen, urllen;
const char *next, *prev, *cmd, *url;
int r;
if (!bus || !line)
return -EINVAL;
/*
* Requests look like this:
* <cmd> <url> RTSP/<major>.<minor>
*/
next = line;
/* parse <cmd> */
cmd = line;
next = strchr(next, ' ');
if (!next || next == cmd)
goto error;
cmdlen = next - cmd;
/* skip " " */
++next;
/* parse <url> */
url = next;
next = strchr(next, ' ');
if (!next || next == url)
goto error;
urllen = next - url;
/* skip " " */
++next;
/* parse "RTSP/" */
if (strncasecmp(next, "RTSP/", 5))
goto error;
next += 5;
/* parse "%u" */
prev = next;
shl_atoi_u(prev, 10, (const char**)&next, &major);
if (next == prev || *next != '.')
goto error;
/* skip "." */
++next;
/* parse "%u" */
prev = next;
shl_atoi_u(prev, 10, (const char**)&next, &minor);
if (next == prev || *next)
goto error;
r = rtsp_message_new_request_n(bus, &m, cmd, cmdlen, url, urllen);
if (r < 0)
return r;
m->major = major;
m->minor = minor;
*out = m;
return 0;
error:
/*
* Invalid request line.. Set type to UNKNOWN and let the caller deal
* with it. We will not try to send any error to avoid triggering
* another error if the remote side doesn't understand proper RTSP (or
* if our implementation is buggy).
*/
return rtsp_message_new_unknown(bus, out, line);
}
static int rtsp_message_from_reply(struct rtsp *bus,
struct rtsp_message **out,
const char *line)
{
struct rtsp_message *m;
unsigned int major, minor, code;
const char *prev, *next, *str;
int r;
if (!bus || !out || !line)
return -EINVAL;
/*
* Responses look like this:
* RTSP/<major>.<minor> <code> <string..>
* RTSP/%u.%u %u %s
* We first parse the RTSP version and code. Everything appended to
* this is optional and represents the error string.
*/
/* parse "RTSP/" */
if (strncasecmp(line, "RTSP/", 5))
goto error;
next = &line[5];
/* parse "%u" */
prev = next;
shl_atoi_u(prev, 10, (const char**)&next, &major);
if (next == prev || *next != '.')
goto error;
/* skip "." */
++next;
/* parse "%u" */
prev = next;
shl_atoi_u(prev, 10, (const char**)&next, &minor);
if (next == prev || *next != ' ')
goto error;
/* skip " " */
++next;
/* parse: %u */
prev = next;
shl_atoi_u(prev, 10, (const char**)&next, &code);
if (next == prev)
goto error;
if (*next && *next != ' ')
goto error;
/* skip " " */
if (*next)
++next;
/* parse: %s */
str = next;
r = rtsp_message_new_raw_reply(bus, &m, code, str);
if (r < 0)
return r;
m->major = major;
m->minor = minor;
*out = m;
return 0;
error:
/*
* Couldn't parse line. Avoid sending an error message as we could
* trigger another error and end up in an endless error loop. Instead,
* set message type to UNKNOWN and let the caller deal with it.
*/
return rtsp_message_new_unknown(bus, out, line);
}
static int rtsp_message_from_head(struct rtsp *bus,
struct rtsp_message **out,
const char *line)
{
if (!bus || !out || !line)
return -EINVAL;
if (!strncasecmp(line, "RTSP/", 5))
return rtsp_message_from_reply(bus, out, line);
else
return rtsp_message_from_request(bus, out, line);
}
static size_t rtsp__strncspn(const char *str,
size_t len,
const char *reject)
{
size_t i, j;
for (i = 0; i < len; ++i)
for (j = 0; reject[j]; ++j)
if (str[i] == reject[j])
return i;
return i;
}
static int rtsp_message_append_body(struct rtsp_message *m,
const void *body,
size_t len)
{
_shl_free_ char *line = NULL;
const char *d, *v;
void *t;
size_t dl, vl;
int r;
if (!m)
return -EINVAL;
if (len > 0 && !body)
return -EINVAL;
/* if body is empty, nothing to do */
if (!len)
return 0;
/* Usually, we should verify the content-length
* parameter here. However, that's not needed if the
* input is of fixed length, so we skip that. It's
* the caller's responsibility to do that. */
/* if content-type is not text/parameters, append the binary blob */
if (!m->header_ctype ||
!m->header_ctype->value ||
strcmp(m->header_ctype->value, "text/parameters")) {
t = malloc(len + 1);
if (!t)
return -ENOMEM;
free(m->body);
m->body = t;
memcpy(m->body, body, len);
m->body_size = len;
return 0;
}
r = rtsp_message_open_body(m);
if (r < 0)
return r;
d = body;
while (len > 0) {
dl = rtsp__strncspn(d, len, "\r\n");
v = d;
vl = dl;
/* allow \r, \n, and \r\n as terminator */
if (dl < len) {
++dl;
if (d[dl] == '\r') {
if (dl < len && d[dl] == '\n')
++dl;
}
}
d += dl;
len -= dl;
/* ignore empty body lines */
if (vl > 0) {
free(line);
line = malloc(vl + 1);
if (!line)
return -ENOMEM;
memcpy(line, v, vl);
line[vl] = 0;
sanitize_line(line, vl);
/* full header; append to message */
r = rtsp_message_append_header_line(m, NULL, line);
if (r < 0)
return r;
}
}
r = rtsp_message_close_body(m);
if (r < 0)
return r;
return 0;
}
int rtsp_message_new_from_raw(struct rtsp *bus,
struct rtsp_message **out,
const void *data,
size_t len)
{
_rtsp_message_unref_ struct rtsp_message *m = NULL;
_shl_free_ char *line = NULL;
const char *d, *v;
size_t dl, vl;
int r;
if (!bus)
return -EINVAL;
if (len > 0 && !data)
return -EINVAL;
d = data;
while (len > 0) {
dl = rtsp__strncspn(d, len, "\r\n");
v = d;
vl = dl;
/* allow \r, \n, and \r\n as terminator */
if (dl < len && d[dl++] == '\r')
if (dl < len && d[dl] == '\n')
++dl;
d += dl;
len -= dl;
if (!vl) {
/* empty line; start of body */
if (!m) {
r = rtsp_message_from_head(bus, &m, "");
if (r < 0)
return r;
}
r = rtsp_message_append_body(m, d, len);
if (r < 0)
return r;
break;
} else {
free(line);
line = malloc(vl + 1);
if (!line)
return -ENOMEM;
memcpy(line, v, vl);
line[vl] = 0;
sanitize_line(line, vl);
if (m) {
/* full header; append to message */
r = rtsp_message_append_header_line(m,
NULL,
line);
} else {
/* head line; create message */
r = rtsp_message_from_head(bus, &m, line);
}
if (r < 0)
return r;
}
}
if (!m)
return -EINVAL;
r = rtsp_message_seal(m);
if (r < 0)
return r;
*out = m;
m = NULL;
return 0;
}
/*
* Parser State Machine
* The parser state-machine is quite simple. We take an input buffer of
* arbitrary length from the caller and feed it byte by byte into the state
* machine.
*
* Parsing RTSP messages is rather troublesome due to the ASCII-nature. It's
* easy to parse as is, but has lots of corner-cases which we want to be
* compatible to maybe broken implementations. Thus, we need this
* state-machine.
*
* All we do here is split the endless input stream into header-lines. The
* header-lines are not handled by the state-machine itself but passed on. If a
* message contains an entity payload, we parse the body. Otherwise, we submit
* the message and continue parsing the next one.
*/
static int parser_append_header(struct rtsp *bus,
char *line)
{
struct rtsp_parser *dec = &bus->parser;
struct rtsp_header *h;
size_t clen;
const char *next;
int r;
r = rtsp_message_append_header_line(dec->m,
&h,
line);
if (r < 0)
return r;
if (h == dec->m->header_clen) {
/* Screwed content-length line? We cannot recover from that as
* the attached entity is of unknown length. Abort.. */
if (h->token_used < 1)
return -EINVAL;
r = shl_atoi_z(h->tokens[0], 10, &next, &clen);
if (r < 0 || *next)
return -EINVAL;
/* overwrite previous lengths */
dec->remaining_body = clen;
} else if (h == dec->m->header_cseq) {
if (h->token_used >= 1) {
r = shl_atoi_z(h->tokens[0], 10, &next, &clen);
if (r >= 0 &&
!*next &&
!(clen & RTSP_FLAG_REMOTE_COOKIE)) {
/* overwrite previous values */
dec->m->cookie = clen | RTSP_FLAG_REMOTE_COOKIE;
}
}
}
return r;
}
static int parser_finish_header_line(struct rtsp *bus)
{
struct rtsp_parser *dec = &bus->parser;
_shl_free_ char *line = NULL;
int r;
line = malloc(dec->buflen + 1);
if (!line)
return -ENOMEM;
shl_ring_copy(&dec->buf, line, dec->buflen);
line[dec->buflen] = 0;
sanitize_line(line, dec->buflen);
if (!dec->m)
r = rtsp_message_from_head(bus, &dec->m, line);
else
r = parser_append_header(bus, line);
return r;
}
static int parser_submit(struct rtsp *bus)
{
_rtsp_message_unref_ struct rtsp_message *m = NULL;
struct rtsp_parser *dec = &bus->parser;
int r;
if (!dec->m)
return 0;
m = dec->m;
dec->m = NULL;
r = rtsp_message_seal(m);
if (r < 0)
return r;
m->is_used = true;
return rtsp_incoming_message(m);
}
static int parser_submit_data(struct rtsp *bus, uint8_t *p)
{
_rtsp_message_unref_ struct rtsp_message *m = NULL;
struct rtsp_parser *dec = &bus->parser;
int r;
r = rtsp_message_new_data(bus,
&m,
dec->data_channel,
p,
dec->data_size);
if (r < 0) {
free(p);
return r;
}
r = rtsp_message_seal(m);
if (r < 0)
return r;
m->is_used = true;
return rtsp_incoming_message(m);
}
static int parser_feed_char_new(struct rtsp *bus, char ch)
{
struct rtsp_parser *dec = &bus->parser;
switch (ch) {
case '\r':
case '\n':
case '\t':
case ' ':
/* If no msg has been started, yet, we ignore LWS for
* compatibility reasons. Note that they're actually not
* allowed, but should be ignored by implementations. */
++dec->buflen;
break;
case '$':
/* Interleaved data. Followed by 1 byte channel-id and 2-byte
* data-length. */
dec->state = STATE_DATA_HEAD;
dec->data_channel = 0;
dec->data_size = 0;
/* clear any previous whitespace and leading '$' */
shl_ring_pull(&dec->buf, dec->buflen + 1);
dec->buflen = 0;
break;
default:
/* Clear any pending data in the ring-buffer and then just
* push the char into the buffer. Any char except LWS is fine
* here. */
dec->state = STATE_HEADER;
dec->remaining_body = 0;
shl_ring_pull(&dec->buf, dec->buflen);
dec->buflen = 1;
break;
}
return 0;
}
static int parser_feed_char_header(struct rtsp *bus, char ch)
{
struct rtsp_parser *dec = &bus->parser;
int r;
switch (ch) {
case '\r':
if (dec->last_chr == '\r' || dec->last_chr == '\n') {
/* \r\r means empty new-line. We actually allow \r\r\n,
* too. \n\r means empty new-line, too, but might also
* be finished off as \n\r\n so go to STATE_HEADER_NL
* to optionally complete the new-line.
* However, if the body is empty, we need to finish the
* msg early as there might be no \n coming.. */
dec->state = STATE_HEADER_NL;
/* First finish the last header line if any. Don't
* include the current \r as it is already part of the
* empty following line. */
r = parser_finish_header_line(bus);
if (r < 0)
return r;
/* discard buffer *and* whitespace */
shl_ring_pull(&dec->buf, dec->buflen + 1);
dec->buflen = 0;
/* No remaining body. Finish message! */
if (!dec->remaining_body) {
r = parser_submit(bus);
if (r < 0)
return r;
}
} else {
/* '\r' following any character just means newline
* (optionally followed by \n). We don't do anything as
* it might be a continuation line. */
++dec->buflen;
}
break;
case '\n':
if (dec->last_chr == '\n') {
/* We got \n\n, which means we need to finish the
* current header-line. If there's no remaining body,
* we immediately finish the message and go to
* STATE_NEW. Otherwise, we go to STATE_BODY
* straight. */
/* don't include second \n in header-line */
r = parser_finish_header_line(bus);
if (r < 0)
return r;
/* discard buffer *and* whitespace */
shl_ring_pull(&dec->buf, dec->buflen + 1);
dec->buflen = 0;
if (dec->remaining_body) {
dec->state = STATE_BODY;
} else {
dec->state = STATE_NEW;
r = parser_submit(bus);
if (r < 0)
return r;
}
} else if (dec->last_chr == '\r') {
/* We got an \r\n. We cannot finish the header line as
* it might be a continuation line. Next character
* decides what to do. Don't do anything here.
* \r\n\r cannot happen here as it is handled by
* STATE_HEADER_NL. */
++dec->buflen;
} else {
/* Same as above, we cannot finish the line as it
* might be a continuation line. Do nothing. */
++dec->buflen;
}
break;
case '\t':
case ' ':
/* Whitespace. Simply push into buffer and don't do anything.
* In case of a continuation line, nothing has to be done,
* either. */
++dec->buflen;
break;
default:
if (dec->last_chr == '\r' || dec->last_chr == '\n') {
/* Last line is complete and this is no whitespace,
* thus it's not a continuation line.
* Finish the line. */
/* don't include new char in line */
r = parser_finish_header_line(bus);
if (r < 0)
return r;
shl_ring_pull(&dec->buf, dec->buflen);
dec->buflen = 0;
}
/* consume character and handle special chars */
++dec->buflen;
if (ch == '"') {
/* go to STATE_HEADER_QUOTE */
dec->state = STATE_HEADER_QUOTE;
dec->quoted = false;
}
break;
}
return 0;
}
static int parser_feed_char_header_quote(struct rtsp *bus, char ch)
{
struct rtsp_parser *dec = &bus->parser;
if (dec->last_chr == '\\' && !dec->quoted) {
/* This character is quoted, so copy it unparsed. To handle
* double-backslash, we set the "quoted" bit. */
++dec->buflen;
dec->quoted = true;
} else {
dec->quoted = false;
/* consume character and handle special chars */
++dec->buflen;
if (ch == '"')
dec->state = STATE_HEADER;
}
return 0;
}
static int parser_feed_char_body(struct rtsp *bus, char ch)
{
struct rtsp_parser *dec = &bus->parser;
char *line;
int r;
/* If remaining_body was already 0, the message had no body. Note that
* messages without body are finished early, so no need to call
* decoder_submit() here. Simply forward @ch to STATE_NEW.
* @rlen is usually 0. We don't care and forward it, too. */
if (!dec->remaining_body) {
dec->state = STATE_NEW;
return parser_feed_char_new(bus, ch);
}
/* *any* character is allowed as body */
++dec->buflen;
if (!--dec->remaining_body) {
/* full body received, copy it and go to STATE_NEW */
if (dec->m) {
line = malloc(dec->buflen + 1);
if (!line)
return -ENOMEM;
shl_ring_copy(&dec->buf, line, dec->buflen);
line[dec->buflen] = 0;
r = rtsp_message_append_body(dec->m,
line,
dec->buflen);
if (r >= 0)
r = parser_submit(bus);
free(line);
} else {
r = 0;
}
dec->state = STATE_NEW;
shl_ring_pull(&dec->buf, dec->buflen);
dec->buflen = 0;
if (r < 0)
return r;
}
return 0;
}
static int parser_feed_char_header_nl(struct rtsp *bus, char ch)
{
struct rtsp_parser *dec = &bus->parser;
/* STATE_HEADER_NL means we received an empty line ending with \r. The
* standard requires a following \n but advises implementations to
* accept \r on itself, too.
* What we do is to parse a \n as end-of-header and any character as
* end-of-header plus start-of-body. Note that we discard anything in
* the ring-buffer that has already been parsed (which normally can
* nothing, but lets be safe). */
if (ch == '\n') {
/* discard transition chars plus new \n */
shl_ring_pull(&dec->buf, dec->buflen + 1);
dec->buflen = 0;
dec->state = STATE_BODY;
if (!dec->remaining_body)
dec->state = STATE_NEW;
return 0;
} else {
/* discard any transition chars and push @ch into body */
shl_ring_pull(&dec->buf, dec->buflen);
dec->buflen = 0;
dec->state = STATE_BODY;
return parser_feed_char_body(bus, ch);
}
}
static int parser_feed_char_data_head(struct rtsp *bus, char ch)
{
struct rtsp_parser *dec = &bus->parser;
uint8_t buf[3];
/* Read 1 byte channel-id and 2 byte body length. */
if (++dec->buflen >= 3) {
shl_ring_copy(&dec->buf, buf, 3);
shl_ring_pull(&dec->buf, dec->buflen);
dec->buflen = 0;
dec->data_channel = buf[0];
dec->data_size = (((uint16_t)buf[1]) << 8) | (uint16_t)buf[2];
dec->state = STATE_DATA_BODY;
}
return 0;
}
static int parser_feed_char_data_body(struct rtsp *bus, char ch)
{
struct rtsp_parser *dec = &bus->parser;
uint8_t *buf;
int r;
/* Read @dec->data_size bytes of raw data. */
if (++dec->buflen >= dec->data_size) {
buf = malloc(dec->data_size + 1);
if (!buf)
return -ENOMEM;
/* Not really needed, but in case it's actually a text-payload
* make sure it's 0-terminated to work around client bugs. */
buf[dec->data_size] = 0;
shl_ring_copy(&dec->buf, buf, dec->data_size);
r = parser_submit_data(bus, buf);
free(buf);
dec->state = STATE_NEW;
shl_ring_pull(&dec->buf, dec->buflen);
dec->buflen = 0;
if (r < 0)
return r;
}
return 0;
}
static int parser_feed_char(struct rtsp *bus, char ch)
{
struct rtsp_parser *dec = &bus->parser;
int r = 0;
switch (dec->state) {
case STATE_NEW:
r = parser_feed_char_new(bus, ch);
break;
case STATE_HEADER:
r = parser_feed_char_header(bus, ch);
break;
case STATE_HEADER_QUOTE:
r = parser_feed_char_header_quote(bus, ch);
break;
case STATE_HEADER_NL:
r = parser_feed_char_header_nl(bus, ch);
break;
case STATE_BODY:
r = parser_feed_char_body(bus, ch);
break;
case STATE_DATA_HEAD:
r = parser_feed_char_data_head(bus, ch);
break;
case STATE_DATA_BODY:
r = parser_feed_char_data_body(bus, ch);
break;
}
return r;
}
static int rtsp_parse_data(struct rtsp *bus,
const char *buf,
size_t len)
{
struct rtsp_parser *dec = &bus->parser;
size_t i;
int r;
if (!len)
return -EAGAIN;
/*
* We keep dec->buflen as cache for the current parsed-buffer size. We
* need to push the whole input-buffer into our parser-buffer and go
* through it one-by-one. The parser increments dec->buflen for each of
* these and once we're done, we verify our state is consistent.
*/
dec->buflen = shl_ring_get_size(&dec->buf);
r = shl_ring_push(&dec->buf, buf, len);
if (r < 0)
return r;
for (i = 0; i < len; ++i) {
r = parser_feed_char(bus, buf[i]);
if (r < 0)
return r;
dec->last_chr = buf[i];
}
/* check for internal parser inconsistencies; should not happen! */
if (dec->buflen != shl_ring_get_size(&dec->buf))
return -EFAULT;
return 0;
}
/*
* Bus Management
* The bus layer is responsible of sending and receiving messages. It hooks
* into any sd-event loop and properly serializes rtsp_message objects to the
* given file-descriptor and vice-versa.
*
* On any I/O error, the bus layer tries to drain the input-queue and then pass
* the HUP to the user. This way, we try to get all messages that the remote
* side sent to us, before we give up and close the stream.
*
* Note that this layer is independent of the underlying transport. However, we
* require the transport to be stream-based. Packet-based transports are not
* supported and will fail silently.
*/
static int rtsp_call_message(struct rtsp_message *m,
struct rtsp_message *reply)
{
int r;
/* protect users by making sure arguments stay around */
rtsp_message_ref(m);
rtsp_message_ref(reply);
if (m->cb_fn)
r = m->cb_fn(m->bus, reply, m->fn_data);
else
r = 0;
rtsp_message_unref(reply);
rtsp_message_unref(m);
return r;
}
static int rtsp_call_reply(struct rtsp *bus, struct rtsp_message *reply)
{
struct rtsp_message *m;
uint64_t *elem;
int r;
if (!shl_htable_lookup_u64(&bus->waiting,
reply->cookie & ~RTSP_FLAG_REMOTE_COOKIE,
&elem))
return 0;
m = rtsp_message_from_htable(elem);
rtsp_message_ref(m);
rtsp_drop_message(m);
r = rtsp_call_message(m, reply);
rtsp_message_unref(m);
return r;
}
static int rtsp_call(struct rtsp *bus, struct rtsp_message *m)
{
struct rtsp_match *match;
struct shl_dlist *i, *t;
int r;
/* make sure bus and message stay around during any callbacks */
rtsp_ref(bus);
rtsp_message_ref(m);
r = 0;
bus->is_calling = true;
shl_dlist_for_each(i, &bus->matches) {
match = shl_dlist_entry(i, struct rtsp_match, list);
r = match->cb_fn(bus, m, match->data);
if (r != 0)
break;
}
bus->is_calling = false;
shl_dlist_for_each_safe(i, t, &bus->matches) {
match = shl_dlist_entry(i, struct rtsp_match, list);
if (match->is_removed)
rtsp_free_match(match);
}
rtsp_message_unref(m);
rtsp_unref(bus);
return r;
}
static int rtsp_hup(struct rtsp *bus)
{
if (bus->is_dead)
return 0;
rtsp_detach_event(bus);
bus->is_dead = true;
return rtsp_call(bus, NULL);
}
static int rtsp_timer_fn(sd_event_source *src, uint64_t usec, void *data)
{
struct rtsp_message *m = data;
int r;
/* make sure message stays around during unlinking and callbacks */
rtsp_message_ref(m);
sd_event_source_set_enabled(m->timer_source, SD_EVENT_OFF);
rtsp_drop_message(m);
r = rtsp_call_message(m, NULL);
rtsp_message_unref(m);
return r;
}
static int rtsp_link_waiting(struct rtsp_message *m)
{
int r;
r = shl_htable_insert_u64(&m->bus->waiting, &m->cookie);
if (r < 0)
return r;
/* no need to wait for timeout if no-body listens */
if (m->bus->event && m->cb_fn) {
r = sd_event_add_time(m->bus->event,
&m->timer_source,
CLOCK_MONOTONIC,
m->timeout,
0,
rtsp_timer_fn,
m);
if (r < 0)
goto error;
r = sd_event_source_set_priority(m->timer_source,
m->bus->priority);
if (r < 0)
goto error;
}
m->is_waiting = true;
++m->bus->waiting_cnt;
rtsp_message_ref(m);
return 0;
error:
sd_event_source_unref(m->timer_source);
m->timer_source = NULL;
shl_htable_remove_u64(&m->bus->waiting, m->cookie, NULL);
return r;
}
static void rtsp_unlink_waiting(struct rtsp_message *m)
{
if (m->is_waiting) {
sd_event_source_unref(m->timer_source);
m->timer_source = NULL;
shl_htable_remove_u64(&m->bus->waiting, m->cookie, NULL);
m->is_waiting = false;
--m->bus->waiting_cnt;
rtsp_message_unref(m);
}
}
static void rtsp_link_outgoing(struct rtsp_message *m)
{
shl_dlist_link_tail(&m->bus->outgoing, &m->list);
m->is_outgoing = true;
++m->bus->outgoing_cnt;
rtsp_message_ref(m);
}
static void rtsp_unlink_outgoing(struct rtsp_message *m)
{
if (m->is_outgoing) {
shl_dlist_unlink(&m->list);
m->is_outgoing = false;
m->is_sending = false;
--m->bus->outgoing_cnt;
rtsp_message_unref(m);
}
}
static int rtsp_incoming_message(struct rtsp_message *m)
{
int r;
switch (m->type) {
case RTSP_MESSAGE_UNKNOWN:
case RTSP_MESSAGE_REQUEST:
case RTSP_MESSAGE_DATA:
/* simply forward all these to the match-handlers */
r = rtsp_call(m->bus, m);
if (r < 0)
return r;
break;
case RTSP_MESSAGE_REPLY:
/* find the waiting request and invoke the handler */
r = rtsp_call_reply(m->bus, m);
if (r < 0)
return r;
break;
}
return 0;
}
static int rtsp_read(struct rtsp *bus)
{
char buf[4096];
ssize_t res;
res = recv(bus->fd,
buf,
sizeof(buf),
MSG_DONTWAIT);
if (res < 0) {
if (errno == EAGAIN || errno == EINTR)
return -EAGAIN;
return -errno;
} else if (!res) {
/* there're no 0-length packets on streams; this is EOF */
return -EPIPE;
} else if (res > sizeof(buf)) {
res = sizeof(buf);
}
/* parses all messages and calls rtsp_incoming_message() for each */
return rtsp_parse_data(bus, buf, res);
}
static int rtsp_write_message(struct rtsp_message *m)
{
size_t remaining;
ssize_t res;
m->is_sending = true;
remaining = m->raw_size - m->sent;
res = send(m->bus->fd,
&m->raw[m->sent],
remaining,
MSG_NOSIGNAL | MSG_DONTWAIT);
if (res < 0) {
if (errno == EAGAIN || errno == EINTR)
return -EAGAIN;
return -errno;
} else if (res > (ssize_t)remaining) {
res = remaining;
}
m->sent += res;
if (m->sent >= m->raw_size) {
/* no need to wait for answer if no-body listens */
if (!m->cb_fn)
rtsp_unlink_waiting(m);
/* might destroy the message */
rtsp_unlink_outgoing(m);
}
return 0;
}
static int rtsp_write(struct rtsp *bus)
{
struct rtsp_message *m;
if (shl_dlist_empty(&bus->outgoing))
return 0;
m = shl_dlist_first_entry(&bus->outgoing, struct rtsp_message, list);
return rtsp_write_message(m);
}
static int rtsp_io_fn(sd_event_source *src, int fd, uint32_t mask, void *data)
{
struct rtsp *bus = data;
int r, write_r;
/* make sure bus stays around during any possible callbacks */
rtsp_ref(bus);
/*
* Whenever we encounter I/O errors, we have to make sure to drain the
* input queue first, before we handle any HUP. A server might send us
* a message and immediately close the queue. We must not handle the
* HUP first or we loose data.
* Therefore, if we read a message successfully, we always return
* success and wait for the next event-loop iteration. Furthermore,
* whenever there is a write-error, we must try reading from the input
* queue even if EPOLLIN is not set. The input might have arrived in
* between epoll_wait() and send(). Therefore, write-errors are only
* ever handled if the input-queue is empty. In all other cases they
* are ignored until either reading fails or the input queue is empty.
*/
if (mask & EPOLLOUT) {
write_r = rtsp_write(bus);
if (write_r == -EAGAIN)
write_r = 0;
} else {
write_r = 0;
}
if (mask & EPOLLIN || write_r < 0) {
r = rtsp_read(bus);
if (r < 0 && r != -EAGAIN)
goto error;
else if (r >= 0)
goto out;
}
if (!(mask & (EPOLLHUP | EPOLLERR)) && write_r >= 0) {
r = 0;
goto out;
}
/* I/O error, forward HUP to match-handlers */
error:
r = rtsp_hup(bus);
out:
rtsp_unref(bus);
return r;
}
static int rtsp_io_prepare_fn(sd_event_source *src, void *data)
{
struct rtsp *bus = data;
uint32_t mask;
int r;
mask = EPOLLHUP | EPOLLERR | EPOLLIN;
if (!shl_dlist_empty(&bus->outgoing))
mask |= EPOLLOUT;
r = sd_event_source_set_io_events(bus->fd_source, mask);
if (r < 0)
return r;
return 0;
}
int rtsp_open(struct rtsp **out, int fd)
{
_rtsp_unref_ struct rtsp *bus = NULL;
if (!out || fd < 0)
return -EINVAL;
bus = calloc(1, sizeof(*bus));
if (!bus)
return -ENOMEM;
bus->ref = 1;
bus->fd = fd;
shl_dlist_init(&bus->matches);
shl_dlist_init(&bus->outgoing);
shl_htable_init_u64(&bus->waiting);
*out = bus;
bus = NULL;
return 0;
}
void rtsp_ref(struct rtsp *bus)
{
if (!bus || !bus->ref)
return;
++bus->ref;
}
void rtsp_unref(struct rtsp *bus)
{
struct rtsp_message *m;
struct rtsp_match *match;
struct shl_dlist *i;
size_t refs;
bool q;
if (!bus || !bus->ref)
return;
/* If the reference count is equal to the number of messages we have
* in our internal queues plus the reference we're about to drop, then
* all remaining references are self-references. Therefore, going over
* all messages and in case they also have no external references, drop
* all queues so bus->ref drops to 1 and we can free it. */
refs = bus->outgoing_cnt + bus->waiting_cnt + 1;
if (bus->parser.m)
++refs;
if (bus->ref <= refs) {
q = true;
shl_dlist_for_each(i, &bus->outgoing) {
m = shl_dlist_entry(i, struct rtsp_message, list);
if (m->ref > 1) {
q = false;
break;
}
}
if (q) {
RTSP_FOREACH_WAITING(m, bus) {
if (m->ref > 1) {
q = false;
break;
}
}
}
if (q) {
while (!shl_dlist_empty(&bus->outgoing)) {
m = shl_dlist_first_entry(&bus->outgoing,
struct rtsp_message,
list);
rtsp_unlink_outgoing(m);
}
while ((m = RTSP_FIRST_WAITING(bus)))
rtsp_unlink_waiting(m);
rtsp_message_unref(bus->parser.m);
bus->parser.m = NULL;
}
}
if (!bus->ref || --bus->ref)
return;
while (!shl_dlist_empty(&bus->matches)) {
match = shl_dlist_first_entry(&bus->matches,
struct rtsp_match,
list);
rtsp_free_match(match);
}
rtsp_detach_event(bus);
shl_ring_clear(&bus->parser.buf);
shl_htable_clear_u64(&bus->waiting, NULL, NULL);
close(bus->fd);
free(bus);
}
bool rtsp_is_dead(struct rtsp *bus)
{
return !bus || bus->is_dead;
}
int rtsp_attach_event(struct rtsp *bus, sd_event *event, int priority)
{
struct rtsp_message *m;
int r;
if (!bus)
return -EINVAL;
if (bus->is_dead)
return -EINVAL;
if (bus->event)
return -EALREADY;
if (event) {
bus->event = event;
sd_event_ref(event);
} else {
r = sd_event_default(&bus->event);
if (r < 0)
return r;
}
bus->priority = priority;
r = sd_event_add_io(bus->event,
&bus->fd_source,
bus->fd,
EPOLLHUP | EPOLLERR | EPOLLIN,
rtsp_io_fn,
bus);
if (r < 0)
goto error;
r = sd_event_source_set_priority(bus->fd_source, priority);
if (r < 0)
goto error;
r = sd_event_source_set_prepare(bus->fd_source, rtsp_io_prepare_fn);
if (r < 0)
goto error;
RTSP_FOREACH_WAITING(m, bus) {
/* no need to wait for timeout if no-body listens */
if (!m->cb_fn)
continue;
r = sd_event_add_time(bus->event,
&m->timer_source,
CLOCK_MONOTONIC,
m->timeout,
0,
rtsp_timer_fn,
m);
if (r < 0)
goto error;
r = sd_event_source_set_priority(m->timer_source, priority);
if (r < 0)
goto error;
}
return 0;
error:
rtsp_detach_event(bus);
return r;
}
void rtsp_detach_event(struct rtsp *bus)
{
struct rtsp_message *m;
if (!bus || !bus->event)
return;
RTSP_FOREACH_WAITING(m, bus) {
sd_event_source_unref(m->timer_source);
m->timer_source = NULL;
}
sd_event_source_unref(bus->fd_source);
bus->fd_source = NULL;
sd_event_unref(bus->event);
bus->event = NULL;
}
/**
* rtsp_add_match() - Add match-callback
* @bus: rtsp bus to register callback on
* @cb_fn: function to be used as callback
* @data: user-context data that is passed through unchanged
*
* The given callback is called for each incoming request that was not matched
* automatically to scheduled transactions. Note that you can register many
* callbacks and they're called in the order they're registered. If a callback
* handled a message, no further callbacks are called.
*
* You can register multiple callbacks with the _same_ @cb_fn and @data just
* fine. However, once you unregister them, they're always unregistered in the
* reverse order you registered them in.
*
* All match-callbacks are automatically removed when @bus is destroyed.
*
* Returns:
* True on success, negative error code on failure.
*/
int rtsp_add_match(struct rtsp *bus, rtsp_callback_fn cb_fn, void *data)
{
struct rtsp_match *match;
if (!bus || !cb_fn)
return -EINVAL;
match = calloc(1, sizeof(*match));
if (!match)
return -ENOMEM;
match->cb_fn = cb_fn;
match->data = data;
shl_dlist_link_tail(&bus->matches, &match->list);
return 0;
}
/**
* rtsp_remove_match() - Remove match-callback
* @bus: rtsp bus to unregister callback from
* @cb_fn: callback function to unregister
* @data: user-context data used during registration
*
* This reverts a previous call to rtsp_add_match(). If a given callback is not
* found, nothing is done. Note that if you register a callback with the same
* cb_fn+data combination multiple times, this only removes the last of them.
*
* All match-callbacks are automatically removed when @bus is destroyed.
*/
void rtsp_remove_match(struct rtsp *bus, rtsp_callback_fn cb_fn, void *data)
{
struct rtsp_match *match;
struct shl_dlist *i;
if (!bus || !cb_fn)
return;
shl_dlist_for_each_reverse(i, &bus->matches) {
match = shl_dlist_entry(i, struct rtsp_match, list);
if (match->cb_fn == cb_fn && match->data == data) {
if (bus->is_calling)
match->is_removed = true;
else
rtsp_free_match(match);
break;
}
}
}
static void rtsp_free_match(struct rtsp_match *match)
{
if (!match)
return;
shl_dlist_unlink(&match->list);
free(match);
}
int rtsp_send(struct rtsp *bus, struct rtsp_message *m)
{
return rtsp_call_async(bus, m, NULL, NULL, 0, NULL);
}
int rtsp_call_async(struct rtsp *bus,
struct rtsp_message *m,
rtsp_callback_fn cb_fn,
void *data,
uint64_t timeout,
uint64_t *cookie)
{
int r;
if (!bus || bus->is_dead || !m || !m->cookie)
return -EINVAL;
if (m->bus != bus || m->is_outgoing || m->is_waiting || m->is_used)
return -EINVAL;
r = rtsp_message_seal(m);
if (r < 0)
return r;
if (!m->raw)
return -EINVAL;
m->is_used = true;
m->cb_fn = cb_fn;
m->fn_data = data;
m->timeout = timeout ? : RTSP_DEFAULT_TIMEOUT;
m->timeout += shl_now(CLOCK_MONOTONIC);
/* verify cookie and generate one if none is set */
if (m->cookie & RTSP_FLAG_REMOTE_COOKIE) {
if (m->type != RTSP_MESSAGE_UNKNOWN &&
m->type != RTSP_MESSAGE_REPLY)
return -EINVAL;
} else {
if (m->type == RTSP_MESSAGE_REPLY)
return -EINVAL;
}
/* needs m->cookie set correctly */
r = rtsp_link_waiting(m);
if (r < 0)
return r;
rtsp_link_outgoing(m);
if (cookie)
*cookie = m->cookie;
return 0;
}
static void rtsp_drop_message(struct rtsp_message *m)
{
if (!m)
return;
/* never interrupt messages while being partly sent */
if (!m->is_sending)
rtsp_unlink_outgoing(m);
/* remove from waiting list so neither timeouts nor completions fire */
rtsp_unlink_waiting(m);
}
void rtsp_call_async_cancel(struct rtsp *bus, uint64_t cookie)
{
struct rtsp_message *m;
uint64_t *elem;
if (!bus || !cookie)
return;
if (!shl_htable_lookup_u64(&bus->waiting, cookie, &elem))
return;
m = rtsp_message_from_htable(elem);
rtsp_drop_message(m);
}