diff options
-rw-r--r-- | .gitignore | 27 | ||||
-rw-r--r-- | COPYING | 37 | ||||
-rw-r--r-- | Makefile.am | 150 | ||||
-rw-r--r-- | NEWS | 4 | ||||
-rw-r--r-- | README | 40 | ||||
-rwxr-xr-x | autogen.sh | 17 | ||||
-rw-r--r-- | configure.ac | 75 | ||||
-rw-r--r-- | docs/libwfd.pc.in | 11 | ||||
-rw-r--r-- | docs/libwfd.sym | 22 | ||||
-rw-r--r-- | src/libwfd.h | 816 | ||||
-rw-r--r-- | src/rtsp_decoder.c | 1270 | ||||
-rw-r--r-- | src/rtsp_tokenizer.c | 191 | ||||
-rw-r--r-- | src/shl_llog.h | 247 | ||||
-rw-r--r-- | src/shl_macro.h | 219 | ||||
-rw-r--r-- | src/shl_ring.c | 188 | ||||
-rw-r--r-- | src/shl_ring.h | 52 | ||||
-rw-r--r-- | src/shl_util.c | 260 | ||||
-rw-r--r-- | src/shl_util.h | 83 | ||||
-rw-r--r-- | test/test_common.h | 109 | ||||
-rw-r--r-- | test/test_rtsp.c | 558 |
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 @@ -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: @@ -0,0 +1,4 @@ += libwfd Release News = + +CHANGES WITH 1: + * TODO @@ -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 + ) +) |