summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore27
-rw-r--r--COPYING37
-rw-r--r--Makefile.am150
-rw-r--r--NEWS4
-rw-r--r--README40
-rwxr-xr-xautogen.sh17
-rw-r--r--configure.ac75
-rw-r--r--docs/libwfd.pc.in11
-rw-r--r--docs/libwfd.sym22
-rw-r--r--src/libwfd.h816
-rw-r--r--src/rtsp_decoder.c1270
-rw-r--r--src/rtsp_tokenizer.c191
-rw-r--r--src/shl_llog.h247
-rw-r--r--src/shl_macro.h219
-rw-r--r--src/shl_ring.c188
-rw-r--r--src/shl_ring.h52
-rw-r--r--src/shl_util.c260
-rw-r--r--src/shl_util.h83
-rw-r--r--test/test_common.h109
-rw-r--r--test/test_rtsp.c558
20 files changed, 4376 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..53d0475
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+*.la
+*.lo
+*.log
+*.o
+*.swp
+*.tar.xz
+*.trs
+.deps/
+.dirstamp
+.libs/
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache/
+build-aux/
+config.h
+config.h.in
+config.h.in~
+config.log
+config.status
+configure
+docs/libwfd.pc
+libtool
+m4/
+stamp-h1
+test-suite.log
+test_rtsp
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..00a0ce5
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,37 @@
+= Authors =
+
+This software was written by:
+ David Herrmann <dh.herrmann@gmail.com>
+
+= Copyright Notice =
+
+This software is licensed under the terms of the MIT license. Please see each
+source file for the related copyright notice and license.
+
+If a file does not contain a copright notice, the following license shall
+apply:
+
+ Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+== Third-Party Source ==
+
+ - none so far -
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..f2a1b82
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,150 @@
+#
+# libwfd - Global Makefile
+# Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
+#
+
+#
+# Library Version Numbers
+#
+
+LIBWFD_CURRENT = 0
+LIBWFD_REVISION = 0
+LIBWFD_AGE = 0
+
+#
+# Global Configurations and Initializations
+#
+
+ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
+AM_MAKEFLAGS = --no-print-directory
+AUTOMAKE_OPTIONS = color-tests
+
+SUBDIRS = .
+
+.DELETE_ON_ERROR:
+
+include_HEADERS =
+EXTRA_DIST = \
+ README \
+ COPYING \
+ NEWS \
+ docs/libwfd.pc.in \
+ docs/libwfd.sym
+CLEANFILES =
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA =
+TPHONY =
+
+TESTS =
+MEMTESTS =
+check_PROGRAMS =
+lib_LTLIBRARIES =
+noinst_LTLIBRARIES =
+
+#
+# Default CFlags
+# Make all files include "config.h" by default. This shouldn't cause any
+# problems and we cannot forget to include it anymore.
+# Also make the linker discard all unused symbols.
+#
+
+AM_CFLAGS = \
+ -Wall \
+ -pipe \
+ -fno-common \
+ -ffast-math \
+ -fdiagnostics-show-option \
+ -fno-strict-aliasing \
+ -fvisibility=hidden \
+ -ffunction-sections \
+ -fdata-sections
+AM_CPPFLAGS = \
+ -include $(top_builddir)/config.h \
+ -I $(srcdir)/src
+AM_LDFLAGS = \
+ -Wl,--as-needed \
+ -Wl,--gc-sections \
+ -Wl,-z,relro \
+ -Wl,-z,now
+
+#
+# SHL - Static Helper Library
+# The SHL subsystem contains several small code pieces used all over libwfd and
+# other applications.
+#
+
+noinst_LTLIBRARIES += libshl.la
+
+libshl_la_SOURCES = \
+ src/shl_llog.h \
+ src/shl_macro.h \
+ src/shl_ring.h \
+ src/shl_ring.c \
+ src/shl_util.h \
+ src/shl_util.c
+libshl_la_CPPFLAGS = $(AM_CPPFLAGS)
+libshl_la_LDFLAGS = $(AM_LDFLAGS)
+libshl_la_LIBADD = $(AM_LIBADD)
+
+#
+# libwfd
+# Main library build instructions
+#
+
+lib_LTLIBRARIES += libwfd.la
+include_HEADERS += src/libwfd.h
+pkgconfig_DATA += docs/libwfd.pc
+
+libwfd_la_SOURCES = \
+ src/libwfd.h \
+ src/rtsp_decoder.c \
+ src/rtsp_tokenizer.c
+libwfd_la_CPPFLAGS = $(AM_CPPFLAGS)
+libwfd_la_LIBADD = libshl.la
+EXTRA_libwfd_la_DEPENDENCIES = $(top_srcdir)/docs/libwfd.sym
+libwfd_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -version-info $(LIBWFD_CURRENT):$(LIBWFD_REVISION):$(LIBWFD_AGE) \
+ -Wl,--version-script="$(top_srcdir)/docs/libwfd.sym"
+
+#
+# Tests
+#
+
+tests = \
+ test_rtsp
+
+if BUILD_HAVE_CHECK
+check_PROGRAMS += $(tests)
+TESTS += $(tests)
+endif
+
+test_sources = \
+ test/test_common.h
+test_libs = \
+ libwfd.la \
+ libshl.la \
+ $(CHECK_LIBS)
+test_cflags = \
+ $(AM_CPPFLAGS) \
+ $(CHECK_CFLAGS)
+test_lflags = \
+ $(AM_LDFLAGS)
+
+test_rtsp_SOURCES = test/test_rtsp.c $(test_sources)
+test_rtsp_CPPFLAGS = $(test_cflags)
+test_rtsp_LDADD = $(test_libs)
+test_rtsp_LDFLAGS = $(test_lflags)
+
+#
+# Phony targets
+#
+
+.PHONY: $(TPHONY)
+
+#
+# Empty .SECONDARY target causes alle intermediate files to be treated as
+# secondary files. That is, they don't get deleted after make finished.
+#
+
+.SECONDARY:
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..a34083d
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,4 @@
+= libwfd Release News =
+
+CHANGES WITH 1:
+ * TODO
diff --git a/README b/README
new file mode 100644
index 0000000..4c184a2
--- /dev/null
+++ b/README
@@ -0,0 +1,40 @@
+= libwfd - Wifi-Display/Miracast Protocol Implementation =
+
+TODO
+
+Website:
+ http://www.freedesktop.org/wiki/Software/miraclecast
+
+== Requirements ==
+
+ libwfd has no runtime requirements other than a ISO-C compatible C library.
+
+== Download ==
+
+Released tarballs can be found at:
+ http://www.freedesktop.org/software/miraclecast/releases
+
+== Install ==
+
+ To compile libwfd, run the standard autotools commands:
+ $ test -f ./configure || NOCONFIGURE=1 ./autogen.sh
+ $ ./configure --prefix=/usr
+ $ make
+ $ sudo make install
+ To compile and run the test applications, use:
+ $ make check
+
+== Documentation ==
+
+WIP
+
+== License ==
+
+ This software is licensed under the terms of an MIT-like license. Please see
+ ./COPYING for further information.
+
+== Contact ==
+
+ This software is maintained by:
+ David Herrmann <dh.herrmann@gmail.com>
+ If you have any questions, do not hesitate to contact one of the maintainers.
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..65a6fe1
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+set -e
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+origdir=`pwd`
+cd $srcdir
+
+mkdir -p m4
+autoreconf -is --force
+
+cd $origdir
+
+if test -z "$NOCONFIGURE" ; then
+ exec $srcdir/configure "$@"
+fi
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..b92d6d7
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,75 @@
+#
+# libwfd - build configuration script
+# Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
+#
+
+AC_PREREQ(2.68)
+
+AC_INIT([libwfd],
+ [3],
+ [http://www.freedesktop.org/wiki/Software/miraclecast],
+ [libwfd],
+ [http://www.freedesktop.org/wiki/Software/miraclecast])
+AC_CONFIG_SRCDIR([src/libwfd.h])
+AC_CONFIG_AUX_DIR([build-aux])
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_HEADER(config.h)
+AC_USE_SYSTEM_EXTENSIONS
+AC_SYS_LARGEFILE
+AC_CANONICAL_HOST
+
+AM_INIT_AUTOMAKE([foreign 1.11 subdir-objects dist-xz no-dist-gzip tar-pax -Wall -Werror -Wno-portability])
+AM_SILENT_RULES([yes])
+
+AC_SUBST(PACKAGE_DESCRIPTION, ["Wifi-Display/Miracast Protocol Implementation"])
+
+AC_PROG_CC
+AC_PROG_CC_C99
+AM_PROG_CC_C_O
+m4_ifdef([AM_PROG_AR], [AM_PROG_AR])
+AC_PROG_SED
+AC_PROG_MKDIR_P
+AC_PROG_LN_S
+AC_PROG_GREP
+AC_PROG_AWK
+
+LT_PREREQ(2.2)
+LT_INIT
+
+#
+# Test for "check" which we use for our test-suite. If not found, we disable
+# all tests.
+#
+
+PKG_CHECK_MODULES([CHECK], [check],
+ [have_check=yes], [have_check=no])
+AC_SUBST(CHECK_CFLAGS)
+AC_SUBST(CHECK_LIBS)
+AM_CONDITIONAL([BUILD_HAVE_CHECK], [test "x$have_check" = "xyes"])
+
+#
+# Makefile vars
+# After everything is configured, we create all makefiles.
+#
+
+AC_CONFIG_FILES([Makefile
+ docs/libwfd.pc])
+AC_OUTPUT
+
+#
+# Configuration output
+# Show configuration to the user so they can check whether everything was
+# configured as expected.
+#
+
+AC_MSG_NOTICE([Build configuration:
+
+ prefix: $prefix
+ exec-prefix: $exec_prefix
+ libdir: $libdir
+ includedir: $includedir
+
+ Miscellaneous Options:
+ building tests: $have_check
+
+ Run "${MAKE-make}" to start compilation process])
diff --git a/docs/libwfd.pc.in b/docs/libwfd.pc.in
new file mode 100644
index 0000000..a3447b9
--- /dev/null
+++ b/docs/libwfd.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libwfd
+Description: @PACKAGE_DESCRIPTION@
+URL: @PACKAGE_URL@
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lwfd
+Cflags: -I${includedir}
diff --git a/docs/libwfd.sym b/docs/libwfd.sym
new file mode 100644
index 0000000..a29cda1
--- /dev/null
+++ b/docs/libwfd.sym
@@ -0,0 +1,22 @@
+LIBWFD_1 {
+global:
+ wfd_rtsp_tokenize;
+
+ wfd_rtsp_method_get_name;
+ wfd_rtsp_method_from_name;
+ wfd_rtsp_status_is_valid;
+ wfd_rtsp_status_get_base;
+ wfd_rtsp_status_get_description;
+ wfd_rtsp_header_get_name;
+ wfd_rtsp_header_from_name;
+ wfd_rtsp_header_from_name_n;
+
+ wfd_rtsp_decoder_new;
+ wfd_rtsp_decoder_free;
+ wfd_rtsp_decoder_reset;
+ wfd_rtsp_decoder_set_data;
+ wfd_rtsp_decoder_get_data;
+ wfd_rtsp_decoder_feed;
+local:
+ *;
+};
diff --git a/src/libwfd.h b/src/libwfd.h
new file mode 100644
index 0000000..a43dd66
--- /dev/null
+++ b/src/libwfd.h
@@ -0,0 +1,816 @@
+/*
+ * libwfd - Wifi-Display/Miracast Protocol Implementation
+ *
+ * Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef WFD_LIBWFD_H
+#define WFD_LIBWFD_H
+
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define WFD__PACKED __attribute__((__packed__))
+
+/**
+ * @defgroup wfd_p2p Wifi-Display Definitions for wifi-p2p
+ * Definitions from the Wifi-Display specification for Wifi-P2P
+ *
+ * This section contains definitions and constants from the Wifi-Display
+ * specification regarding Wifi-P2P.
+ *
+ * @{
+ */
+
+/*
+ * TODO: WFD 5.2.7 defines service-discovery frames. wpa-supplicant currently
+ * does not support custom OUI fields. Fix this and then add support for
+ * WFD service discovery.
+ */
+
+/*
+ * IE elements
+ */
+
+#define WFD_IE_ID 0xdd
+#define WFD_IE_OUI_1_0 0x506f9a0a
+#define WFD_IE_DATA_MAX 251
+
+struct wfd_ie {
+ uint8_t element_id;
+ uint8_t length;
+ uint32_t oui;
+ uint8_t data[];
+} WFD__PACKED;
+
+/*
+ * IE subelements
+ */
+
+enum wfd_ie_sub_type {
+ WFD_IE_SUB_DEV_INFO = 0,
+ WFD_IE_SUB_ASSOC_BSSID = 1,
+ WFD_IE_SUB_AUDIO_FORMATS = 2,
+ WFD_IE_SUB_VIDEO_FORMATS = 3,
+ WFD_IE_SUB_3D_FORMATS = 4,
+ WFD_IE_SUB_CONTENT_PROTECT = 5,
+ WFD_IE_SUB_COUPLED_SINK = 6,
+ WFD_IE_SUB_EXT_CAP = 7,
+ WFD_IE_SUB_LOCAL_IP = 8,
+ WFD_IE_SUB_SESSION_INFO = 9,
+ WFD_IE_SUB_ALT_MAC = 10,
+ WFD_IE_SUB_NUM
+};
+
+struct wfd_ie_sub {
+ uint8_t subelement_id;
+ uint16_t length;
+ uint8_t data[];
+} WFD__PACKED;
+
+/*
+ * IE subelement device information
+ */
+
+/* role */
+#define WFD_IE_SUB_DEV_INFO_ROLE_MASK 0x0003
+#define WFD_IE_SUB_DEV_INFO_SOURCE 0x0000
+#define WFD_IE_SUB_DEV_INFO_PRIMARY_SINK 0x0001
+#define WFD_IE_SUB_DEV_INFO_SECONDARY_SINK 0x0002
+#define WFD_IE_SUB_DEV_INFO_DUAL_ROLE 0x0003
+
+/* coupled sink as source */
+#define WFD_IE_SUB_DEV_INFO_SRC_COUPLED_SINK_MASK 0x0004
+#define WFD_IE_SUB_DEV_INFO_SRC_NO_COUPLED_SINK 0x0000
+#define WFD_IE_SUB_DEV_INFO_SRC_CAN_COUPLED_SINK 0x0004
+
+/* coupled sink as sink */
+#define WFD_IE_SUB_DEV_INFO_SINK_COUPLED_SINK_MASK 0x0008
+#define WFD_IE_SUB_DEV_INFO_SINK_NO_COUPLED_SINK 0x0000
+#define WFD_IE_SUB_DEV_INFO_SINK_CAN_COUPLED_SINK 0x0008
+
+/* availability for session establishment */
+#define WFD_IE_SUB_DEV_INFO_AVAILABLE_MASK 0x0030
+#define WFD_IE_SUB_DEV_INFO_NOT_AVAILABLE 0x0000
+#define WFD_IE_SUB_DEV_INFO_AVAILABLE 0x0010
+
+/* WFD service discovery */
+#define WFD_IE_SUB_DEV_INFO_WSD_MASK 0x0040
+#define WFD_IE_SUB_DEV_INFO_NO_WSD 0x0000
+#define WFD_IE_SUB_DEV_INFO_CAN_WSD 0x0040
+
+/* preferred connectivity */
+#define WFD_IE_SUB_DEV_INFO_PC_MASK 0x0080
+#define WFD_IE_SUB_DEV_INFO_PREFER_P2P 0x0000
+#define WFD_IE_SUB_DEV_INFO_PREFER_TDLS 0x0080
+
+/* content protection */
+#define WFD_IE_SUB_DEV_INFO_CP_MASK 0x0100
+#define WFD_IE_SUB_DEV_INFO_NO_CP 0x0000
+#define WFD_IE_SUB_DEV_INFO_CAN_CP 0x0100
+
+/* separate time-sync */
+#define WFD_IE_SUB_DEV_INFO_TIME_SYNC_MASK 0x0200
+#define WFD_IE_SUB_DEV_INFO_NO_TIME_SYNC 0x0000
+#define WFD_IE_SUB_DEV_INFO_CAN_TIME_SYNC 0x0200
+
+/* no audio */
+#define WFD_IE_SUB_DEV_INFO_NO_AUDIO_MASK 0x0400
+#define WFD_IE_SUB_DEV_INFO_CAN_AUDIO 0x0000
+#define WFD_IE_SUB_DEV_INFO_NO_AUDIO 0x0400
+
+/* audio only */
+#define WFD_IE_SUB_DEV_INFO_AUDIO_ONLY_MASK 0x0800
+#define WFD_IE_SUB_DEV_INFO_NO_AUDIO_ONLY 0x0000
+#define WFD_IE_SUB_DEV_INFO_AUDIO_ONLY 0x0800
+
+/* persistent TLDS */
+#define WFD_IE_SUB_DEV_INFO_PERSIST_TLDS_MASK 0x1000
+#define WFD_IE_SUB_DEV_INFO_NO_PERSIST_TLDS 0x0000
+#define WFD_IE_SUB_DEV_INFO_PERSIST_TLDS 0x1000
+
+/* persistent TLDS group re-invoke */
+#define WFD_IE_SUB_DEV_INFO_TLDS_REINVOKE_MASK 0x2000
+#define WFD_IE_SUB_DEV_INFO_NO_TLDS_REINVOKE 0x0000
+#define WFD_IE_SUB_DEV_INFO_TLDS_REINVOKE 0x2000
+
+#define WFD_IE_SUB_DEV_INFO_DEFAULT_PORT 7236
+
+struct wfd_ie_sub_dev_info {
+ uint16_t dev_info;
+ uint16_t ctrl_port;
+ uint16_t max_throughput;
+} WFD__PACKED;
+
+/*
+ * IE subelement associated BSSID
+ */
+
+struct wfd_ie_sub_assoc_bssid {
+ uint8_t bssid[6];
+} WFD__PACKED;
+
+/*
+ * IE subelement audio formats
+ */
+
+/* lpcm modes; 2C_16_48000 is mandatory */
+#define WFD_IE_SUB_AUDIO_FORMATS_LPCM_2C_16_44100 0x00000001
+#define WFD_IE_SUB_AUDIO_FORMATS_LPCM_2C_16_48000 0x00000002
+
+/* aac modes */
+#define WFD_IE_SUB_AUDIO_FORMATS_AAC_2C_16_48000 0x00000001
+#define WFD_IE_SUB_AUDIO_FORMATS_AAC_4C_16_48000 0x00000002
+#define WFD_IE_SUB_AUDIO_FORMATS_AAC_6C_16_48000 0x00000004
+#define WFD_IE_SUB_AUDIO_FORMATS_AAC_8C_16_48000 0x00000008
+
+/* ac3 modes */
+#define WFD_IE_SUB_AUDIO_FORMATS_AC3_2C_16_48000 0x00000001
+#define WFD_IE_SUB_AUDIO_FORMATS_AC3_4C_16_48000 0x00000002
+#define WFD_IE_SUB_AUDIO_FORMATS_AC3_6C_16_48000 0x00000004
+
+/* audio latency; encoded in multiples of 5ms */
+#define WFD_IE_SUB_AUDIO_FORMATS_UNKNOWN_LATENCY 0x00
+#define WFD_IE_SUB_AUDIO_FORMATS_LATENCY_FROM_MS(_ms) \
+ (((_ms) + 4ULL) / 5ULL)
+
+struct wfd_ie_sub_audio_formats {
+ uint32_t lpcm_modes;
+ uint8_t lpcm_latency;
+ uint32_t aac_modes;
+ uint8_t aac_latency;
+ uint32_t ac3_modes;
+ uint8_t ac3_latency;
+} WFD__PACKED;
+
+/*
+ * IE subelement video formats
+ * Multiple video-subelements are allowed, one for each supported 264 profile.
+ */
+
+/* cea modes; required cea modes; 640x480@p60 is always required; if you
+ * support higher resolutions at p60 or p50, you also must support 720x480@p60
+ * or 720x576@p50 respectively */
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_640_480_P60 0x00000001
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_720_480_P60 0x00000002
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_720_480_I60 0x00000004
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_720_576_P50 0x00000008
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_720_576_I50 0x00000010
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_1280_720_P30 0x00000020
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_1280_720_P60 0x00000040
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_1920_1080_P30 0x00000080
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_1920_1080_P60 0x00000100
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_1920_1080_I60 0x00000200
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_1280_720_P25 0x00000400
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_1280_720_P50 0x00000800
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_1920_1080_P25 0x00001000
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_1920_1080_P50 0x00002000
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_1920_1080_I50 0x00004000
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_1280_720_P24 0x00008000
+#define WFD_IE_SUB_VIDEO_FORMATS_CEA_1920_1080_P24 0x00010000
+
+/* vesa modes; if you support higher refresh-rates, you must also support
+ * *all* lower rates of the same mode */
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_800_600_P30 0x00000001
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_800_600_P60 0x00000002
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1024_768_P30 0x00000004
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1024_768_P60 0x00000008
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1152_864_P30 0x00000010
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1152_864_P60 0x00000020
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1280_768_P30 0x00000040
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1280_768_P60 0x00000080
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1280_800_P30 0x00000100
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1280_800_P60 0x00000200
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1360_768_P30 0x00000400
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1360_768_P60 0x00000800
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1366_768_P30 0x00001000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1366_768_P60 0x00002000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1280_1024_P30 0x00004000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1280_1024_P60 0x00008000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1400_1050_P30 0x00010000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1400_1050_P60 0x00020000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1440_900_P30 0x00040000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1440_900_P60 0x00080000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1600_900_P30 0x00100000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1600_900_P60 0x00200000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1600_1200_P30 0x00400000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1600_1200_P60 0x00800000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1680_1024_P30 0x01000000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1680_1024_P60 0x02000000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1680_1050_P30 0x04000000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1680_1050_P60 0x08000000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1920_1200_P30 0x10000000
+#define WFD_IE_SUB_VIDEO_FORMATS_VESA_1920_1200_P60 0x20000000
+
+/* hh modes (handheld devices) */
+#define WFD_IE_SUB_VIDEO_FORMATS_HH_800_480_P30 0x00000000
+#define WFD_IE_SUB_VIDEO_FORMATS_HH_800_480_P60 0x00000000
+#define WFD_IE_SUB_VIDEO_FORMATS_HH_854_480_P30 0x00000000
+#define WFD_IE_SUB_VIDEO_FORMATS_HH_854_480_P60 0x00000000
+#define WFD_IE_SUB_VIDEO_FORMATS_HH_864_480_P30 0x00000000
+#define WFD_IE_SUB_VIDEO_FORMATS_HH_864_480_P60 0x00000000
+#define WFD_IE_SUB_VIDEO_FORMATS_HH_640_360_P30 0x00000000
+#define WFD_IE_SUB_VIDEO_FORMATS_HH_640_360_P60 0x00000000
+#define WFD_IE_SUB_VIDEO_FORMATS_HH_960_540_P30 0x00000000
+#define WFD_IE_SUB_VIDEO_FORMATS_HH_960_540_P60 0x00000000
+#define WFD_IE_SUB_VIDEO_FORMATS_HH_848_480_P30 0x00000000
+#define WFD_IE_SUB_VIDEO_FORMATS_HH_848_480_P60 0x00000000
+
+/* native mode; table */
+#define WFD_IE_SUB_VIDEO_FORMATS_NATIVE_MODE_TABLE_MASK 0x03
+#define WFD_IE_SUB_VIDEO_FORMATS_NATIVE_MODE_CEA_TABLE 0x00
+#define WFD_IE_SUB_VIDEO_FORMATS_NATIVE_MODE_VESA_TABLE 0x01
+#define WFD_IE_SUB_VIDEO_FORMATS_NATIVE_MODE_HH_TABLE 0x02
+
+/* native mode; index */
+#define WFD_IE_SUB_VIDEO_FORMATS_NATIVE_MODE_IDX_MASK 0xfc
+#define WFD_IE_SUB_VIDEO_FORMATS_NATIVE_MODE_IDX_SHIFT 3
+
+/* h264 profiles; base-profile / high-profile; mostly only one bit allowed */
+#define WFD_IE_SUB_VIDEO_FORMATS_PROFILE_CBP 0x01
+#define WFD_IE_SUB_VIDEO_FORMATS_PROFILE_CHP 0x02
+
+/* max h264 level; mostly only one bit allowed */
+#define WFD_IE_SUB_VIDEO_FORMATS_H264_LEVEL_3_1 0x01
+#define WFD_IE_SUB_VIDEO_FORMATS_H264_LEVEL_3_2 0x02
+#define WFD_IE_SUB_VIDEO_FORMATS_H264_LEVEL_4_0 0x04
+#define WFD_IE_SUB_VIDEO_FORMATS_H264_LEVEL_4_1 0x08
+#define WFD_IE_SUB_VIDEO_FORMATS_H264_LEVEL_4_2 0x10
+
+/* display latency; encoded in multiples of 5ms */
+#define WFD_IE_SUB_VIDEO_FORMATS_UNKNOWN_LATENCY 0x00
+#define WFD_IE_SUB_VIDEO_FORMATS_LATENCY_FROM_MS(_ms) \
+ (((_ms) + 4ULL) / 5ULL)
+
+/* smallest slice size expressed in number of macro-blocks or 0x0 */
+#define WFD_IE_SUB_VIDEO_FORMATS_NO_SLICES 0x0000
+
+/* if no slices allowed, this can be set on slice_env */
+#define WFD_IE_SUB_VIDEO_FORMATS_NO_SLICE_ENC 0x0000
+
+/* max number of slices per picture MINUS 1 (0 not allowed) */
+#define WFD_IE_SUB_VIDEO_FORMATS_SLICE_ENC_MAX_MASK 0x03ff
+#define WFD_IE_SUB_VIDEO_FORMATS_SLICE_ENC_MAX_SHIFT 0
+
+/* ratio of max-slice-size to be used and slice_min field (0 not allowed) */
+#define WFD_IE_SUB_VIDEO_FORMATS_SLICE_ENC_RATIO_MASK 0x0c00
+#define WFD_IE_SUB_VIDEO_FORMATS_SLICE_ENC_RATIO_SHIFT 10
+
+/* frame skipping */
+#define WFD_IE_SUB_VIDEO_FORMATS_NO_FRAME_SKIP 0x00
+#define WFD_IE_SUB_VIDEO_FORMATS_CAN_FRAME_SKIP 0x01
+
+#define WFD_IE_SUB_VIDEO_FORMATS_FRAME_SKIP_MAX_I_MASK 0x0e
+#define WFD_IE_SUB_VIDEO_FORMATS_FRAME_SKIP_MAX_I_SHIFT 1
+#define WFD_IE_SUB_VIDEO_FORMATS_FRAME_SKIP_MAX_I_ANY 0x00
+
+#define WFD_IE_SUB_VIDEO_FORMATS_FRAME_SKIP_NO_DYN 0x00
+#define WFD_IE_SUB_VIDEO_FORMATS_FRAME_SKIP_CAN_DYN 0x10
+
+struct wfd_ie_sub_video_formats {
+ uint32_t cea_modes;
+ uint32_t vesa_modes;
+ uint32_t hh_modes;
+ uint8_t native_mode;
+ uint8_t h264_profile;
+ uint8_t h264_max_level;
+ uint8_t latency;
+ uint16_t slice_min;
+ uint16_t slice_enc;
+ uint8_t frame_skip;
+} WFD__PACKED;
+
+/*
+ * IE subelement 3d formats
+ * Multiple 3d-subelements are allowed, one for each supported h264 profile.
+ */
+
+/* 3d cpabilities; required modes; 1920x540/540@p24 is always required; if you
+ * support higher resolutions at p60 or p50, you also must support
+ * 1280x360/360@p60 or 1280x360/360@p50 respectively */
+#define WFD_IE_SUB_3D_FORMATS_CAP_1920_X_540_540_P24 0x0000000000000001
+#define WFD_IE_SUB_3D_FORMATS_CAP_1280_X_360_360_P60 0x0000000000000002
+#define WFD_IE_SUB_3D_FORMATS_CAP_1280_X_360_360_P50 0x0000000000000004
+#define WFD_IE_SUB_3D_FORMATS_CAP_1920_X_1080_P24_P24 0x0000000000000008
+#define WFD_IE_SUB_3D_FORMATS_CAP_1280_X_720_P60_P60 0x0000000000000010
+#define WFD_IE_SUB_3D_FORMATS_CAP_1280_X_720_P30_P30 0x0000000000000020
+#define WFD_IE_SUB_3D_FORMATS_CAP_1280_X_720_P50_P50 0x0000000000000040
+#define WFD_IE_SUB_3D_FORMATS_CAP_1280_X_720_P25_P25 0x0000000000000080
+#define WFD_IE_SUB_3D_FORMATS_CAP_1920_X_1080_45_1080_P24 0x0000000000000100
+#define WFD_IE_SUB_3D_FORMATS_CAP_1280_X_720_30_720_P60 0x0000000000000200
+#define WFD_IE_SUB_3D_FORMATS_CAP_1280_X_720_30_720_P30 0x0000000000000400
+#define WFD_IE_SUB_3D_FORMATS_CAP_1280_X_720_30_720_P50 0x0000000000000800
+#define WFD_IE_SUB_3D_FORMATS_CAP_1280_X_720_30_720_P25 0x0000000000001000
+#define WFD_IE_SUB_3D_FORMATS_CAP_960_960_X_1080_I60 0x0000000000002000
+#define WFD_IE_SUB_3D_FORMATS_CAP_960_960_X_1080_I50 0x0000000000004000
+#define WFD_IE_SUB_3D_FORMATS_CAP_640_X_240_240_P60 0x0000000000008000
+#define WFD_IE_SUB_3D_FORMATS_CAP_320_320_X_480_P60 0x0000000000010000
+#define WFD_IE_SUB_3D_FORMATS_CAP_720_X_240_240_P60 0x0000000000020000
+#define WFD_IE_SUB_3D_FORMATS_CAP_360_360_X_480_P60 0x0000000000040000
+#define WFD_IE_SUB_3D_FORMATS_CAP_720_X_288_288_P50 0x0000000000080000
+#define WFD_IE_SUB_3D_FORMATS_CAP_360_360_X_576_P50 0x0000000000100000
+#define WFD_IE_SUB_3D_FORMATS_CAP_1280_X_360_360_P24 0x0000000000200000
+#define WFD_IE_SUB_3D_FORMATS_CAP_640_640_X_720_P24 0x0000000000400000
+#define WFD_IE_SUB_3D_FORMATS_CAP_1280_X_360_360_P25 0x0000000000800000
+#define WFD_IE_SUB_3D_FORMATS_CAP_640_640_X_720_P25 0x0000000001000000
+#define WFD_IE_SUB_3D_FORMATS_CAP_1280_X_360_360_P30 0x0000000002000000
+#define WFD_IE_SUB_3D_FORMATS_CAP_640_640_X_720_P30 0x0000000004000000
+#define WFD_IE_SUB_3D_FORMATS_CAP_1920_X_540_540_P30 0x0000000008000000
+#define WFD_IE_SUB_3D_FORMATS_CAP_1920_X_540_540_P50 0x0000000010000000
+#define WFD_IE_SUB_3D_FORMATS_CAP_1920_X_540_540_P60 0x0000000020000000
+#define WFD_IE_SUB_3D_FORMATS_CAP_640_640_X_720_P50 0x0000000040000000
+#define WFD_IE_SUB_3D_FORMATS_CAP_640_640_X_720_P60 0x0000000080000000
+#define WFD_IE_SUB_3D_FORMATS_CAP_960_960_X_1080_P24 0x0000000100000000
+#define WFD_IE_SUB_3D_FORMATS_CAP_960_960_X_1080_P50 0x0000000200000000
+#define WFD_IE_SUB_3D_FORMATS_CAP_960_960_X_1080_P60 0x0000000400000000
+#define WFD_IE_SUB_3D_FORMATS_CAP_1920_X_1080_45_1080_P30 0x0000000800000000
+#define WFD_IE_SUB_3D_FORMATS_CAP_1920_X_1080_45_1080_I50 0x0000001000000000
+#define WFD_IE_SUB_3D_FORMATS_CAP_1920_X_1080_45_1080_I60 0x0000002000000000
+
+struct wfd_ie_sub_3d_formats {
+ uint64_t capabilities;
+ uint8_t native_mode; /* same as video_formats.native_mode */
+ uint8_t h264_profile; /* same as video_formats.h264_profile */
+ uint8_t h264_max_level; /* same as video_formats.h264_max_level */
+ uint8_t latency; /* same as video_formats.latency */
+ uint16_t slice_min; /* same as video_formats.slice_min */
+ uint16_t slice_enc; /* same as video_formats.slice_enc */
+ uint8_t frame_skip; /* same as video_formats.frame_skip */
+} WFD__PACKED;
+
+/*
+ * IE subelement content protection
+ */
+
+/* HDCP 2.0 */
+#define WFD_IE_SUB_CONTENT_PROTECT_HDCP_2_0_MASK 0x01
+#define WFD_IE_SUB_CONTENT_PROTECT_NO_HDCP_2_0 0x00
+#define WFD_IE_SUB_CONTENT_PROTECT_CAN_HDCP_2_0 0x01
+
+/* HDCP 2.1; if set, you must also set HDCP 2.0 */
+#define WFD_IE_SUB_CONTENT_PROTECT_HDCP_2_1_MASK 0x02
+#define WFD_IE_SUB_CONTENT_PROTECT_NO_HDCP_2_1 0x00
+#define WFD_IE_SUB_CONTENT_PROTECT_CAN_HDCP_2_1 0x02
+
+struct wfd_ie_sub_content_protect {
+ uint8_t flags;
+} WFD__PACKED;
+
+/*
+ * IE subelement coupled sink information
+ */
+
+/* status */
+#define WFD_IE_SUB_COUPLED_SINK_STATUS_MASK 0x03
+#define WFD_IE_SUB_COUPLED_SINK_NOT_COUPLED 0x00
+#define WFD_IE_SUB_COUPLED_SINK_COUPLED 0x01
+#define WFD_IE_SUB_COUPLED_SINK_COUPLE_TEARDOWN 0x02
+
+struct wfd_ie_sub_coupled_sink {
+ uint8_t status;
+ uint8_t mac[6];
+} WFD__PACKED;
+
+/*
+ * IE subelement extended capabilities
+ */
+
+/* UIBC */
+#define WFD_IE_SUB_EXT_CAP_UIBC_MASK 0x01
+#define WFD_IE_SUB_EXT_CAP_NO_UIBC 0x00
+#define WFD_IE_SUB_EXT_CAP_CAN_UIBC 0x01
+
+/* I2C */
+#define WFD_IE_SUB_EXT_CAP_I2C_MASK 0x02
+#define WFD_IE_SUB_EXT_CAP_NO_I2C 0x00
+#define WFD_IE_SUB_EXT_CAP_CAN_I2C 0x02
+
+/* Preferred Mode */
+#define WFD_IE_SUB_EXT_CAP_PREFER_MODE_MASK 0x04
+#define WFD_IE_SUB_EXT_CAP_NO_PREFER_MODE 0x00
+#define WFD_IE_SUB_EXT_CAP_CAN_PREFER_MODE 0x04
+
+/* Standby */
+#define WFD_IE_SUB_EXT_CAP_STANDBY_MASK 0x08
+#define WFD_IE_SUB_EXT_CAP_NO_STANDBY 0x00
+#define WFD_IE_SUB_EXT_CAP_CAN_STANDBY 0x08
+
+/* Persistend TDLS */
+#define WFD_IE_SUB_EXT_CAP_PERSIST_TDLS_MASK 0x10
+#define WFD_IE_SUB_EXT_CAP_NO_PERSIST_TDLS 0x00
+#define WFD_IE_SUB_EXT_CAP_CAN_PERSIST_TDLS 0x10
+
+/* Persistend TDLS BSSID */
+#define WFD_IE_SUB_EXT_CAP_PERSIST_TDLS_BSSID_MASK 0x20
+#define WFD_IE_SUB_EXT_CAP_NO_PERSIST_TDLS_BSSID 0x00
+#define WFD_IE_SUB_EXT_CAP_CAN_PERSIST_TDLS_BSSID 0x20
+
+struct wfd_ie_sub_ext_cap {
+ uint16_t flags;
+} WFD__PACKED;
+
+/*
+ * IE subelement local ip
+ */
+
+#define WFD_IE_SUB_LOCAL_IP_IPV4 0x01
+
+struct wfd_ie_sub_local_ip {
+ uint8_t version;
+ uint8_t ip[4];
+} WFD__PACKED;
+
+/*
+ * IE subelement session information
+ */
+
+/* real payload is actually an array of this object for each device */
+struct wfd_ie_sub_session_info {
+ uint8_t length; /* fixed: 23 == (sizeof(this) - 1) */
+ uint8_t mac[6];
+ uint8_t bssid[6];
+ uint16_t dev_info; /* same as dev_info.dev_info */
+ uint16_t max_throughput; /* same as dev_info.max_throughput */
+ uint8_t coupled_status; /* same as coupled_sink.status */
+ uint8_t coupled_mac[6]; /* same as coupled_sink.mac */
+} WFD__PACKED;
+
+/*
+ * IE subelement alternative mac
+ */
+
+struct wfd_ie_sub_alt_mac {
+ uint8_t mac[6];
+} WFD__PACKED;
+
+/** @} */
+
+/**
+ * @defgroup wfd_rtsp Wifi-Display API for RTSP
+ * API for the Wifi-Display specification regarding RTSP
+ *
+ * This section contains definitions and constants from the Wifi-Display
+ * specification regarding RTSP and provides a basic API to parse and handle
+ * WFD-RTSP messages.
+ *
+ * Note that this parser is neither fast nor optimized for memory-usage. RTSP is
+ * not a high-throughput protocol, therefore, this API is made for easy use, not
+ * low-latency and high-throughput. This shouldn't hurt at all. If you use RTSP
+ * for huge payloads, you're doing it wrong. Note that this does *NOT* apply to
+ * RTSP data messages, which allow combining RTP streams with RTSP. These
+ * messages are handled properly and in a fast manner by this decoder.
+ *
+ * This decoder tries to provide a very convenient API that parses basic types
+ * but still allows the caller to get access to any unknown lines. This way,
+ * standard-conformant parsers can be easily written, while extensions are still
+ * allowed.
+ *
+ * @{
+ */
+
+/**
+ * wfd_rtsp_tokenize - Tokenize an RTSP line
+ * @line: input line
+ * @len: length of the line or -1 if zero-terminated
+ * @out: storage pointer for tokens
+ *
+ * This tokenizes a single RTSP line. It splits the given input-line by RTSP
+ * tokens and does some very basic line-parsing. A pointer to the tokenized
+ * string is stored in @out and must be freed by the caller. The tokenized
+ * string is separated by binary-0 and you can use wfd_rtsp_next_token() to jump
+ * to the next token. Binary-0 is not allowed in RTSP lines so this is safe.
+ *
+ * This function returns the number of tokens parsed. On error, a negative errno
+ * code is returned and @out is left untouched.
+ */
+ssize_t wfd_rtsp_tokenize(const char *line, ssize_t len, char **out);
+
+/**
+ * wfd_rtsp_next_token - Return next RTSP token
+ * @tokens: toknized RTSP line
+ *
+ * This can only be used in combination with wfd_rtsp_tokenize. It returns the next
+ * token from the tokenized line.
+ */
+static inline char *wfd_rtsp_next_token(char *tokens)
+{
+ return tokens + strlen(tokens) + 1;
+}
+
+/* RTSP messages */
+
+typedef void (*wfd_rtsp_log_t) (void *data,
+ const char *file,
+ int line,
+ const char *func,
+ const char *subs,
+ unsigned int sev,
+ const char *format,
+ va_list args);
+
+enum wfd_rtsp_msg_type {
+ WFD_RTSP_MSG_UNKNOWN,
+
+ WFD_RTSP_MSG_REQUEST,
+ WFD_RTSP_MSG_RESPONSE,
+
+ WFD_RTSP_MSG_CNT
+};
+
+enum wfd_rtsp_method_type {
+ WFD_RTSP_METHOD_UNKNOWN,
+
+ WFD_RTSP_METHOD_ANNOUNCE,
+ WFD_RTSP_METHOD_DESCRIBE,
+ WFD_RTSP_METHOD_GET_PARAMETER,
+ WFD_RTSP_METHOD_OPTIONS,
+ WFD_RTSP_METHOD_PAUSE,
+ WFD_RTSP_METHOD_PLAY,
+ WFD_RTSP_METHOD_RECORD,
+ WFD_RTSP_METHOD_REDIRECT,
+ WFD_RTSP_METHOD_SETUP,
+ WFD_RTSP_METHOD_SET_PARAMETER,
+ WFD_RTSP_METHOD_TEARDOWN,
+
+ WFD_RTSP_METHOD_CNT
+};
+
+const char *wfd_rtsp_method_get_name(unsigned int method);
+unsigned int wfd_rtsp_method_from_name(const char *method);
+
+enum wfd_rtsp_status_code {
+ WFD_RTSP_STATUS_CONTINUE = 100,
+
+ WFD_RTSP_STATUS_OK = 200,
+ WFD_RTSP_STATUS_CREATED,
+
+ WFD_RTSP_STATUS_LOW_ON_STORAGE_SPACE = 250,
+
+ WFD_RTSP_STATUS_MULTIPLE_CHOICES = 300,
+ WFD_RTSP_STATUS_MOVED_PERMANENTLY,
+ WFD_RTSP_STATUS_MOVED_TEMPORARILY,
+ WFD_RTSP_STATUS_SEE_OTHER,
+ WFD_RTSP_STATUS_NOT_MODIFIED,
+ WFD_RTSP_STATUS_USE_PROXY,
+
+ WFD_RTSP_STATUS_BAD_REQUEST = 400,
+ WFD_RTSP_STATUS_UNAUTHORIZED,
+ WFD_RTSP_STATUS_PAYMENT_REQUIRED,
+ WFD_RTSP_STATUS_FORBIDDEN,
+ WFD_RTSP_STATUS_NOT_FOUND,
+ WFD_RTSP_STATUS_METHOD_NOT_ALLOWED,
+ WFD_RTSP_STATUS_NOT_ACCEPTABLE,
+ WFD_RTSP_STATUS_PROXY_AUTHENTICATION_REQUIRED,
+ WFD_RTSP_STATUS_REQUEST_TIMEOUT,
+ WFD_RTSP_STATUS__PLACEHOLDER__1,
+ WFD_RTSP_STATUS_GONE,
+ WFD_RTSP_STATUS_LENGTH_REQUIRED,
+ WFD_RTSP_STATUS_PRECONDITION_FAILED,
+ WFD_RTSP_STATUS_REQUEST_ENTITY_TOO_LARGE,
+ WFD_RTSP_STATUS_REQUEST_URI_TOO_LARGE,
+ WFD_RTSP_STATUS_UNSUPPORTED_MEDIA_TYPE,
+
+ WFD_RTSP_STATUS_PARAMETER_NOT_UNDERSTOOD = 451,
+ WFD_RTSP_STATUS_CONFERENCE_NOT_FOUND,
+ WFD_RTSP_STATUS_NOT_ENOUGH_BANDWIDTH,
+ WFD_RTSP_STATUS_SESSION_NOT_FOUND,
+ WFD_RTSP_STATUS_METHOD_NOT_VALID_IN_THIS_STATE,
+ WFD_RTSP_STATUS_HEADER_FIELD_NOT_VALID_FOR_RESOURCE,
+ WFD_RTSP_STATUS_INVALID_RANGE,
+ WFD_RTSP_STATUS_PARAMETER_IS_READ_ONLY,
+ WFD_RTSP_STATUS_AGGREGATE_OPERATION_NOT_ALLOWED,
+ WFD_RTSP_STATUS_ONLY_AGGREGATE_OPERATION_ALLOWED,
+ WFD_RTSP_STATUS_UNSUPPORTED_TRANSPORT,
+ WFD_RTSP_STATUS_DESTINATION_UNREACHABLE,
+
+ WFD_RTSP_STATUS_INTERNAL_SERVER_ERROR = 500,
+ WFD_RTSP_STATUS_NOT_IMPLEMENTED,
+ WFD_RTSP_STATUS_BAD_GATEWAY,
+ WFD_RTSP_STATUS_SERVICE_UNAVAILABLE,
+ WFD_RTSP_STATUS_GATEWAY_TIMEOUT,
+ WFD_RTSP_STATUS_WFD_RTSP_VERSION_NOT_SUPPORTED,
+
+ WFD_RTSP_STATUS_OPTION_NOT_SUPPORTED = 551,
+
+ WFD_RTSP_STATUS_CNT
+};
+
+bool wfd_rtsp_status_is_valid(unsigned int status);
+unsigned int wfd_rtsp_status_get_base(unsigned int status);
+const char *wfd_rtsp_status_get_description(unsigned int status);
+
+enum wfd_rtsp_header_type {
+ WFD_RTSP_HEADER_UNKNOWN,
+
+ WFD_RTSP_HEADER_ACCEPT,
+ WFD_RTSP_HEADER_ACCEPT_ENCODING,
+ WFD_RTSP_HEADER_ACCEPT_LANGUAGE,
+ WFD_RTSP_HEADER_ALLOW,
+ WFD_RTSP_HEADER_AUTHORIZATION,
+ WFD_RTSP_HEADER_BANDWIDTH,
+ WFD_RTSP_HEADER_BLOCKSIZE,
+ WFD_RTSP_HEADER_CACHE_CONTROL,
+ WFD_RTSP_HEADER_CONFERENCE,
+ WFD_RTSP_HEADER_CONNECTION,
+ WFD_RTSP_HEADER_CONTENT_BASE,
+ WFD_RTSP_HEADER_CONTENT_ENCODING,
+ WFD_RTSP_HEADER_CONTENT_LANGUAGE,
+ WFD_RTSP_HEADER_CONTENT_LENGTH,
+ WFD_RTSP_HEADER_CONTENT_LOCATION,
+ WFD_RTSP_HEADER_CONTENT_TYPE,
+ WFD_RTSP_HEADER_CSEQ,
+ WFD_RTSP_HEADER_DATE,
+ WFD_RTSP_HEADER_EXPIRES,
+ WFD_RTSP_HEADER_FROM,
+ WFD_RTSP_HEADER_HOST,
+ WFD_RTSP_HEADER_IF_MATCH,
+ WFD_RTSP_HEADER_IF_MODIFIED_SINCE,
+ WFD_RTSP_HEADER_LAST_MODIFIED,
+ WFD_RTSP_HEADER_LOCATION,
+ WFD_RTSP_HEADER_PROXY_AUTHENTICATE,
+ WFD_RTSP_HEADER_PROXY_REQUIRE,
+ WFD_RTSP_HEADER_PUBLIC,
+ WFD_RTSP_HEADER_RANGE,
+ WFD_RTSP_HEADER_REFERER,
+ WFD_RTSP_HEADER_RETRY_AFTER,
+ WFD_RTSP_HEADER_REQUIRE,
+ WFD_RTSP_HEADER_RTP_INFO,
+ WFD_RTSP_HEADER_SCALE,
+ WFD_RTSP_HEADER_SPEED,
+ WFD_RTSP_HEADER_SERVER,
+ WFD_RTSP_HEADER_SESSION,
+ WFD_RTSP_HEADER_TIMESTAMP,
+ WFD_RTSP_HEADER_TRANSPORT,
+ WFD_RTSP_HEADER_UNSUPPORTED,
+ WFD_RTSP_HEADER_USER_AGENT,
+ WFD_RTSP_HEADER_VARY,
+ WFD_RTSP_HEADER_VIA,
+ WFD_RTSP_HEADER_WWW_AUTHENTICATE,
+
+ WFD_RTSP_HEADER_CNT
+};
+
+const char *wfd_rtsp_header_get_name(unsigned int header);
+unsigned int wfd_rtsp_header_from_name(const char *header);
+unsigned int wfd_rtsp_header_from_name_n(const char *header, size_t len);
+
+struct wfd_rtsp_msg {
+ unsigned int type;
+
+ struct wfd_rtsp_msg_id {
+ char *line;
+ size_t length;
+
+ union {
+ struct {
+ char *method;
+ unsigned int type;
+ char *uri;
+ unsigned int major;
+ unsigned int minor;
+ } request;
+
+ struct {
+ unsigned int major;
+ unsigned int minor;
+ unsigned int status;
+ char *phrase;
+ } response;
+ };
+ } id;
+
+ struct wfd_rtsp_msg_header {
+ size_t count;
+ char **lines;
+ size_t *lengths;
+
+ union {
+ size_t content_length;
+ unsigned long cseq;
+ };
+ } headers[WFD_RTSP_HEADER_CNT];
+
+ struct wfd_rtsp_msg_entity {
+ void *value;
+ size_t size;
+ } entity;
+};
+
+/* rtsp decoder */
+
+struct wfd_rtsp_decoder;
+
+enum wfd_rtsp_decoder_event_type {
+ WFD_RTSP_DECODER_MSG,
+ WFD_RTSP_DECODER_DATA,
+ WFD_RTSP_DECODER_ERROR,
+};
+
+struct wfd_rtsp_decoder_event {
+ unsigned int type;
+
+ union {
+ struct wfd_rtsp_msg *msg;
+ struct wfd_rtsp_decoder_data {
+ uint8_t channel;
+ uint16_t size;
+ uint8_t *value;
+ } data;
+ struct wfd_rtsp_decoder_error {
+ void *data;
+ size_t length;
+ } error;
+ };
+};
+
+typedef int (*wfd_rtsp_decoder_event_t) (struct wfd_rtsp_decoder *dec,
+ void *data,
+ struct wfd_rtsp_decoder_event *event);
+
+int wfd_rtsp_decoder_new(wfd_rtsp_decoder_event_t event_fn,
+ void *data,
+ wfd_rtsp_log_t log_fn,
+ void *log_data,
+ struct wfd_rtsp_decoder **out);
+void wfd_rtsp_decoder_free(struct wfd_rtsp_decoder *dec);
+void wfd_rtsp_decoder_reset(struct wfd_rtsp_decoder *dec);
+
+void wfd_rtsp_decoder_set_data(struct wfd_rtsp_decoder *dec, void *data);
+void *wfd_rtsp_decoder_get_data(struct wfd_rtsp_decoder *dec);
+
+int wfd_rtsp_decoder_feed(struct wfd_rtsp_decoder *dec,
+ const void *buf,
+ size_t len);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WFD_LIBWFD_H */
diff --git a/src/rtsp_decoder.c b/src/rtsp_decoder.c
new file mode 100644
index 0000000..fc74076
--- /dev/null
+++ b/src/rtsp_decoder.c
@@ -0,0 +1,1270 @@
+/*
+ * libwfd - Wifi-Display/Miracast Protocol Implementation
+ *
+ * Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "libwfd.h"
+#include "shl_llog.h"
+#include "shl_macro.h"
+#include "shl_ring.h"
+#include "shl_util.h"
+
+enum state {
+ STATE_NEW,
+ STATE_HEADER,
+ STATE_HEADER_QUOTE,
+ STATE_HEADER_NL,
+ STATE_BODY,
+ STATE_DATA_HEAD,
+ STATE_DATA_BODY,
+};
+
+struct wfd_rtsp_decoder {
+ wfd_rtsp_decoder_event_t event_fn;
+ void *data;
+ llog_submit_t llog;
+ void *llog_data;
+
+ struct wfd_rtsp_msg msg;
+
+ struct shl_ring buf;
+ size_t buflen;
+ unsigned int state;
+ char last_chr;
+ size_t remaining_body;
+
+ uint8_t data_channel;
+ size_t data_size;
+
+ bool quoted : 1;
+ bool dead : 1;
+};
+
+/*
+ * Lookup Tables
+ */
+
+static const char *method_names[] = {
+ [WFD_RTSP_METHOD_ANNOUNCE] = "ANNOUNCE",
+ [WFD_RTSP_METHOD_DESCRIBE] = "DESCRIBE",
+ [WFD_RTSP_METHOD_GET_PARAMETER] = "GET_PARAMETER",
+ [WFD_RTSP_METHOD_OPTIONS] = "OPTIONS",
+ [WFD_RTSP_METHOD_PAUSE] = "PAUSE",
+ [WFD_RTSP_METHOD_PLAY] = "PLAY",
+ [WFD_RTSP_METHOD_RECORD] = "RECORD",
+ [WFD_RTSP_METHOD_REDIRECT] = "REDIRECT",
+ [WFD_RTSP_METHOD_SETUP] = "SETUP",
+ [WFD_RTSP_METHOD_SET_PARAMETER] = "SET_PARAMETER",
+ [WFD_RTSP_METHOD_TEARDOWN] = "TEARDOWN",
+ [WFD_RTSP_METHOD_CNT] = NULL,
+};
+
+_shl_public_
+const char *wfd_rtsp_method_get_name(unsigned int method)
+{
+ if (method >= SHL_ARRAY_LENGTH(method_names))
+ return NULL;
+
+ return method_names[method];
+}
+
+_shl_public_
+unsigned int wfd_rtsp_method_from_name(const char *method)
+{
+ size_t i;
+
+ for (i = 0; i < SHL_ARRAY_LENGTH(method_names); ++i)
+ if (method_names[i] && !strcasecmp(method, method_names[i]))
+ return i;
+
+ return WFD_RTSP_METHOD_UNKNOWN;
+}
+
+_shl_public_
+bool wfd_rtsp_status_is_valid(unsigned int status)
+{
+ return status >= 100 && status < 600;
+}
+
+_shl_public_
+unsigned int wfd_rtsp_status_get_base(unsigned int status)
+{
+ switch (status) {
+ case 100 ... 199:
+ return 100;
+ case 200 ... 299:
+ return 200;
+ case 300 ... 399:
+ return 300;
+ case 400 ... 499:
+ return 400;
+ case 500 ... 599:
+ return 500;
+ default:
+ return 600;
+ }
+}
+
+static const char *status_descriptions[] = {
+ [WFD_RTSP_STATUS_CONTINUE] = "Continue",
+
+ [WFD_RTSP_STATUS_OK] = "OK",
+ [WFD_RTSP_STATUS_CREATED] = "Created",
+
+ [WFD_RTSP_STATUS_LOW_ON_STORAGE_SPACE] = "Low on Storage Space",
+
+ [WFD_RTSP_STATUS_MULTIPLE_CHOICES] = "Multiple Choices",
+ [WFD_RTSP_STATUS_MOVED_PERMANENTLY] = "Moved Permanently",
+ [WFD_RTSP_STATUS_MOVED_TEMPORARILY] = "Moved Temporarily",
+ [WFD_RTSP_STATUS_SEE_OTHER] = "See Other",
+ [WFD_RTSP_STATUS_NOT_MODIFIED] = "Not Modified",
+ [WFD_RTSP_STATUS_USE_PROXY] = "Use Proxy",
+
+ [WFD_RTSP_STATUS_BAD_REQUEST] = "Bad Request",
+ [WFD_RTSP_STATUS_UNAUTHORIZED] = "Unauthorized",
+ [WFD_RTSP_STATUS_PAYMENT_REQUIRED] = "Payment Required",
+ [WFD_RTSP_STATUS_FORBIDDEN] = "Forbidden",
+ [WFD_RTSP_STATUS_NOT_FOUND] = "Not Found",
+ [WFD_RTSP_STATUS_METHOD_NOT_ALLOWED] = "Method not Allowed",
+ [WFD_RTSP_STATUS_NOT_ACCEPTABLE] = "Not Acceptable",
+ [WFD_RTSP_STATUS_PROXY_AUTHENTICATION_REQUIRED] = "Proxy Authentication Required",
+ [WFD_RTSP_STATUS_REQUEST_TIMEOUT] = "Request Time-out",
+ [WFD_RTSP_STATUS_GONE] = "Gone",
+ [WFD_RTSP_STATUS_LENGTH_REQUIRED] = "Length Required",
+ [WFD_RTSP_STATUS_PRECONDITION_FAILED] = "Precondition Failed",
+ [WFD_RTSP_STATUS_REQUEST_ENTITY_TOO_LARGE] = "Request Entity Too Large",
+ [WFD_RTSP_STATUS_REQUEST_URI_TOO_LARGE] = "Request-URI too Large",
+ [WFD_RTSP_STATUS_UNSUPPORTED_MEDIA_TYPE] = "Unsupported Media Type",
+
+ [WFD_RTSP_STATUS_PARAMETER_NOT_UNDERSTOOD] = "Parameter not Understood",
+ [WFD_RTSP_STATUS_CONFERENCE_NOT_FOUND] = "Conference not Found",
+ [WFD_RTSP_STATUS_NOT_ENOUGH_BANDWIDTH] = "Not Enough Bandwidth",
+ [WFD_RTSP_STATUS_SESSION_NOT_FOUND] = "Session not Found",
+ [WFD_RTSP_STATUS_METHOD_NOT_VALID_IN_THIS_STATE] = "Method not Valid in this State",
+ [WFD_RTSP_STATUS_HEADER_FIELD_NOT_VALID_FOR_RESOURCE] = "Header Field not Valid for Resource",
+ [WFD_RTSP_STATUS_INVALID_RANGE] = "Invalid Range",
+ [WFD_RTSP_STATUS_PARAMETER_IS_READ_ONLY] = "Parameter is Read-only",
+ [WFD_RTSP_STATUS_AGGREGATE_OPERATION_NOT_ALLOWED] = "Aggregate Operation not Allowed",
+ [WFD_RTSP_STATUS_ONLY_AGGREGATE_OPERATION_ALLOWED] = "Only Aggregate Operation Allowed",
+ [WFD_RTSP_STATUS_UNSUPPORTED_TRANSPORT] = "Unsupported Transport",
+ [WFD_RTSP_STATUS_DESTINATION_UNREACHABLE] = "Destination Unreachable",
+
+ [WFD_RTSP_STATUS_INTERNAL_SERVER_ERROR] = "Internal Server Error",
+ [WFD_RTSP_STATUS_NOT_IMPLEMENTED] = "Not Implemented",
+ [WFD_RTSP_STATUS_BAD_GATEWAY] = "Bad Gateway",
+ [WFD_RTSP_STATUS_SERVICE_UNAVAILABLE] = "Service Unavailable",
+ [WFD_RTSP_STATUS_GATEWAY_TIMEOUT] = "Gateway Time-out",
+ [WFD_RTSP_STATUS_WFD_RTSP_VERSION_NOT_SUPPORTED] = "RTSP Version not Supported",
+
+ [WFD_RTSP_STATUS_OPTION_NOT_SUPPORTED] = "Option not Supported",
+
+ [WFD_RTSP_STATUS_CNT] = NULL,
+};
+
+_shl_public_
+const char *wfd_rtsp_status_get_description(unsigned int status)
+{
+ if (status >= SHL_ARRAY_LENGTH(status_descriptions))
+ return NULL;
+
+ return status_descriptions[status];
+}
+
+static const char *header_names[] = {
+ [WFD_RTSP_HEADER_ACCEPT] = "Accept",
+ [WFD_RTSP_HEADER_ACCEPT_ENCODING] = "Accept-Encoding",
+ [WFD_RTSP_HEADER_ACCEPT_LANGUAGE] = "Accept-Language",
+ [WFD_RTSP_HEADER_ALLOW] = "Allow",
+ [WFD_RTSP_HEADER_AUTHORIZATION] = "Authorization",
+ [WFD_RTSP_HEADER_BANDWIDTH] = "Bandwidth",
+ [WFD_RTSP_HEADER_BLOCKSIZE] = "Blocksize",
+ [WFD_RTSP_HEADER_CACHE_CONTROL] = "Cache-Control",
+ [WFD_RTSP_HEADER_CONFERENCE] = "Conference",
+ [WFD_RTSP_HEADER_CONNECTION] = "Connection",
+ [WFD_RTSP_HEADER_CONTENT_BASE] = "Content-Base",
+ [WFD_RTSP_HEADER_CONTENT_ENCODING] = "Content-Encoding",
+ [WFD_RTSP_HEADER_CONTENT_LANGUAGE] = "Content-Language",
+ [WFD_RTSP_HEADER_CONTENT_LENGTH] = "Content-Length",
+ [WFD_RTSP_HEADER_CONTENT_LOCATION] = "Content-Location",
+ [WFD_RTSP_HEADER_CONTENT_TYPE] = "Content-Type",
+ [WFD_RTSP_HEADER_CSEQ] = "CSeq",
+ [WFD_RTSP_HEADER_DATE] = "Date",
+ [WFD_RTSP_HEADER_EXPIRES] = "Expires",
+ [WFD_RTSP_HEADER_FROM] = "From",
+ [WFD_RTSP_HEADER_HOST] = "Host",
+ [WFD_RTSP_HEADER_IF_MATCH] = "If-Match",
+ [WFD_RTSP_HEADER_IF_MODIFIED_SINCE] = "If-Modified-Since",
+ [WFD_RTSP_HEADER_LAST_MODIFIED] = "Last-Modified",
+ [WFD_RTSP_HEADER_LOCATION] = "Location",
+ [WFD_RTSP_HEADER_PROXY_AUTHENTICATE] = "Proxy-Authenticate",
+ [WFD_RTSP_HEADER_PROXY_REQUIRE] = "Proxy-Require",
+ [WFD_RTSP_HEADER_PUBLIC] = "Public",
+ [WFD_RTSP_HEADER_RANGE] = "Range",
+ [WFD_RTSP_HEADER_REFERER] = "Referer",
+ [WFD_RTSP_HEADER_RETRY_AFTER] = "Retry-After",
+ [WFD_RTSP_HEADER_REQUIRE] = "Require",
+ [WFD_RTSP_HEADER_RTP_INFO] = "RTP-Info",
+ [WFD_RTSP_HEADER_SCALE] = "Scale",
+ [WFD_RTSP_HEADER_SPEED] = "Speed",
+ [WFD_RTSP_HEADER_SERVER] = "Server",
+ [WFD_RTSP_HEADER_SESSION] = "Session",
+ [WFD_RTSP_HEADER_TIMESTAMP] = "Timestamp",
+ [WFD_RTSP_HEADER_TRANSPORT] = "Transport",
+ [WFD_RTSP_HEADER_UNSUPPORTED] = "Unsupported",
+ [WFD_RTSP_HEADER_USER_AGENT] = "User-Agent",
+ [WFD_RTSP_HEADER_VARY] = "Vary",
+ [WFD_RTSP_HEADER_VIA] = "Via",
+ [WFD_RTSP_HEADER_WWW_AUTHENTICATE] = "WWW-Authenticate",
+ [WFD_RTSP_HEADER_CNT] = NULL,
+};
+
+_shl_public_
+const char *wfd_rtsp_header_get_name(unsigned int header)
+{
+ if (header >= SHL_ARRAY_LENGTH(header_names))
+ return NULL;
+
+ return header_names[header];
+}
+
+_shl_public_
+unsigned int wfd_rtsp_header_from_name(const char *header)
+{
+ size_t i;
+
+ for (i = 0; i < SHL_ARRAY_LENGTH(header_names); ++i)
+ if (header_names[i] && !strcasecmp(header, header_names[i]))
+ return i;
+
+ return WFD_RTSP_HEADER_UNKNOWN;
+}
+
+_shl_public_
+unsigned int wfd_rtsp_header_from_name_n(const char *header, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < SHL_ARRAY_LENGTH(header_names); ++i)
+ if (header_names[i] &&
+ !strncasecmp(header, header_names[i], len) &&
+ !header_names[i][len])
+ return i;
+
+ return WFD_RTSP_HEADER_UNKNOWN;
+}
+
+/*
+ * Helpers
+ */
+
+static void msg_clear_id(struct wfd_rtsp_msg *msg)
+{
+ switch (msg->type) {
+ case WFD_RTSP_MSG_REQUEST:
+ free(msg->id.request.method);
+ free(msg->id.request.uri);
+ break;
+ case WFD_RTSP_MSG_RESPONSE:
+ free(msg->id.response.phrase);
+ break;
+ }
+
+ free(msg->id.line);
+ shl_zero(msg->id);
+}
+
+static void msg_clear_headers(struct wfd_rtsp_msg *msg)
+{
+ struct wfd_rtsp_msg_header *h;
+ size_t i, j;
+
+ for (i = 0; i < WFD_RTSP_HEADER_CNT; ++i) {
+ h = &msg->headers[i];
+
+ for (j = 0; j < h->count; ++j)
+ free(h->lines[j]);
+
+ free(h->lines);
+ free(h->lengths);
+ }
+
+ shl_zero(msg->headers);
+}
+
+static void msg_clear_entity(struct wfd_rtsp_msg *msg)
+{
+ free(msg->entity.value);
+ shl_zero(msg->entity);
+}
+
+static void msg_clear(struct wfd_rtsp_msg *msg)
+{
+ msg_clear_id(msg);
+ msg_clear_headers(msg);
+ msg_clear_entity(msg);
+}
+
+static int decoder_call(struct wfd_rtsp_decoder *dec,
+ struct wfd_rtsp_decoder_event *ev)
+{
+ return dec->event_fn(dec, dec->data, ev);
+}
+
+static int decoder_submit(struct wfd_rtsp_decoder *dec)
+{
+ struct wfd_rtsp_decoder_event ev = { };
+ int r;
+
+ ev.type = WFD_RTSP_DECODER_MSG;
+ ev.msg = &dec->msg;
+ r = decoder_call(dec, &ev);
+ msg_clear(&dec->msg);
+
+ return r;
+}
+
+static int decoder_submit_data(struct wfd_rtsp_decoder *dec, uint8_t *p)
+{
+ struct wfd_rtsp_decoder_event ev = { };
+
+ ev.type = WFD_RTSP_DECODER_DATA;
+ ev.data.channel = dec->data_channel;
+ ev.data.size = dec->data_size;
+ ev.data.value = p;
+ return decoder_call(dec, &ev);
+}
+
+/*
+ * Header ID-line Handling
+ * This parses both, the REQUEST and RESPONSE lines of an RTSP method. It is
+ * always the first header line and defines the type of message. If it is
+ * unrecognized, we set it to UNKNOWN.
+ * Note that regardless of the ID-line, all following lines are parsed as
+ * generic headers followed by an optional entity.
+ */
+
+static int decoder_parse_request(struct wfd_rtsp_decoder *dec,
+ char *line,
+ size_t len)
+{
+ unsigned int major, minor;
+ size_t cmdlen, urllen;
+ char *next, *prev, *cmd, *url;
+
+ /* Requests look like this:
+ * <cmd> <url> RTSP/<major>.<minor>
+ * We try to match <cmd> here, but accept invalid commands. <url> is
+ * never parsed (it can become pretty complex if done properly). */
+
+ 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;
+
+ cmd = strndup(cmd, cmdlen);
+ url = strndup(url, urllen);
+ if (!cmd || !url) {
+ free(cmd);
+ return llog_ENOMEM(dec);
+ }
+
+ dec->msg.type = WFD_RTSP_MSG_REQUEST;
+ dec->msg.id.line = line;
+ dec->msg.id.length = len;
+ dec->msg.id.request.method = cmd;
+ dec->msg.id.request.type = wfd_rtsp_method_from_name(cmd);
+ dec->msg.id.request.uri = url;
+ dec->msg.id.request.major = major;
+ dec->msg.id.request.minor = minor;
+
+ 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). */
+ dec->msg.type = WFD_RTSP_MSG_UNKNOWN;
+ dec->msg.id.line = line;
+ dec->msg.id.length = len;
+ return 0;
+}
+
+static int decoder_parse_response(struct wfd_rtsp_decoder *dec,
+ char *line,
+ size_t len)
+{
+ unsigned int major, minor, code;
+ char *prev, *next, *str;
+
+ /* 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. */
+
+ /* skip "RTSP/", already parsed by parent */
+ 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 = strdup(next);
+ if (!str)
+ return llog_ENOMEM(dec);
+
+ dec->msg.type = WFD_RTSP_MSG_RESPONSE;
+ dec->msg.id.line = line;
+ dec->msg.id.length = len;
+ dec->msg.id.response.major = major;
+ dec->msg.id.response.minor = minor;
+ dec->msg.id.response.status = code;
+ dec->msg.id.response.phrase = str;
+
+ 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. */
+ dec->msg.type = WFD_RTSP_MSG_UNKNOWN;
+ dec->msg.id.line = line;
+ dec->msg.id.length = len;
+ return 0;
+}
+
+static int decoder_parse_id(struct wfd_rtsp_decoder *dec, char *line, size_t len)
+{
+ if (!strncasecmp(line, "RTSP/", 5))
+ return decoder_parse_response(dec, line, len);
+ else
+ return decoder_parse_request(dec, line, len);
+}
+
+/*
+ * RTSP Header Parser
+ * This parses RTSP header lines. These follow the ID-line and may contain
+ * arbitrary additional information. Note that we parse any kind of message that
+ * we cannot identify as UNKNOWN. Thus, the caller can implement arbitrary
+ * additional parsers.
+ *
+ * Furthermore, if a header-line cannot be parsed correctly, even though the
+ * header-type is known, we still add it as UNKNOWN. Therefore, if the caller
+ * implements extensions to *known* header lines, it still needs to go through
+ * all unknown lines. It's not enough to go through the lines of the given type.
+ */
+
+static int header_append(struct wfd_rtsp_msg_header *h, char *line, size_t len)
+{
+ char **tlines;
+ size_t *tlengths;
+ size_t num;
+
+ num = h->count + 2;
+
+ tlines = realloc(h->lines, num * sizeof(*h->lines));
+ if (!tlines)
+ return -ENOMEM;
+ h->lines = tlines;
+
+ tlengths = realloc(h->lengths, num * sizeof(*h->lengths));
+ if (!tlengths)
+ return -ENOMEM;
+ h->lengths = tlengths;
+
+ h->lines[h->count] = line;
+ h->lengths[h->count] = len;
+ ++h->count;
+ h->lines[h->count] = NULL;
+ h->lengths[h->count] = 0;
+
+ return 0;
+}
+
+static int decoder_add_unknown_line(struct wfd_rtsp_decoder *dec,
+ char *line,
+ size_t len)
+{
+ struct wfd_rtsp_msg_header *h;
+ int r;
+
+ /* Cannot parse header line. Append it at the end of the line-array
+ * of type UNKNOWN. Let the caller deal with it. */
+
+ h = &dec->msg.headers[WFD_RTSP_HEADER_UNKNOWN];
+ r = header_append(h, line, len);
+ return r < 0 ? llog_ERR(dec, r) : 0;
+}
+
+static int decoder_parse_content_length(struct wfd_rtsp_decoder *dec,
+ char *line,
+ size_t len,
+ char *tokens,
+ size_t num)
+{
+ struct wfd_rtsp_msg_header *h;
+ int r;
+ size_t clen;
+ char *next;
+
+ h = &dec->msg.headers[WFD_RTSP_HEADER_CONTENT_LENGTH];
+
+ r = shl_atoi_z(tokens, 10, (const char**)&next, &clen);
+ if (r < 0 || *next) {
+ /* Screwed content-length line? We cannot recover from that as
+ * the attached entity is of unknown length. Abort.. */
+ return -EINVAL;
+ }
+
+ r = header_append(h, line, len);
+ if (r < 0)
+ return llog_ERR(dec, r);
+
+ /* overwrite previous lengths */
+ h->content_length = clen;
+ dec->remaining_body = clen;
+
+ return 0;
+}
+
+static int decoder_parse_cseq(struct wfd_rtsp_decoder *dec,
+ char *line,
+ size_t len,
+ char *tokens,
+ size_t num)
+{
+ struct wfd_rtsp_msg_header *h;
+ int r;
+ unsigned long val;
+ char *next;
+
+ h = &dec->msg.headers[WFD_RTSP_HEADER_CSEQ];
+
+ r = shl_atoi_ul(tokens, 10, (const char**)&next, &val);
+ if (r < 0 || *next) {
+ /* Screwed cseq line? Append it as unknown line. */
+ return decoder_add_unknown_line(dec, line, len);
+ }
+
+ r = header_append(h, line, len);
+ if (r < 0)
+ return llog_ERR(dec, r);
+
+ /* overwrite previous cseqs */
+ h->cseq = val;
+
+ return 0;
+}
+
+static int decoder_parse_header(struct wfd_rtsp_decoder *dec,
+ char *line,
+ size_t len)
+{
+ unsigned int type;
+ char *next, *tokens;
+ ssize_t num;
+ int r;
+
+ num = wfd_rtsp_tokenize(line, len, &tokens);
+ if (num < 0)
+ return llog_ENOMEM(dec);
+ if (num < 2)
+ goto error;
+
+ /* Header lines look like this:
+ * <name>: <value> */
+ next = tokens;
+
+ /* parse <name> */
+ type = wfd_rtsp_header_from_name(next);
+ next = wfd_rtsp_next_token(next);
+ --num;
+
+ /* parse ":" */
+ if (strcmp(next, ":"))
+ goto error;
+ next = wfd_rtsp_next_token(next);
+ --num;
+
+ /* dispatch to header specific parser */
+ switch (type) {
+ case WFD_RTSP_HEADER_CONTENT_LENGTH:
+ r = decoder_parse_content_length(dec, line, len, next, num);
+ break;
+ case WFD_RTSP_HEADER_CSEQ:
+ r = decoder_parse_cseq(dec, line, len, next, num);
+ break;
+ default:
+ /* no parser for given type available; append to list */
+ r = header_append(&dec->msg.headers[type], line, len);
+ if (r < 0)
+ llog_vERR(dec, r);
+ break;
+ }
+
+ free(tokens);
+ return r;
+
+error:
+ free(tokens);
+ return decoder_add_unknown_line(dec, line, len);
+}
+
+/*
+ * Generic Header-line and ID-line parser
+ * This parser is invoked on each successfully read header/id-line. It sanitizes
+ * the input and then calls the correct header or ID parser, depending on
+ * current state.
+ */
+
+static size_t sanitize_header_line(struct wfd_rtsp_decoder *dec,
+ 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 = 0;
+ escaped = 0;
+
+ for (i = 0; i < len; ++i) {
+ c = *src++;
+ prev = last_c;
+ last_c = c;
+
+ if (quoted) {
+ if (prev == '\\' && !escaped) {
+ escaped = 1;
+ /* turn escaped binary zero into "\0" */
+ if (c == '\0')
+ c = '0';
+ } else {
+ escaped = 0;
+ if (c == '"') {
+ quoted = 0;
+ } else if (c == '\0') {
+ /* skip binary 0 */
+ continue;
+ }
+ }
+ } else {
+ /* ignore any binary 0 */
+ if (c == '\0')
+ 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;
+
+ if (c == '"') {
+ quoted = 1;
+ escaped = 0;
+ }
+ }
+
+ *dst++ = c;
+ }
+
+ /* terminate string with binary zero */
+ *dst = 0;
+
+ /* remove trailing whitespace */
+ while (dst > line && *(dst - 1) == ' ')
+ *--dst = 0;
+
+ /* the decoder already trims leading-whitespace */
+
+ return dst - line;
+}
+
+static int decoder_finish_header_line(struct wfd_rtsp_decoder *dec)
+{
+ char *line;
+ size_t l;
+ int r;
+
+ line = malloc(dec->buflen + 1);
+ if (!line)
+ return llog_ENOMEM(dec);
+
+ shl_ring_copy(&dec->buf, line, dec->buflen);
+ line[dec->buflen] = 0;
+ l = sanitize_header_line(dec, line, dec->buflen);
+
+ if (!dec->msg.id.line)
+ r = decoder_parse_id(dec, line, l);
+ else
+ r = decoder_parse_header(dec, line, l);
+
+ if (r < 0)
+ free(line);
+
+ return r;
+}
+
+/*
+ * State Machine
+ * The decoder state-machine is quite simple. We take an input buffer of
+ * arbitrary length from the user 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.
+ */
+
+_shl_public_
+int wfd_rtsp_decoder_new(wfd_rtsp_decoder_event_t event_fn,
+ void *data,
+ wfd_rtsp_log_t log_fn,
+ void *log_data,
+ struct wfd_rtsp_decoder **out)
+{
+ struct wfd_rtsp_decoder *dec;
+
+ if (!event_fn || !out)
+ return llog_dEINVAL(log_fn, data);
+
+ dec = calloc(1, sizeof(*dec));
+ if (!dec)
+ return llog_dENOMEM(log_fn, data);
+
+ dec->event_fn = event_fn;
+ dec->data = data;
+ dec->llog = log_fn;
+ dec->llog_data = log_data;
+
+ *out = dec;
+ return 0;
+}
+
+_shl_public_
+void wfd_rtsp_decoder_free(struct wfd_rtsp_decoder *dec)
+{
+ if (!dec)
+ return;
+
+ msg_clear(&dec->msg);
+ shl_ring_clear(&dec->buf);
+ free(dec);
+}
+
+_shl_public_
+void wfd_rtsp_decoder_reset(struct wfd_rtsp_decoder *dec)
+{
+ if (!dec)
+ return;
+
+ msg_clear(&dec->msg);
+ shl_ring_flush(&dec->buf);
+
+ dec->buflen = 0;
+ dec->last_chr = 0;
+ dec->state = 0;
+ dec->remaining_body = 0;
+
+ dec->data_channel = 0;
+ dec->data_size = 0;
+
+ dec->quoted = false;
+ dec->dead = false;
+}
+
+_shl_public_
+void wfd_rtsp_decoder_set_data(struct wfd_rtsp_decoder *dec, void *data)
+{
+ if (!dec)
+ return;
+
+ dec->data = data;
+}
+
+_shl_public_
+void *wfd_rtsp_decoder_get_data(struct wfd_rtsp_decoder *dec)
+{
+ if (!dec)
+ return NULL;
+
+ return dec->data;
+}
+
+static int decoder_feed_char_new(struct wfd_rtsp_decoder *dec, char ch)
+{
+ 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 decoder_feed_char_header(struct wfd_rtsp_decoder *dec, char ch)
+{
+ 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 = decoder_finish_header_line(dec);
+ 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 = decoder_submit(dec);
+ 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 = decoder_finish_header_line(dec);
+ 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 = decoder_submit(dec);
+ 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 = decoder_finish_header_line(dec);
+ 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 decoder_feed_char_header_quote(struct wfd_rtsp_decoder *dec, char ch)
+{
+ 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 decoder_feed_char_body(struct wfd_rtsp_decoder *dec, char ch)
+{
+ 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 decoder_feed_char_new(dec, ch);
+ }
+
+ /* *any* character is allowed as body */
+ ++dec->buflen;
+
+ if (!--dec->remaining_body) {
+ /* full body received, copy it and go to STATE_NEW */
+
+ line = malloc(dec->buflen + 1);
+ if (!line)
+ return llog_ENOMEM(dec);
+
+ shl_ring_copy(&dec->buf, line, dec->buflen);
+ line[dec->buflen] = 0;
+
+ dec->msg.entity.value = line;
+ dec->msg.entity.size = dec->buflen;
+ r = decoder_submit(dec);
+
+ dec->state = STATE_NEW;
+ shl_ring_pull(&dec->buf, dec->buflen);
+ dec->buflen = 0;
+
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int decoder_feed_char_header_nl(struct wfd_rtsp_decoder *dec, char ch)
+{
+ /* 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 decoder_feed_char_body(dec, ch);
+ }
+}
+
+static int decoder_feed_char_data_head(struct wfd_rtsp_decoder *dec, char ch)
+{
+ 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 decoder_feed_char_data_body(struct wfd_rtsp_decoder *dec, char ch)
+{
+ 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 llog_ENOMEM(dec);
+
+ /* 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 = decoder_submit_data(dec, 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 decoder_feed_char(struct wfd_rtsp_decoder *dec, char ch)
+{
+ int r = 0;
+
+ switch (dec->state) {
+ case STATE_NEW:
+ r = decoder_feed_char_new(dec, ch);
+ break;
+ case STATE_HEADER:
+ r = decoder_feed_char_header(dec, ch);
+ break;
+ case STATE_HEADER_QUOTE:
+ r = decoder_feed_char_header_quote(dec, ch);
+ break;
+ case STATE_HEADER_NL:
+ r = decoder_feed_char_header_nl(dec, ch);
+ break;
+ case STATE_BODY:
+ r = decoder_feed_char_body(dec, ch);
+ break;
+ case STATE_DATA_HEAD:
+ r = decoder_feed_char_data_head(dec, ch);
+ break;
+ case STATE_DATA_BODY:
+ r = decoder_feed_char_data_body(dec, ch);
+ break;
+ }
+
+ return r;
+}
+
+_shl_public_
+int wfd_rtsp_decoder_feed(struct wfd_rtsp_decoder *dec,
+ const void *buf,
+ size_t len)
+{
+ char ch;
+ size_t i;
+ int r;
+
+ if (!dec)
+ return -EINVAL;
+ if (dec->dead)
+ return llog_EINVAL(dec);
+ if (!len)
+ return 0;
+ if (!buf)
+ return llog_EINVAL(dec);
+
+ /* 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) {
+ llog_vERR(dec, r);
+ goto error;
+ }
+
+ for (i = 0; i < len; ++i) {
+ ch = ((const char*)buf)[i];
+ r = decoder_feed_char(dec, ch);
+ if (r < 0)
+ goto error;
+
+ dec->last_chr = ch;
+ }
+
+ /* check for internal parser inconsistencies; should not happen! */
+ if (dec->buflen != shl_ring_get_size(&dec->buf)) {
+ llog_error(dec, "internal RTSP parser error");
+ r = -EFAULT;
+ goto error;
+ }
+
+ return 0;
+
+error:
+ dec->dead = true;
+ return r;
+}
diff --git a/src/rtsp_tokenizer.c b/src/rtsp_tokenizer.c
new file mode 100644
index 0000000..7f56a98
--- /dev/null
+++ b/src/rtsp_tokenizer.c
@@ -0,0 +1,191 @@
+/*
+ * libwfd - Wifi-Display/Miracast Protocol Implementation
+ *
+ * Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include "libwfd.h"
+#include "shl_macro.h"
+
+/*
+ * RTSP Tokenizer
+ * The RTSP standard is word-based and allows linear-whitespace between any two
+ * tokens or special-chars. This tokenizer splits a given line into a list of
+ * tokens and returns how many tokens were generated. It also sanitizes the line
+ * by removing whitespace, trimming leading/trailing whitespace, removing binary
+ * zero characters and decoding escape-sequences.
+ *
+ * This tokenizer can be used before or after the basic RTSP sanitizer. But note
+ * that some RTSP requests or responses contain URIs or other embedded
+ * information which should not be tokenized as they don't follow basic RTSP
+ * rules (yeah, who came up with that shit..).
+ */
+_shl_public_
+ssize_t wfd_rtsp_tokenize(const char *line, ssize_t len, char **out)
+{
+ char *t, *dst, c, prev, last_c;
+ const char *src;
+ size_t num;
+ bool quoted, escaped;
+
+ if (!line || !out)
+ return -EINVAL;
+
+ /* we need at most twice as much space for all the terminating 0s */
+ if (len < 0)
+ len = strlen(line);
+ t = calloc(2, len + 1);
+ if (!t)
+ return -ENOMEM;
+
+ num = 0;
+ src = line;
+ dst = t;
+ quoted = false;
+ escaped = false;
+ prev = 0;
+ last_c = 0;
+
+ for ( ; len > 0; --len) {
+ c = *src++;
+ prev = last_c;
+ last_c = 0;
+
+ if (quoted) {
+ if (escaped) {
+ last_c = c;
+ if (c == '\\') {
+ *dst++ = '\\';
+ } else if (c == '"') {
+ *dst++ = '"';
+ } else if (c == 'n') {
+ *dst++ = '\n';
+ } else if (c == 'r') {
+ *dst++ = '\r';
+ } else if (c == 't') {
+ *dst++ = '\t';
+ } else if (c == 'a') {
+ *dst++ = '\a';
+ } else if (c == 'f') {
+ *dst++ = '\f';
+ } else if (c == 'v') {
+ *dst++ = '\v';
+ } else if (c == 'b') {
+ *dst++ = '\b';
+ } else if (c == 'e') {
+ *dst++ = 0x1b; /* ESC */
+ } else if (c == 0) {
+ /* turn escaped binary 0 into \0 */
+ *dst++ = '\\';
+ *dst++ = '0';
+ last_c = '0';
+ } else {
+ /* keep unknown escape sequences */
+ *dst++ = '\\';
+ *dst++ = c;
+ }
+ escaped = false;
+ } else {
+ if (c == '"') {
+ *dst++ = 0;
+ ++num;
+ quoted = false;
+ } else if (c == '\\') {
+ escaped = true;
+ last_c = prev;
+ } else if (c == 0) {
+ /* discard */
+ last_c = prev;
+ } else {
+ *dst++ = c;
+ last_c = c;
+ }
+ }
+ } else {
+ if (c == '"') {
+ if (prev) {
+ *dst++ = 0;
+ ++num;
+ }
+ quoted = true;
+ } else if (c == 0) {
+ /* discard */
+ last_c = prev;
+ } else if (c == ' ' ||
+ c == '\t' ||
+ c == '\n' ||
+ c == '\r') {
+ if (prev) {
+ *dst++ = 0;
+ ++num;
+ }
+ } else if (c == '(' ||
+ c == ')' ||
+ c == '[' ||
+ c == ']' ||
+ c == '{' ||
+ c == '}' ||
+ c == '<' ||
+ c == '>' ||
+ c == '@' ||
+ c == ',' ||
+ c == ';' ||
+ c == ':' ||
+ c == '\\' ||
+ c == '/' ||
+ c == '?' ||
+ c == '=') {
+ if (prev) {
+ *dst++ = 0;
+ ++num;
+ }
+ *dst++ = c;
+ *dst++ = 0;
+ ++num;
+ } else if (c <= 31 || c == 127) {
+ /* ignore CTLs */
+ if (prev) {
+ *dst++ = 0;
+ ++num;
+ }
+ } else {
+ *dst++ = c;
+ last_c = c;
+ }
+ }
+ }
+
+ if (last_c || quoted) {
+ if (escaped)
+ *dst++ = '\\';
+ *dst++ = 0;
+ ++num;
+ }
+
+ *out = t;
+ return num;
+}
diff --git a/src/shl_llog.h b/src/shl_llog.h
new file mode 100644
index 0000000..1dd4a3c
--- /dev/null
+++ b/src/shl_llog.h
@@ -0,0 +1,247 @@
+/*
+ * SHL - Library Log/Debug Interface
+ *
+ * Copyright (c) 2010-2013 David Herrmann <dh.herrmann@gmail.com>
+ * Dedicated to the Public Domain
+ */
+
+/*
+ * Library Log/Debug Interface
+ * Libraries should always avoid producing side-effects. This includes writing
+ * log-messages of any kind. However, you often don't want to disable debugging
+ * entirely, therefore, the core objects often contain a pointer to a function
+ * which performs logging. If that pointer is NULL (default), logging is
+ * disabled.
+ *
+ * This header should never be installed into the system! This is _no_ public
+ * header. Instead, copy it into your application if you want and use it there.
+ * Your public library API should include something like this:
+ *
+ * typedef void (*MYPREFIX_log_t) (void *data,
+ * const char *file,
+ * int line,
+ * const char *func,
+ * const char *subs,
+ * unsigned int sev,
+ * const char *format,
+ * va_list args);
+ *
+ * And then the user can supply such a function when creating a new context
+ * object of your library or simply supply NULL. Internally, you have a field of
+ * type "MYPREFIX_log_t llog" in your main structure. If you pass this to the
+ * convenience helpers like llog_dbg(), llog_warn() etc. it will automatically
+ * use the "llog" field to print the message. If it is NULL, nothing is done.
+ *
+ * The arguments of the log-function are defined as:
+ * data: User-supplied data field that is passed straight through.
+ * file: Zero terminated string of the file-name where the log-message
+ * occurred. Can be NULL.
+ * line: Line number of @file where the message occurred. Set to 0 or smaller
+ * if not available.
+ * func: Function name where the log-message occurred. Can be NULL.
+ * subs: Subsystem where the message occurred (zero terminated). Can be NULL.
+ * sev: Severity of log-message. An integer between 0 and 7 as defined below.
+ * These are identical to the linux-kernel severities so there is no need
+ * to include these in your public API. Every app can define them
+ * themselves, if they need it.
+ * format: Format string. Must not be NULL.
+ * args: Argument array
+ *
+ * The user should also be able to optionally provide a data field which is
+ * always passed unmodified as first parameter to the log-function. This allows
+ * to add context to the logger.
+ */
+
+#ifndef SHL_LLOG_H
+#define SHL_LLOG_H
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+enum llog_severity {
+ LLOG_FATAL = 0,
+ LLOG_ALERT = 1,
+ LLOG_CRITICAL = 2,
+ LLOG_ERROR = 3,
+ LLOG_WARNING = 4,
+ LLOG_NOTICE = 5,
+ LLOG_INFO = 6,
+ LLOG_DEBUG = 7,
+ LLOG_SEV_NUM,
+};
+
+typedef void (*llog_submit_t) (void *data,
+ const char *file,
+ int line,
+ const char *func,
+ const char *subs,
+ unsigned int sev,
+ const char *format,
+ va_list args);
+
+static inline __attribute__((format(printf, 8, 9)))
+void llog_format(llog_submit_t llog,
+ void *data,
+ const char *file,
+ int line,
+ const char *func,
+ const char *subs,
+ unsigned int sev,
+ const char *format,
+ ...)
+{
+ int saved_errno = errno;
+ va_list list;
+
+ if (llog) {
+ va_start(list, format);
+ errno = saved_errno;
+ llog(data, file, line, func, subs, sev, format, list);
+ va_end(list);
+ }
+}
+
+#ifndef LLOG_SUBSYSTEM
+static const char *LLOG_SUBSYSTEM __attribute__((__unused__));
+#endif
+
+#define LLOG_DEFAULT __FILE__, __LINE__, __func__, LLOG_SUBSYSTEM
+
+#define llog_printf(obj, sev, format, ...) \
+ llog_format((obj)->llog, \
+ (obj)->llog_data, \
+ LLOG_DEFAULT, \
+ (sev), \
+ (format), \
+ ##__VA_ARGS__)
+#define llog_dprintf(obj, data, sev, format, ...) \
+ llog_format((obj), \
+ (data), \
+ LLOG_DEFAULT, \
+ (sev), \
+ (format), \
+ ##__VA_ARGS__)
+
+static inline __attribute__((format(printf, 4, 5)))
+void llog_dummyf(llog_submit_t llog, void *data, unsigned int sev,
+ const char *format, ...)
+{
+}
+
+/*
+ * Helpers
+ * They pick up all the default values and submit the message to the
+ * llog-subsystem. The llog_debug() function will discard the message unless
+ * BUILD_ENABLE_DEBUG is defined.
+ */
+
+#ifdef BUILD_ENABLE_DEBUG
+ #define llog_ddebug(obj, data, format, ...) \
+ llog_dprintf((obj), (data), LLOG_DEBUG, (format), ##__VA_ARGS__)
+ #define llog_debug(obj, format, ...) \
+ llog_ddebug((obj)->llog, (obj)->llog_data, (format), ##__VA_ARGS__)
+#else
+ #define llog_ddebug(obj, data, format, ...) \
+ llog_dummyf((obj), (data), LLOG_DEBUG, (format), ##__VA_ARGS__)
+ #define llog_debug(obj, format, ...) \
+ llog_ddebug((obj)->llog, (obj)->llog_data, (format), ##__VA_ARGS__)
+#endif
+
+#define llog_info(obj, format, ...) \
+ llog_printf((obj), LLOG_INFO, (format), ##__VA_ARGS__)
+#define llog_dinfo(obj, data, format, ...) \
+ llog_dprintf((obj), (data), LLOG_INFO, (format), ##__VA_ARGS__)
+#define llog_notice(obj, format, ...) \
+ llog_printf((obj), LLOG_NOTICE, (format), ##__VA_ARGS__)
+#define llog_dnotice(obj, data, format, ...) \
+ llog_dprintf((obj), (data), LLOG_NOTICE, (format), ##__VA_ARGS__)
+#define llog_warning(obj, format, ...) \
+ llog_printf((obj), LLOG_WARNING, (format), ##__VA_ARGS__)
+#define llog_dwarning(obj, data, format, ...) \
+ llog_dprintf((obj), (data), LLOG_WARNING, (format), ##__VA_ARGS__)
+#define llog_error(obj, format, ...) \
+ llog_printf((obj), LLOG_ERROR, (format), ##__VA_ARGS__)
+#define llog_derror(obj, data, format, ...) \
+ llog_dprintf((obj), (data), LLOG_ERROR, (format), ##__VA_ARGS__)
+#define llog_critical(obj, format, ...) \
+ llog_printf((obj), LLOG_CRITICAL, (format), ##__VA_ARGS__)
+#define llog_dcritical(obj, data, format, ...) \
+ llog_dprintf((obj), (data), LLOG_CRITICAL, (format), ##__VA_ARGS__)
+#define llog_alert(obj, format, ...) \
+ llog_printf((obj), LLOG_ALERT, (format), ##__VA_ARGS__)
+#define llog_dalert(obj, data, format, ...) \
+ llog_dprintf((obj), (data), LLOG_ALERT, (format), ##__VA_ARGS__)
+#define llog_fatal(obj, format, ...) \
+ llog_printf((obj), LLOG_FATAL, (format), ##__VA_ARGS__)
+#define llog_dfatal(obj, data, format, ...) \
+ llog_dprintf((obj), (data), LLOG_FATAL, (format), ##__VA_ARGS__)
+
+/*
+ * Default log messages
+ * These macros can be used to produce default log messages. You can use them
+ * directly in an "return" statement. The "v" variants automatically cast the
+ * result to void so it can be used in return statements inside of void
+ * functions. The "d" variants use the logging object directly as the parent
+ * might not exist, yet.
+ *
+ * Most of the messages work only if debugging is enabled. This is, because they
+ * are used in debug paths and would slow down normal applications.
+ */
+
+#define llog_dEINVAL(obj, data) \
+ (llog_derror((obj), (data), "invalid arguments"), -EINVAL)
+#define llog_EINVAL(obj) \
+ (llog_dEINVAL((obj)->llog, (obj)->llog_data))
+#define llog_vEINVAL(obj) \
+ ((void)llog_EINVAL(obj))
+#define llog_vdEINVAL(obj, data) \
+ ((void)llog_dEINVAL((obj), (data)))
+
+#define llog_dEFAULT(obj, data) \
+ (llog_derror((obj), (data), "internal operation failed"), -EFAULT)
+#define llog_EFAULT(obj) \
+ (llog_dEFAULT((obj)->llog, (obj)->llog_data))
+#define llog_vEFAULT(obj) \
+ ((void)llog_EFAULT(obj))
+#define llog_vdEFAULT(obj, data) \
+ ((void)llog_dEFAULT((obj), (data)))
+
+#define llog_dENOMEM(obj, data) \
+ (llog_derror((obj), (data), "out of memory"), -ENOMEM)
+#define llog_ENOMEM(obj) \
+ (llog_dENOMEM((obj)->llog, (obj)->llog_data))
+#define llog_vENOMEM(obj) \
+ ((void)llog_ENOMEM(obj))
+#define llog_vdENOMEM(obj, data) \
+ ((void)llog_dENOMEM((obj), (data)))
+
+#define llog_dEPIPE(obj, data) \
+ (llog_derror((obj), (data), "fd closed unexpectedly"), -EPIPE)
+#define llog_EPIPE(obj) \
+ (llog_dEPIPE((obj)->llog, (obj)->llog_data))
+#define llog_vEPIPE(obj) \
+ ((void)llog_EPIPE(obj))
+#define llog_vdEPIPE(obj, data) \
+ ((void)llog_dEPIPE((obj), (data)))
+
+#define llog_dERRNO(obj, data) \
+ (llog_derror((obj), (data), "syscall failed (%d): %m", errno), -errno)
+#define llog_ERRNO(obj) \
+ (llog_dERRNO((obj)->llog, (obj)->llog_data))
+#define llog_vERRNO(obj) \
+ ((void)llog_ERRNO(obj))
+#define llog_vdERRNO(obj, data) \
+ ((void)llog_dERRNO((obj), (data)))
+
+#define llog_dERR(obj, data, _r) \
+ (errno = -(_r), llog_derror((obj), (data), "syscall failed (%d): %m", (_r)), (_r))
+#define llog_ERR(obj, _r) \
+ (llog_dERR((obj)->llog, (obj)->llog_data, (_r)))
+#define llog_vERR(obj, _r) \
+ ((void)llog_ERR((obj), (_r)))
+#define llog_vdERR(obj, data, _r) \
+ ((void)llog_dERR((obj), (data), (_r)))
+
+#endif /* SHL_LLOG_H */
diff --git a/src/shl_macro.h b/src/shl_macro.h
new file mode 100644
index 0000000..ae855df
--- /dev/null
+++ b/src/shl_macro.h
@@ -0,0 +1,219 @@
+/*
+ * SHL - Macros
+ *
+ * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
+ * Dedicated to the Public Domain
+ */
+
+/*
+ * Macros
+ */
+
+#ifndef SHL_MACRO_H
+#define SHL_MACRO_H
+
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* sanity checks required for some macros */
+#if __SIZEOF_POINTER__ != 4 && __SIZEOF_POINTER__ != 8
+#error "Pointer size is neither 4 nor 8 bytes"
+#endif
+
+/* gcc attributes; look them up for more information */
+#define _shl_printf_(_a, _b) __attribute__((__format__(printf, _a, _b)))
+#define _shl_alloc_(...) __attribute__((__alloc_size__(__VA_ARGS__)))
+#define _shl_sentinel_ __attribute__((__sentinel__))
+#define _shl_noreturn_ __attribute__((__noreturn__))
+#define _shl_unused_ __attribute__((__unused__))
+#define _shl_pure_ __attribute__((__pure__))
+#define _shl_const_ __attribute__((__const__))
+#define _shl_deprecated_ __attribute__((__deprecated__))
+#define _shl_packed_ __attribute__((__packed__))
+#define _shl_malloc_ __attribute__((__malloc__))
+#define _shl_weak_ __attribute__((__weak__))
+#define _shl_likely_(_val) (__builtin_expect(!!(_val), 1))
+#define _shl_unlikely_(_val) (__builtin_expect(!!(_val), 0))
+#define _shl_public_ __attribute__((__visibility__("default")))
+#define _shl_hidden_ __attribute__((__visibility__("hidden")))
+#define _shl_weakref_(_val) __attribute__((__weakref__(#_val)))
+#define _shl_cleanup_(_val) __attribute__((__cleanup__(_val)))
+
+/* 2-level stringify helper */
+#define SHL__STRINGIFY(_val) #_val
+#define SHL_STRINGIFY(_val) SHL__STRINGIFY(_val)
+
+/* 2-level concatenate helper */
+#define SHL__CONCATENATE(_a, _b) _a ## _b
+#define SHL_CONCATENATE(_a, _b) SHL__CONCATENATE(_a, _b)
+
+/* unique identifier with prefix */
+#define SHL_UNIQUE(_prefix) SHL_CONCATENATE(_prefix, __COUNTER__)
+
+/* array element count */
+#define SHL_ARRAY_LENGTH(_array) (sizeof(_array)/sizeof(*(_array)))
+
+/* get parent pointer by container-type, member and member-pointer */
+#define shl_container_of(_ptr, _type, _member) \
+ ({ \
+ const typeof( ((_type *)0)->_member ) *__mptr = (_ptr); \
+ (_type *)( (char *)__mptr - offsetof(_type, _member) ); \
+ })
+
+/* return maximum of two values and do strict type checking */
+#define shl_max(_a, _b) \
+ ({ \
+ typeof(_a) __a = (_a); \
+ typeof(_b) __b = (_b); \
+ (void) (&__a == &__b); \
+ __a > __b ? __a : __b; \
+ })
+
+/* same as shl_max() but perform explicit cast beforehand */
+#define shl_max_t(_type, _a, _b) \
+ ({ \
+ _type __a = (_type)(_a); \
+ _type __b = (_type)(_b); \
+ __a > __b ? __a : __b; \
+ })
+
+/* return minimum of two values and do strict type checking */
+#define shl_min(_a, _b) \
+ ({ \
+ typeof(_a) __a = (_a); \
+ typeof(_b) __b = (_b); \
+ (void) (&__a == &__b); \
+ __a < __b ? __a : __b; \
+ })
+
+/* same as shl_min() but perform explicit cast beforehand */
+#define shl_min_t(_type, _a, _b) \
+ ({ \
+ _type __a = (_type)(_a); \
+ _type __b = (_type)(_b); \
+ __a < __b ? __a : __b; \
+ })
+
+/* clamp value between low and high barriers */
+#define shl_clamp(_val, _low, _high) \
+ ({ \
+ typeof(_val) __v = (_val); \
+ typeof(_low) __l = (_low); \
+ typeof(_high) __h = (_high); \
+ (void) (&__v == &__l); \
+ (void) (&__v == &__h); \
+ ((__v > __h) ? __h : ((__v < __l) ? __l : __v)); \
+ })
+
+/* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */
+static inline size_t SHL_ALIGN_POWER2(size_t u)
+{
+ return 1ULL << ((sizeof(u) * 8ULL) - __builtin_clzll(u - 1ULL));
+}
+
+/* zero memory or type */
+#define shl_memzero(_ptr, _size) (memset((_ptr), 0, (_size)))
+#define shl_zero(_ptr) (shl_memzero(&(_ptr), sizeof(_ptr)))
+
+/* ptr <=> uint casts */
+#define SHL_PTR_TO_TYPE(_type, _ptr) ((_type)((uintptr_t)(_ptr)))
+#define SHL_TYPE_TO_PTR(_type, _int) ((void*)((uintptr_t)(_int)))
+#define SHL_PTR_TO_INT(_ptr) SHL_PTR_TO_TYPE(int, (_ptr))
+#define SHL_INT_TO_PTR(_ptr) SHL_TYPE_TO_PTR(int, (_ptr))
+#define SHL_PTR_TO_UINT(_ptr) SHL_PTR_TO_TYPE(unsigned int, (_ptr))
+#define SHL_UINT_TO_PTR(_ptr) SHL_TYPE_TO_PTR(unsigned int, (_ptr))
+#define SHL_PTR_TO_LONG(_ptr) SHL_PTR_TO_TYPE(long, (_ptr))
+#define SHL_LONG_TO_PTR(_ptr) SHL_TYPE_TO_PTR(long, (_ptr))
+#define SHL_PTR_TO_ULONG(_ptr) SHL_PTR_TO_TYPE(unsigned long, (_ptr))
+#define SHL_ULONG_TO_PTR(_ptr) SHL_TYPE_TO_PTR(unsigned long, (_ptr))
+#define SHL_PTR_TO_S32(_ptr) SHL_PTR_TO_TYPE(int32_t, (_ptr))
+#define SHL_S32_TO_PTR(_ptr) SHL_TYPE_TO_PTR(int32_t, (_ptr))
+#define SHL_PTR_TO_U32(_ptr) SHL_PTR_TO_TYPE(uint32_t, (_ptr))
+#define SHL_U32_TO_PTR(_ptr) SHL_TYPE_TO_PTR(uint32_t, (_ptr))
+#define SHL_PTR_TO_S64(_ptr) SHL_PTR_TO_TYPE(int64_t, (_ptr))
+#define SHL_S64_TO_PTR(_ptr) SHL_TYPE_TO_PTR(int64_t, (_ptr))
+#define SHL_PTR_TO_U64(_ptr) SHL_PTR_TO_TYPE(uint64_t, (_ptr))
+#define SHL_U64_TO_PTR(_ptr) SHL_TYPE_TO_PTR(uint64_t, (_ptr))
+
+/* compile-time assertions */
+#define shl_assert_cc(_expr) static_assert(_expr, #_expr)
+
+/*
+ * Safe Multiplications
+ * Multiplications are subject to overflows. These helpers guarantee that the
+ * multiplication can be done safely and return -ERANGE if not.
+ *
+ * Note: This is horribly slow for ull/uint64_t as we need a division to test
+ * for overflows. Take that into account when using these. For smaller integers,
+ * we can simply use an upcast-multiplication which gcc should be smart enough
+ * to optimize.
+ */
+
+#define SHL__REAL_MULT(_max, _val, _factor) \
+ ({ \
+ (_factor == 0 || *(_val) <= (_max) / (_factor)) ? \
+ ((*(_val) *= (_factor)), 0) : \
+ -ERANGE; \
+ })
+
+#define SHL__UPCAST_MULT(_type, _max, _val, _factor) \
+ ({ \
+ _type v = *(_val) * (_type)(_factor); \
+ (v <= (_max)) ? \
+ ((*(_val) = v), 0) : \
+ -ERANGE; \
+ })
+
+static inline int shl_mult_ull(unsigned long long *val,
+ unsigned long long factor)
+{
+ return SHL__REAL_MULT(ULLONG_MAX, val, factor);
+}
+
+static inline int shl_mult_ul(unsigned long *val, unsigned long factor)
+{
+#if ULONG_MAX < ULLONG_MAX
+ return SHL__UPCAST_MULT(unsigned long long, ULONG_MAX, val, factor);
+#else
+ shl_assert_cc(sizeof(unsigned long) == sizeof(unsigned long long));
+ return shl_mult_ull((unsigned long long*)val, factor);
+#endif
+}
+
+static inline int shl_mult_u(unsigned int *val, unsigned int factor)
+{
+#if UINT_MAX < ULONG_MAX
+ return SHL__UPCAST_MULT(unsigned long, UINT_MAX, val, factor);
+#elif UINT_MAX < ULLONG_MAX
+ return SHL__UPCAST_MULT(unsigned long long, UINT_MAX, val, factor);
+#else
+ shl_assert_cc(sizeof(unsigned int) == sizeof(unsigned long long));
+ return shl_mult_ull(val, factor);
+#endif
+}
+
+static inline int shl_mult_u64(uint64_t *val, uint64_t factor)
+{
+ return SHL__REAL_MULT(UINT64_MAX, val, factor);
+}
+
+static inline int shl_mult_u32(uint32_t *val, uint32_t factor)
+{
+ return SHL__UPCAST_MULT(uint_fast64_t, UINT32_MAX, val, factor);
+}
+
+static inline int shl_mult_u16(uint16_t *val, uint16_t factor)
+{
+ return SHL__UPCAST_MULT(uint_fast32_t, UINT16_MAX, val, factor);
+}
+
+static inline int shl_mult_u8(uint8_t *val, uint8_t factor)
+{
+ return SHL__UPCAST_MULT(uint_fast16_t, UINT8_MAX, val, factor);
+}
+
+#endif /* SHL_MACRO_H */
diff --git a/src/shl_ring.c b/src/shl_ring.c
new file mode 100644
index 0000000..a703fa8
--- /dev/null
+++ b/src/shl_ring.c
@@ -0,0 +1,188 @@
+/*
+ * SHL - Ring buffer
+ *
+ * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
+ * Dedicated to the Public Domain
+ */
+
+/*
+ * Ring buffer
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include "shl_macro.h"
+#include "shl_ring.h"
+
+#define RING_MASK(_r, _v) ((_v) & ((_r)->size - 1))
+
+void shl_ring_flush(struct shl_ring *r)
+{
+ r->start = 0;
+ r->used = 0;
+}
+
+void shl_ring_clear(struct shl_ring *r)
+{
+ free(r->buf);
+ memset(r, 0, sizeof(*r));
+}
+
+/*
+ * Get data pointers for current ring-buffer data. @vec must be an array of 2
+ * iovec objects. They are filled according to the data available in the
+ * ring-buffer. 0, 1 or 2 is returned according to the number of iovec objects
+ * that were filled (0 meaning buffer is empty).
+ *
+ * Hint: "struct iovec" is defined in <sys/uio.h> and looks like this:
+ * struct iovec {
+ * void *iov_base;
+ * size_t iov_len;
+ * };
+ */
+size_t shl_ring_peek(struct shl_ring *r, struct iovec *vec)
+{
+ if (r->used == 0) {
+ return 0;
+ } else if (r->start + r->used <= r->size) {
+ if (vec) {
+ vec[0].iov_base = &r->buf[r->start];
+ vec[0].iov_len = r->used;
+ }
+ return 1;
+ } else {
+ if (vec) {
+ vec[0].iov_base = &r->buf[r->start];
+ vec[0].iov_len = r->size - r->start;
+ vec[1].iov_base = r->buf;
+ vec[1].iov_len = r->used - (r->size - r->start);
+ }
+ return 2;
+ }
+}
+
+/*
+ * Copy data from the ring buffer into the linear external buffer @buf. Copy
+ * at most @size bytes. If the ring buffer size is smaller, copy less bytes and
+ * return the number of bytes copied.
+ */
+size_t shl_ring_copy(struct shl_ring *r, void *buf, size_t size)
+{
+ size_t l;
+
+ if (size > r->used)
+ size = r->used;
+
+ if (size > 0) {
+ l = r->size - r->start;
+ if (size <= l) {
+ memcpy(buf, &r->buf[r->start], size);
+ } else {
+ memcpy(buf, &r->buf[r->start], l);
+ memcpy((uint8_t*)buf + l, r->buf, size - l);
+ }
+ }
+
+ return size;
+}
+
+/*
+ * Resize ring-buffer to size @nsize. @nsize must be a power-of-2, otherwise
+ * ring operations will behave incorrectly.
+ */
+static int ring_resize(struct shl_ring *r, size_t nsize)
+{
+ uint8_t *buf;
+ size_t l;
+
+ buf = malloc(nsize);
+ if (!buf)
+ return -ENOMEM;
+
+ if (r->used > 0) {
+ l = r->size - r->start;
+ if (r->used <= l) {
+ memcpy(buf, &r->buf[r->start], r->used);
+ } else {
+ memcpy(buf, &r->buf[r->start], l);
+ memcpy(&buf[l], r->buf, r->used - l);
+ }
+ }
+
+ free(r->buf);
+ r->buf = buf;
+ r->size = nsize;
+ r->start = 0;
+
+ return 0;
+}
+
+/*
+ * Resize ring-buffer to provide enough room for @add bytes of new data. This
+ * resizes the buffer if it is too small. It returns -ENOMEM on OOM and 0 on
+ * success.
+ */
+static int ring_grow(struct shl_ring *r, size_t add)
+{
+ size_t need;
+
+ if (r->size - r->used >= add)
+ return 0;
+
+ need = r->used + add;
+ if (need <= r->used)
+ return -ENOMEM;
+ else if (need < 4096)
+ need = 4096;
+
+ need = SHL_ALIGN_POWER2(need);
+ if (need == 0)
+ return -ENOMEM;
+
+ return ring_resize(r, need);
+}
+
+/*
+ * Push @len bytes from @u8 into the ring buffer. The buffer is resized if it
+ * is too small. -ENOMEM is returned on OOM, 0 on success.
+ */
+int shl_ring_push(struct shl_ring *r, const void *u8, size_t size)
+{
+ int err;
+ size_t pos, l;
+
+ if (size == 0)
+ return 0;
+
+ err = ring_grow(r, size);
+ if (err < 0)
+ return err;
+
+ pos = RING_MASK(r, r->start + r->used);
+ l = r->size - pos;
+ if (l >= size) {
+ memcpy(&r->buf[pos], u8, size);
+ } else {
+ memcpy(&r->buf[pos], u8, l);
+ memcpy(r->buf, (const uint8_t*)u8 + l, size - l);
+ }
+
+ r->used += size;
+
+ return 0;
+}
+
+/*
+ * Remove @len bytes from the start of the ring-buffer. Note that we protect
+ * against overflows so removing more bytes than available is safe.
+ */
+void shl_ring_pull(struct shl_ring *r, size_t size)
+{
+ if (size > r->used)
+ size = r->used;
+
+ r->start = RING_MASK(r, r->start + size);
+ r->used -= size;
+}
diff --git a/src/shl_ring.h b/src/shl_ring.h
new file mode 100644
index 0000000..7be48f6
--- /dev/null
+++ b/src/shl_ring.h
@@ -0,0 +1,52 @@
+/*
+ * SHL - Ring buffer
+ *
+ * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
+ * Dedicated to the Public Domain
+ */
+
+/*
+ * Ring buffer
+ */
+
+#ifndef SHL_RING_H
+#define SHL_RING_H
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+
+struct shl_ring {
+ uint8_t *buf; /* buffer or NULL */
+ size_t size; /* actual size of @buf */
+ size_t start; /* start position of ring */
+ size_t used; /* number of actually used bytes */
+};
+
+/* flush buffer so it is empty again */
+void shl_ring_flush(struct shl_ring *r);
+
+/* flush buffer, free allocated data and reset to initial state */
+void shl_ring_clear(struct shl_ring *r);
+
+/* get pointers to buffer data and their length */
+size_t shl_ring_peek(struct shl_ring *r, struct iovec *vec);
+
+/* copy data into external linear buffer */
+size_t shl_ring_copy(struct shl_ring *r, void *buf, size_t size);
+
+/* push data to the end of the buffer */
+int shl_ring_push(struct shl_ring *r, const void *u8, size_t size);
+
+/* pull data from the front of the buffer */
+void shl_ring_pull(struct shl_ring *r, size_t size);
+
+/* return size of occupied buffer in bytes */
+static inline size_t shl_ring_get_size(struct shl_ring *r)
+{
+ return r->used;
+}
+
+#endif /* SHL_RING_H */
diff --git a/src/shl_util.c b/src/shl_util.c
new file mode 100644
index 0000000..ea4f160
--- /dev/null
+++ b/src/shl_util.c
@@ -0,0 +1,260 @@
+/*
+ * SHL - Utility Helpers
+ *
+ * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
+ * Dedicated to the Public Domain
+ */
+
+/*
+ * Utility Helpers
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include "shl_macro.h"
+#include "shl_util.h"
+
+/*
+ * Strict atoi()
+ * These helpers implement a strict version of atoi() (or strtol()). They only
+ * parse digit/alpha characters. No whitespace or other characters are parsed.
+ * The unsigned-variants explicitly forbid leading +/- signs. Use the signed
+ * variants to allow these.
+ * Base-prefix parsing is only done if base=0 is requested. Otherwise,
+ * base-prefixes are forbidden.
+ * The input string must be ASCII compatbile (which includes UTF8).
+ *
+ * We also always check for overflows and return errors (but continue parsing!)
+ * so callers can catch it correctly.
+ *
+ * Additionally, we allow "length" parameters so strings do not necessarily have
+ * to be zero-terminated. We have wrappers which skip this by passing strlen().
+ */
+
+int shl_ctoi(char ch, unsigned int base)
+{
+ unsigned int v;
+
+ switch (ch) {
+ case '0'...'9':
+ v = ch - '0';
+ break;
+ case 'a'...'z':
+ v = ch - 'a' + 10;
+ break;
+ case 'A'...'Z':
+ v = ch - 'A' + 10;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (v >= base)
+ return -EINVAL;
+
+ return v;
+}
+
+/* figure out base and skip prefix */
+static unsigned int shl__skip_base(const char **str, size_t *len)
+{
+ if (*len > 1) {
+ if ((*str)[0] == '0') {
+ if (shl_ctoi((*str)[1], 8) >= 0) {
+ *str += 1;
+ *len -= 1;
+ return 8;
+ }
+ }
+ }
+
+ if (*len > 2) {
+ if ((*str)[0] == '0' && (*str)[1] == 'x') {
+ if (shl_ctoi((*str)[2], 16) >= 0) {
+ *str += 2;
+ *len -= 2;
+ return 16;
+ }
+ }
+ }
+
+ return 10;
+}
+
+int shl_atoi_ulln(const char *str,
+ size_t len,
+ unsigned int base,
+ const char **next,
+ unsigned long long *out)
+{
+ bool huge;
+ uint32_t val1;
+ unsigned long long val2;
+ size_t pos;
+ int r, c;
+
+ /* We use u32 as storage first so we have fast mult-overflow checks. We
+ * cast up to "unsigned long long" once we exceed UINT32_MAX. Overflow
+ * checks will get pretty slow for non-power2 bases, though. */
+
+ huge = false;
+ val1 = 0;
+ val2 = 0;
+ r = 0;
+
+ if (base > 36) {
+ if (next)
+ *next = str;
+ if (out)
+ *out = 0;
+ return -EINVAL;
+ }
+
+ if (base == 0)
+ base = shl__skip_base(&str, &len);
+
+ for (pos = 0; pos < len; ++pos) {
+ c = shl_ctoi(str[pos], base);
+ if (c < 0)
+ break;
+
+ /* skip calculations on error */
+ if (r < 0)
+ continue;
+
+ if (!huge) {
+ val2 = val1;
+ r = shl_mult_u32(&val1, base);
+ if (r >= 0 && val1 + c >= val1)
+ val1 += c;
+ else
+ huge = true;
+ }
+
+ if (huge) {
+ r = shl_mult_ull(&val2, base);
+ if (r >= 0 && val2 + c >= val2)
+ val2 += c;
+ }
+ }
+
+ if (next)
+ *next = (char*)&str[pos];
+ if (out) {
+ if (r < 0)
+ *out = ULLONG_MAX;
+ else if (huge)
+ *out = val2;
+ else
+ *out = val1;
+ }
+
+ return r;
+}
+
+int shl_atoi_uln(const char *str,
+ size_t len,
+ unsigned int base,
+ const char **next,
+ unsigned long *out)
+{
+ unsigned long long val;
+ int r;
+
+ r = shl_atoi_ulln(str, len, base, next, &val);
+ if (r >= 0 && val > ULONG_MAX)
+ r = -ERANGE;
+
+ if (out)
+ *out = shl_min(val, (unsigned long long)ULONG_MAX);
+
+ return r;
+}
+
+int shl_atoi_un(const char *str,
+ size_t len,
+ unsigned int base,
+ const char **next,
+ unsigned int *out)
+{
+ unsigned long long val;
+ int r;
+
+ r = shl_atoi_ulln(str, len, base, next, &val);
+ if (r >= 0 && val > UINT_MAX)
+ r = -ERANGE;
+
+ if (out)
+ *out = shl_min(val, (unsigned long long)UINT_MAX);
+
+ return r;
+}
+
+int shl_atoi_zn(const char *str,
+ size_t len,
+ unsigned int base,
+ const char **next,
+ size_t *out)
+{
+ unsigned long long val;
+ int r;
+
+ r = shl_atoi_ulln(str, len, base, next, &val);
+ if (r >= 0 && val > SIZE_MAX)
+ r = -ERANGE;
+
+ if (out)
+ *out = shl_min(val, (unsigned long long)SIZE_MAX);
+
+ return r;
+}
+
+/*
+ * Greedy Realloc
+ * The greedy-realloc helpers simplify power-of-2 buffer allocations. If you
+ * have a dynamic array, simply use shl_greedy_realloc() for re-allocations
+ * and it makes sure your buffer-size is always a multiple of 2 and is big
+ * enough for your new entries.
+ * Default size is 64, but you can initialize your buffer to a bigger default
+ * if you need.
+ */
+
+void *shl_greedy_realloc(void **mem, size_t *size, size_t need)
+{
+ size_t nsize;
+ void *p;
+
+ if (*size >= need)
+ return *mem;
+
+ nsize = SHL_ALIGN_POWER2(shl_max_t(size_t, 64U, need));
+ if (nsize == 0)
+ return NULL;
+
+ p = realloc(*mem, nsize);
+ if (!p)
+ return NULL;
+
+ *mem = p;
+ *size = nsize;
+ return p;
+}
+
+void *shl_greedy_realloc0(void **mem, size_t *size, size_t need)
+{
+ size_t prev = *size;
+ uint8_t *p;
+
+ p = shl_greedy_realloc(mem, size, need);
+ if (!p)
+ return NULL;
+
+ if (*size > prev)
+ shl_memzero(&p[prev], *size - prev);
+
+ return p;
+}
diff --git a/src/shl_util.h b/src/shl_util.h
new file mode 100644
index 0000000..a2426bc
--- /dev/null
+++ b/src/shl_util.h
@@ -0,0 +1,83 @@
+/*
+ * SHL - Utility Helpers
+ *
+ * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
+ * Dedicated to the Public Domain
+ */
+
+/*
+ * Utility Helpers
+ */
+
+#ifndef SHL_UTIL_H
+#define SHL_UTIL_H
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* strict atoi */
+
+int shl_ctoi(char ch, unsigned int base);
+
+int shl_atoi_ulln(const char *str,
+ size_t len,
+ unsigned int base,
+ const char **next,
+ unsigned long long *out);
+int shl_atoi_uln(const char *str,
+ size_t len,
+ unsigned int base,
+ const char **next,
+ unsigned long *out);
+int shl_atoi_un(const char *str,
+ size_t len,
+ unsigned int base,
+ const char **next,
+ unsigned int *out);
+int shl_atoi_zn(const char *str,
+ size_t len,
+ unsigned int base,
+ const char **next,
+ size_t *out);
+
+static inline int shl_atoi_ull(const char *str,
+ unsigned int base,
+ const char **next,
+ unsigned long long *out)
+{
+ return shl_atoi_ulln(str, strlen(str), base, next, out);
+}
+
+static inline int shl_atoi_ul(const char *str,
+ unsigned int base,
+ const char **next,
+ unsigned long *out)
+{
+ return shl_atoi_uln(str, strlen(str), base, next, out);
+}
+
+static inline int shl_atoi_u(const char *str,
+ unsigned int base,
+ const char **next,
+ unsigned int *out)
+{
+ return shl_atoi_un(str, strlen(str), base, next, out);
+}
+
+static inline int shl_atoi_z(const char *str,
+ unsigned int base,
+ const char **next,
+ size_t *out)
+{
+ return shl_atoi_zn(str, strlen(str), base, next, out);
+}
+
+/* greedy alloc */
+
+void *shl_greedy_realloc(void **mem, size_t *size, size_t need);
+void *shl_greedy_realloc0(void **mem, size_t *size, size_t need);
+
+#endif /* SHL_UTIL_H */
diff --git a/test/test_common.h b/test/test_common.h
new file mode 100644
index 0000000..bd12647
--- /dev/null
+++ b/test/test_common.h
@@ -0,0 +1,109 @@
+/*
+ * libwfd - Wifi-Display/Miracast Protocol Implementation
+ *
+ * Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Test Helper
+ * This header includes all kinds of helpers for testing. It tries to include
+ * everything required and provides simple macros to avoid duplicating code in
+ * each test. We try to keep tests as small as possible and move everything that
+ * might be common here.
+ *
+ * We avoid sticking to our usual coding conventions (including headers in
+ * source files, etc. ..) and instead make this the most convenient we can.
+ */
+
+#ifndef TEST_COMMON_H
+#define TEST_COMMON_H
+
+#include <errno.h>
+#include <check.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "libwfd.h"
+#include "shl_macro.h"
+#include "shl_util.h"
+
+/* lower address-space is protected from user-allocation, so this is invalid */
+#define TEST_INVALID_PTR ((void*)0x10)
+
+#define TEST_DEFINE_CASE(_name) \
+ static TCase *test_create_case_##_name(void) \
+ { \
+ TCase *tc; \
+ \
+ tc = tcase_create(#_name); \
+
+#define TEST(_name) tcase_add_test(tc, _name);
+
+#define TEST_END_CASE \
+ return tc; \
+ } \
+
+#define TEST_END NULL
+
+#define TEST_CASE(_name) test_create_case_##_name
+
+static inline Suite *test_create_suite(const char *name, ...)
+{
+ Suite *s;
+ va_list list;
+ TCase *(*fn)(void);
+
+ s = suite_create(name);
+
+ va_start(list, name);
+ while ((fn = va_arg(list, TCase *(*)(void))))
+ suite_add_tcase(s, fn());
+ va_end(list);
+
+ return s;
+}
+
+#define TEST_SUITE(_name, ...) test_create_suite((#_name), ##__VA_ARGS__)
+
+static inline int test_run_suite(Suite *s)
+{
+ int ret;
+ SRunner *sr;
+
+ sr = srunner_create(s);
+ srunner_run_all(sr, CK_NORMAL);
+ ret = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return ret;
+}
+
+#define TEST_DEFINE(_suite) \
+ int main(int argc, char **argv) \
+ { \
+ return test_run_suite(_suite); \
+ }
+
+#endif /* TEST_COMMON_H */
diff --git a/test/test_rtsp.c b/test/test_rtsp.c
new file mode 100644
index 0000000..2120918
--- /dev/null
+++ b/test/test_rtsp.c
@@ -0,0 +1,558 @@
+/*
+ * libwfd - Wifi-Display/Miracast Protocol Implementation
+ *
+ * Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "test_common.h"
+
+static int received;
+
+struct orig {
+ ssize_t len;
+ const char *str;
+};
+
+struct expect {
+ size_t times;
+ struct wfd_rtsp_msg msg;
+};
+
+static const struct orig orig[] = {
+ { -1, "SOMETHING\r\n\r\n" },
+ { -1, "SOMETHING" },
+ { -1, "\r" },
+ { -1, "\n" },
+ { -1, "\n" },
+ { -1, "SOME" },
+ { -1, "THING" },
+ { -1, "\r" },
+ { -1, "\r" },
+ { -1, "\n" },
+ { -1, "SOME" },
+ { -1, "THING\n" },
+ { -1, "\r" },
+ { -1, "SOMETHING\n\n" },
+ { -1, "SOMETHING\r\r" },
+ { -1, "SOMETHING\r\r\n" },
+ { -1, "SOMETHING\n\r\n" },
+
+ { -1, "OPTIONS * RTSP/1.0\n\r\n" },
+ { -1, "OPTIONS * RTSP/1.0\n\r\n" },
+ { -1, "OPTIONS *\r RTSP/1.0\n\r\n" },
+ { -1, "OPTIONS *\r\n RTSP/1.0\n\r\n" },
+ { -1, "OPTIONS\r *\n RTSP/1.0\n\r\n" },
+ { -1, " \r\n OPTIONS * RTSP/1.0\n\r\n" },
+ { -1, "\rOPTIONS * RTSP/1.0\n\r\n" },
+ { -1, "\nOPTIONS * RTSP/1.0\n\r\n" },
+ { -1, " OPTIONS *\n\t \r\tRTSP/1.0\n\r\n" },
+ { -1, "OPTIONS * RTSP/1.0 \n\r\n" },
+
+ { -1, "RTSP/1.0 200 OK Something\n\n" },
+
+ { 10, "$\001\000\006RAWSTH" },
+ { 10, "$\001\000\006RAWSTH" },
+
+ { -1, "SOMETHING\r\nsome-header:value\r\n\r\n" },
+
+ { -1, "OPTIONS * RTSP/2.1\n" },
+ { -1, "some-header:value\n" },
+ { -1, "some-other-header:buhu\n" },
+ { -1, "\n" },
+ { -1, "OPTIONS * RTSP/2.1\n" },
+ { -1, "some-header:value \n" },
+ { -1, "some-other-header:buhu \r \n \n" },
+ { -1, "\n" },
+
+ { 16, " \n $\001\000\006RAWSTH" },
+ { 11, " \n \r\n$\001\000" },
+ { 7, "\006RAWSTH" },
+
+ { -1, "OPTIONS * RTSP/2.1\n" },
+ { -1, "some-header :value \n" },
+ { -1, "some-other-header: buhu \r \n \n" },
+ { -1, "some-header : value \n" },
+ { -1, "\n" },
+ { -1, "OPTIONS * RTSP/2.1\n" },
+ { -1, "some-header \r \n :value \n" },
+ { -1, "some-other-header: \r\n buhu \r \n \n" },
+ { -1, "some-header \t\t\t:\r\n value \n" },
+ { -1, "\n" },
+
+ { -1, "STH\r\ncontent-length:5\r\n\r\n12345" },
+
+ { -1, "STH\r\ncontent-length:5/suffix\r\n\r\n12345" },
+
+ { -1, "OPTIONS * RTSP/1.0\n" },
+ { -1, "cseq: 100\n" },
+ { -1, "\n" },
+
+ /* leave this at the end to test missing trailing \n */
+ { -1, "SOMETHING\n\r" },
+};
+
+static const struct expect expect[] = {
+ {
+ .times = 8,
+ .msg = {
+ .type = WFD_RTSP_MSG_UNKNOWN,
+ .id = {
+ /* test .length comparison in test-module */
+ .line = "SOMETHING" "-STUPID",
+ .length = 9,
+ },
+ },
+ },
+ {
+ .times = 10,
+ .msg = {
+ .type = WFD_RTSP_MSG_REQUEST,
+ .id = {
+ .line = "OPTIONS * RTSP/1.0",
+ .request = {
+ .method = "OPTIONS",
+ .type = WFD_RTSP_METHOD_OPTIONS,
+ .uri = "*",
+ .major = 1,
+ .minor = 0,
+ },
+ },
+ },
+ },
+ {
+ .times = 1,
+ .msg = {
+ .type = WFD_RTSP_MSG_RESPONSE,
+ .id = {
+ .line = "RTSP/1.0 200 OK Something",
+ .response = {
+ .major = 1,
+ .minor = 0,
+ .status = 200,
+ .phrase = "OK Something",
+ },
+ },
+ },
+ },
+ {
+ .times = 1,
+ .msg = {
+ .type = WFD_RTSP_MSG_UNKNOWN,
+ .id = {
+ .line = "SOMETHING",
+ },
+ .headers = {
+ [WFD_RTSP_HEADER_UNKNOWN] = {
+ .count = 1,
+ .lines = (char*[]){
+ (char*)"some-header:value",
+ },
+ .lengths = (size_t[]){
+ 0,
+ },
+ },
+ },
+ },
+ },
+ {
+ .times = 2,
+ .msg = {
+ .type = WFD_RTSP_MSG_REQUEST,
+ .id = {
+ .line = "OPTIONS * RTSP/2.1",
+ .request = {
+ .method = "OPTIONS",
+ .type = WFD_RTSP_METHOD_OPTIONS,
+ .uri = "*",
+ .major = 2,
+ .minor = 1,
+ },
+ },
+ .headers = {
+ [WFD_RTSP_HEADER_UNKNOWN] = {
+ .count = 2,
+ .lines = (char*[]){
+ (char*)"some-header:value",
+ (char*)"some-other-header:buhu",
+ },
+ .lengths = (size_t[]){
+ 0,
+ 0,
+ },
+ },
+ },
+ },
+ },
+ {
+ .times = 2,
+ .msg = {
+ .type = WFD_RTSP_MSG_REQUEST,
+ .id = {
+ .line = "OPTIONS * RTSP/2.1",
+ .request = {
+ .method = "OPTIONS",
+ .type = WFD_RTSP_METHOD_OPTIONS,
+ .uri = "*",
+ .major = 2,
+ .minor = 1,
+ },
+ },
+ .headers = {
+ [WFD_RTSP_HEADER_UNKNOWN] = {
+ .count = 3,
+ .lines = (char*[]){
+ (char*)"some-header :value",
+ (char*)"some-other-header: buhu",
+ (char*)"some-header : value",
+ },
+ .lengths = (size_t[]){
+ 0,
+ 0,
+ 0,
+ },
+ },
+ },
+ },
+ },
+ {
+ .times = 1,
+ .msg = {
+ .type = WFD_RTSP_MSG_UNKNOWN,
+ .id = {
+ .line = "STH",
+ },
+ .headers = {
+ [WFD_RTSP_HEADER_CONTENT_LENGTH] = {
+ .count = 1,
+ .lines = (char*[]){
+ (char*)"content-length:5",
+ },
+ .lengths = (size_t[]){
+ 0,
+ },
+ },
+ },
+ .entity = {
+ .value = "12345",
+ },
+ },
+ },
+ {
+ .times = 1,
+ .msg = {
+ .type = WFD_RTSP_MSG_UNKNOWN,
+ .id = {
+ .line = "STH",
+ },
+ .headers = {
+ [WFD_RTSP_HEADER_CONTENT_LENGTH] = {
+ .count = 1,
+ .lines = (char*[]){
+ (char*)"content-length:5/suffix",
+ },
+ .lengths = (size_t[]){
+ 0,
+ },
+ },
+ },
+ .entity = {
+ .value = "12345",
+ },
+ },
+ },
+ {
+ .times = 1,
+ .msg = {
+ .type = WFD_RTSP_MSG_REQUEST,
+ .id = {
+ .line = "OPTIONS * RTSP/1.0",
+ .request = {
+ .method = "OPTIONS",
+ .type = WFD_RTSP_METHOD_OPTIONS,
+ .uri = "*",
+ .major = 1,
+ .minor = 0,
+ },
+ },
+ .headers = {
+ [WFD_RTSP_HEADER_CSEQ] = {
+ .count = 1,
+ .lines = (char*[]){
+ (char*)"cseq: 100",
+ },
+ .lengths = (size_t[]){
+ 0,
+ },
+ },
+ },
+ },
+ },
+ {
+ .times = 1,
+ .msg = {
+ .type = WFD_RTSP_MSG_UNKNOWN,
+ .id = {
+ .line = "SOMETHING",
+ },
+ },
+ },
+};
+
+#define LEN(_len, _str) ((_len) <= 0 ? ((_str) ? strlen(_str) : 0) : (_len))
+
+static int test_wfd_rtsp_decoder_event(struct wfd_rtsp_decoder *dec,
+ void *data,
+ struct wfd_rtsp_decoder_event *ev)
+{
+ bool debug = true;
+ static int pos, num;
+ size_t i, j;
+ const struct expect *e;
+ const struct wfd_rtsp_msg *m, *msg;
+ const struct wfd_rtsp_msg_header *h, *hm;
+
+ if (ev->type == WFD_RTSP_DECODER_DATA) {
+ if (debug)
+ fprintf(stderr, "Raw Data (%u:%u): %s\n",
+ (unsigned int)ev->data.channel,
+ (unsigned int)ev->data.size,
+ (char*)ev->data.value);
+ return 0;
+ }
+
+ if (ev->type != WFD_RTSP_DECODER_MSG)
+ return 0;
+
+ msg = ev->msg;
+ ck_assert(data == TEST_INVALID_PTR);
+ ++received;
+
+ ck_assert(pos < SHL_ARRAY_LENGTH(expect));
+ e = &expect[pos];
+ m = &e->msg;
+ if (++num >= e->times) {
+ ++pos;
+ num = 0;
+ }
+
+ /* print msg */
+
+ if (debug) {
+ fprintf(stderr, "Received Message:\n");
+ fprintf(stderr, " type: %u\n", msg->type);
+ fprintf(stderr, " id: len: %zu line: %s\n",
+ msg->id.length, msg->id.line);
+
+ if (msg->type == WFD_RTSP_MSG_REQUEST) {
+ fprintf(stderr, " method (%d): %s %s RTSP/%u.%u\n",
+ msg->id.request.type,
+ msg->id.request.method,
+ msg->id.request.uri,
+ msg->id.request.major,
+ msg->id.request.minor);
+ } else if (msg->type == WFD_RTSP_MSG_RESPONSE) {
+ fprintf(stderr, " RTSP/%u.%u %u %s\n",
+ msg->id.response.major,
+ msg->id.response.minor,
+ msg->id.response.status,
+ msg->id.response.phrase);
+ }
+
+ fprintf(stderr, " headers:\n");
+ for (i = 0; i < SHL_ARRAY_LENGTH(msg->headers); ++i) {
+ h = &msg->headers[i];
+ if (!h->count)
+ continue;
+
+ fprintf(stderr, " id: %zu %s (count: %zu)\n",
+ i, wfd_rtsp_header_get_name(i) ? : "<unknown>",
+ h->count);
+
+ for (j = 0; j < h->count; ++j) {
+ fprintf(stderr, " line (%zu): %s\n",
+ h->lengths[j], h->lines[j]);
+ }
+ }
+
+ if (msg->entity.size)
+ fprintf(stderr, " body (%zu): %s\n",
+ msg->entity.size, (char*)msg->entity.value);
+ }
+
+ /* compare msgs */
+
+ ck_assert_int_eq(m->type, msg->type);
+ ck_assert_int_eq(LEN(m->id.length, m->id.line), msg->id.length);
+ ck_assert(!memcmp(m->id.line, msg->id.line, msg->id.length));
+
+ if (m->type == WFD_RTSP_MSG_REQUEST) {
+ ck_assert(!strcmp(m->id.request.method,
+ msg->id.request.method));
+ ck_assert_int_eq(m->id.request.type,
+ msg->id.request.type);
+ ck_assert(!strcmp(m->id.request.uri,
+ msg->id.request.uri));
+ ck_assert_int_eq(m->id.request.major,
+ msg->id.request.major);
+ ck_assert_int_eq(m->id.request.minor,
+ msg->id.request.minor);
+ } else if (m->type == WFD_RTSP_MSG_RESPONSE) {
+ ck_assert_int_eq(m->id.response.major,
+ msg->id.response.major);
+ ck_assert_int_eq(m->id.response.minor,
+ msg->id.response.minor);
+ ck_assert_int_eq(m->id.response.status,
+ msg->id.response.status);
+ ck_assert(!strcmp(m->id.response.phrase,
+ msg->id.response.phrase));
+ }
+
+ for (i = 0; i < SHL_ARRAY_LENGTH(msg->headers); ++i) {
+ h = &m->headers[i];
+ hm = &msg->headers[i];
+ ck_assert_int_eq(h->count, hm->count);
+
+ for (j = 0; j < h->count; ++j) {
+ ck_assert_int_eq(LEN(h->lengths[j], h->lines[j]),
+ hm->lengths[j]);
+ ck_assert(!memcmp(h->lines[j],
+ hm->lines[j],
+ hm->lengths[j]));
+ }
+ }
+
+ ck_assert_int_eq(LEN(m->entity.size, m->entity.value),
+ msg->entity.size);
+ ck_assert(!memcmp(m->entity.value, msg->entity.value,
+ msg->entity.size));
+
+ return 0;
+}
+
+START_TEST(test_wfd_rtsp_decoder)
+{
+ struct wfd_rtsp_decoder *d;
+ int r, sent = 0;
+ size_t len, num, i;
+
+ r = wfd_rtsp_decoder_new(NULL, NULL, NULL, NULL, &d);
+ ck_assert(r == -EINVAL);
+
+ r = wfd_rtsp_decoder_new(test_wfd_rtsp_decoder_event, NULL, NULL, NULL, &d);
+ ck_assert(r >= 0);
+
+ wfd_rtsp_decoder_set_data(d, TEST_INVALID_PTR);
+ ck_assert(wfd_rtsp_decoder_get_data(d) == TEST_INVALID_PTR);
+ ck_assert(received == sent);
+
+ for (i = 0; i < SHL_ARRAY_LENGTH(orig); ++i) {
+ if (orig[i].len >= 0)
+ len = orig[i].len;
+ else
+ len = strlen(orig[i].str);
+
+ r = wfd_rtsp_decoder_feed(d, orig[i].str, len);
+ ck_assert(r >= 0);
+ }
+
+ num = 0;
+ for (i = 0; i < SHL_ARRAY_LENGTH(expect); ++i)
+ num += expect[i].times;
+
+ ck_assert_int_eq(received, num);
+
+ wfd_rtsp_decoder_free(d);
+}
+END_TEST
+
+static void tokenize(const char *line,
+ size_t linelen,
+ const char *expect,
+ size_t len,
+ size_t num)
+{
+ const char *s;
+ char *t;
+ ssize_t l, i;
+
+ ck_assert(len > 0);
+
+ l = wfd_rtsp_tokenize(line, linelen, &t);
+ ck_assert(l >= 0);
+
+ if (0) {
+ fprintf(stderr, "TOKENIZER (%lu):\n", (unsigned long)l);
+ s = t;
+ for (i = 0; i < l; ++i) {
+ fprintf(stderr, " TOKEN: %s\n", s);
+ s += strlen(s) + 1;
+ }
+
+ fprintf(stderr, "EXPECT (%lu):\n", (unsigned long)num);
+ s = expect;
+ for (i = 0; i < l; ++i) {
+ fprintf(stderr, " TOKEN: %s\n", s);
+ s += strlen(s) + 1;
+ }
+ }
+
+ ck_assert(l == (ssize_t)num);
+ ck_assert(!memcmp(t, expect, len));
+ free(t);
+}
+
+#define TOKENIZE(_line, _exp, _num) \
+ tokenize((_line), strlen(_line), (_exp), sizeof(_exp), _num)
+#define TOKENIZE_N(_line, _len, _exp, _num) \
+ tokenize((_line), _len, (_exp), sizeof(_exp), _num)
+
+START_TEST(test_wfd_rtsp_tokenizer)
+{
+ TOKENIZE("", "", 0);
+ TOKENIZE("asdf", "asdf", 1);
+ TOKENIZE("asdf\"\"asdf", "asdf\0\0asdf", 3);
+ TOKENIZE("asdf\"asdf\"asdf", "asdf\0asdf\0asdf", 3);
+ TOKENIZE("\"asdf\"", "asdf", 1);
+ TOKENIZE("\"\\n\\\\\\r\"", "\n\\\r", 1);
+ TOKENIZE("\"\\\"\"", "\"", 1);
+ TOKENIZE("\"\\0\"", "\\0", 1);
+ TOKENIZE("\"\\\0\"", "\\", 1);
+ TOKENIZE_N("\"\\\0\"", 4, "\\0", 1);
+ TOKENIZE("\"\\0\\\0\"", "\\0\\", 1);
+ TOKENIZE_N("\"\\0\\\0\"", 6, "\\0\\0", 1);
+ TOKENIZE("content-length: 100", "content-length\0:\0""100", 3);
+ TOKENIZE("content-args: (50+10)", "content-args\0:\0(\0""50+10\0)", 5);
+ TOKENIZE("content-args: (50 + 10)", "content-args\0:\0(\0""50\0+\0""10\0)", 7);
+}
+END_TEST
+
+TEST_DEFINE_CASE(decoder)
+ TEST(test_wfd_rtsp_decoder)
+ TEST(test_wfd_rtsp_tokenizer)
+TEST_END_CASE
+
+TEST_DEFINE(
+ TEST_SUITE(rtsp,
+ TEST_CASE(decoder),
+ TEST_END
+ )
+)