summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Herrmann <dh.herrmann@gmail.com>2014-02-04 16:13:23 +0100
committerDavid Herrmann <dh.herrmann@gmail.com>2014-02-04 16:29:40 +0100
commit6d57a6a0d910d78c1279a9463ca2bae7c6a6cdfb (patch)
treec52cf2c3e9b07ce2ee796ded3b759f2cc539fb0b
Import libwfd from OpenWFD
Initial import of most generic WFD stuff from OpenWFD. The libwfd library will be independent of any Miracast implementation so it can be shared across projects. It will not provide any fancy integration or policy, but only the most basic protocol parsers and handling. Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
-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
+ )
+)