Squashed 'third_party/rawrtc/re/' content from commit f3163ce8b
Change-Id: I6a235e6ac0f03269d951026f9d195da05c40fdab
git-subtree-dir: third_party/rawrtc/re
git-subtree-split: f3163ce8b526a13b35ef71ce4dd6f43585064d8a
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1f4b38f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+build*
+test
+test.c
+test.d
+test.o
+*stamp
+*.previous
+libre.a
+libre.dylib
+libre.pc
+libre.so
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..3fcd677
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,26 @@
+language: c
+
+os:
+ - linux
+ - osx
+
+compiler:
+ - clang
+ - gcc
+
+addons:
+ apt:
+ packages:
+ libssl-dev
+
+install:
+ - wget "https://github.com/alfredh/pytools/raw/master/ccheck.py"
+
+script:
+ - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
+ make EXTRA_CFLAGS=-Werror CCACHE=;
+ fi
+ - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
+ make EXTRA_CFLAGS="-I/usr/local/opt/openssl/include -Werror" EXTRA_LFLAGS="-L/usr/local/opt/openssl/lib" CCACHE=;
+ fi
+ - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then python2 ccheck.py ; fi
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..70dd450
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,137 @@
+#
+# Makefile
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+# Master version number
+VER_MAJOR := 0
+VER_MINOR := 6
+VER_PATCH := 0
+
+PROJECT := re
+VERSION := $(VER_MAJOR).$(VER_MINOR).$(VER_PATCH)
+
+MK := mk/re.mk
+
+include $(MK)
+
+# List of modules
+MODULES += sip sipevent sipreg sipsess
+MODULES += uri http httpauth msg websock
+MODULES += stun turn ice
+MODULES += natbd
+MODULES += rtp sdp jbuf telev
+MODULES += dns
+MODULES += md5 crc32 sha hmac base64
+MODULES += udp sa net tcp tls
+MODULES += list mbuf hash
+MODULES += fmt tmr main mem dbg sys lock mqueue
+MODULES += mod conf
+MODULES += bfcp
+MODULES += aes srtp
+MODULES += odict
+MODULES += json
+MODULES += rtmp
+
+INSTALL := install
+ifeq ($(DESTDIR),)
+PREFIX ?= /usr/local
+else
+PREFIX ?= /usr
+endif
+ifeq ($(LIBDIR),)
+LIBDIR := $(PREFIX)/lib
+endif
+INCDIR := $(PREFIX)/include/re
+MKDIR := $(PREFIX)/share/re
+CFLAGS += -Iinclude
+
+MODMKS := $(patsubst %,src/%/mod.mk,$(MODULES))
+SHARED := libre$(LIB_SUFFIX)
+STATIC := libre.a
+
+include $(MODMKS)
+
+
+OBJS ?= $(patsubst %.c,$(BUILD)/%.o,$(SRCS))
+
+
+all: $(SHARED) $(STATIC)
+
+
+-include $(OBJS:.o=.d)
+
+
+$(SHARED): $(OBJS)
+ @echo " LD $@"
+ @$(LD) $(LFLAGS) $(SH_LFLAGS) $^ $(LIBS) -o $@
+
+
+$(STATIC): $(OBJS)
+ @echo " AR $@"
+ @$(AR) $(AFLAGS) $@ $^
+ifneq ($(RANLIB),)
+ @$(RANLIB) $@
+endif
+
+libre.pc:
+ @echo 'prefix='$(PREFIX) > libre.pc
+ @echo 'exec_prefix=$${prefix}' >> libre.pc
+ @echo 'libdir=$(LIBDIR)' >> libre.pc
+ @echo 'includedir=$${prefix}/include/re' >> libre.pc
+ @echo '' >> libre.pc
+ @echo 'Name: libre' >> libre.pc
+ @echo 'Description: ' >> libre.pc
+ @echo 'Version: '$(VERSION) >> libre.pc
+ @echo 'URL: http://creytiv.com/re.html' >> libre.pc
+ @echo 'Libs: -L$${libdir} -lre' >> libre.pc
+ @echo 'Libs.private: -L$${libdir} -lre ${LIBS}' >> libre.pc
+ @echo 'Cflags: -I$${includedir}' >> libre.pc
+
+$(BUILD)/%.o: src/%.c $(BUILD) Makefile $(MK) $(MODMKS)
+ @echo " CC $@"
+ @$(CC) $(CFLAGS) -c $< -o $@ $(DFLAGS)
+
+
+$(BUILD): Makefile $(MK) $(MODMKS)
+ @mkdir -p $(patsubst %,$(BUILD)/%,$(sort $(dir $(SRCS))))
+ @touch $@
+
+
+.PHONY: clean
+clean:
+ @rm -rf $(SHARED) $(STATIC) libre.pc test.d test.o test $(BUILD)
+
+
+install: $(SHARED) $(STATIC) libre.pc
+ @mkdir -p $(DESTDIR)$(LIBDIR) $(DESTDIR)$(LIBDIR)/pkgconfig \
+ $(DESTDIR)$(INCDIR) $(DESTDIR)$(MKDIR)
+ $(INSTALL) -m 0644 $(shell find include -name "*.h") \
+ $(DESTDIR)$(INCDIR)
+ $(INSTALL) -m 0755 $(SHARED) $(DESTDIR)$(LIBDIR)
+ $(INSTALL) -m 0755 $(STATIC) $(DESTDIR)$(LIBDIR)
+ $(INSTALL) -m 0644 libre.pc $(DESTDIR)$(LIBDIR)/pkgconfig
+ $(INSTALL) -m 0644 $(MK) $(DESTDIR)$(MKDIR)
+
+uninstall:
+ @rm -rf $(DESTDIR)$(INCDIR)
+ @rm -rf $(DESTDIR)$(MKDIR)
+ @rm -f $(DESTDIR)$(LIBDIR)/$(SHARED)
+ @rm -f $(DESTDIR)$(LIBDIR)/$(STATIC)
+ @rm -f $(DESTDIR)$(LIBDIR)/pkgconfig/libre.pc
+
+-include test.d
+
+test.o: test.c Makefile $(MK)
+ @echo " CC $@"
+ @$(CC) $(CFLAGS) -c $< -o $@ $(DFLAGS)
+
+test$(BIN_SUFFIX): test.o $(SHARED) $(STATIC)
+ @echo " LD $@"
+ @$(LD) $(LFLAGS) $< -L. -lre $(LIBS) -o $@
+
+sym: $(SHARED)
+ @nm $(SHARED) | grep " U " | perl -pe 's/\s*U\s+(.*)/$${1}/' \
+ > docs/symbols.txt
+ @echo "$(SHARED) is using `cat docs/symbols.txt | wc -l ` symbols"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..71b776d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,293 @@
+libre README
+============
+
+
+libre is a Generic library for real-time communications with async IO support.
+Copyright (C) 2010 - 2019 Creytiv.com
+
+
+[](https://travis-ci.org/creytiv/re)
+
+
+## Features
+
+* SIP Stack ([RFC 3261](https://tools.ietf.org/html/rfc3261))
+* SDP
+* RTP and RTCP
+* SRTP and SRTCP (Secure RTP)
+* DNS-Client
+* STUN/TURN/ICE stack
+* BFCP
+* HTTP-stack with client/server
+* Websockets
+* Jitter-buffer
+* Async I/O (poll, epoll, select, kqueue)
+* UDP/TCP/TLS/DTLS transport
+* JSON parser
+* Real Time Messaging Protocol (RTMP)
+
+
+## Building
+
+libre is using GNU makefiles. Make and OpenSSL development headers must be
+installed before building.
+
+
+### Build with debug enabled
+
+```
+$ make
+$ sudo make install
+$ sudo ldconfig
+```
+
+### Build with release
+
+```
+$ make RELEASE=1
+$ sudo make RELEASE=1 install
+$ sudo ldconfig
+```
+
+### Build with clang compiler
+
+```
+$ make CC=clang
+$ sudo make CC=clang install
+$ sudo ldconfig
+```
+
+
+## Documentation
+
+The online documentation generated with doxygen is available in
+the main [website](http://creytiv.com/doxygen/re-dox/html/)
+
+
+
+### Examples
+
+Coding examples are available from the
+[redemo](http://creytiv.com/pub/redemo-0.5.0.tar.gz) project
+
+
+## License
+
+The libre project is using the BSD license.
+
+
+## Contributing
+
+Patches can sent via Github
+[Pull-Requests](https://github.com/creytiv/re/pulls) or to the RE devel
+[mailing-list](http://lists.creytiv.com/mailman/listinfo/re-devel).
+Currently we only accept small patches.
+Please send private feedback to libre [at] creytiv.com
+
+
+## Design goals
+
+* Portable POSIX source code (ANSI C89 and ISO C99 standard)
+* Robust, fast, low memory footprint
+* RFC compliance
+* IPv4 and IPv6 support
+
+
+## Modules
+
+| Name | Status | Description |
+|----------|----------|------------------------------------------------|
+| aes | unstable | AES (Advanced Encryption Standard) |
+| base64 | testing | Base-64 encoding/decoding functions |
+| bfcp | unstable | The Binary Floor Control Protocol (BFCP) |
+| conf | testing | Configuration file parser |
+| crc32 | testing | 32-bit CRC defined in ITU V.42 |
+| dbg | testing | Debug printing |
+| dns | stable | DNS resolving (NAPTR, SRV, A) |
+| fmt | testing | Formatted printing and regular expression |
+| hash | testing | Hashmap table |
+| hmac | testing | HMAC: Keyed-Hashing for Message Authentication |
+| http | unstable | HTTP parser (RFC 2616) |
+| httpauth | testing | HTTP-based Authentication (RFC 2617) |
+| ice | unstable | Interactive Connectivity Establishment (ICE) |
+| jbuf | testing | Jitter buffer |
+| json | unstable | JavaScript Object Notation (JSON) |
+| list | stable | Sortable doubly-linked list handling |
+| lock | testing | Resource locking functions |
+| main | testing | Main poll loop |
+| mbuf | stable | Linear memory buffers |
+| md5 | stable | The MD5 Message-Digest Algorithm (RFC 1321) |
+| mem | stable | Memory referencing |
+| mod | testing | Run-time module loading |
+| mqueue | testing | Thread-safe message queue |
+| msg | unstable | Generic message component library |
+| natbd | unstable | NAT Behavior Discovery using STUN |
+| net | testing | Networking routines |
+| odict | unstable | Ordered Dictionary |
+| rtmp | unstable | Real Time Messaging Protocol |
+| rtp | testing | Real-time Transport Protocol |
+| sa | stable | Socket Address functions |
+| sdp | testing | Session Description Protocol |
+| sha | testing | Secure Hash Standard, NIST, FIPS PUB 180-1 |
+| sip | stable | Core SIP library |
+| sipevent | testing | SIP Event framework |
+| sipreg | stable | SIP register client |
+| sipsess | stable | SIP Sessions |
+| srtp | unstable | Secure Real-time Transport Protocol (SRTP) |
+| stun | stable | Session Traversal Utilities for NAT (STUN) |
+| sys | testing | System information |
+| tcp | testing | TCP transport |
+| telev | testing | Telephony Events (RFC 4733) |
+| tls | unstable | Transport Layer Security |
+| tmr | stable | Timer handling |
+| turn | stable | Obtaining Relay Addresses from STUN (TURN) |
+| udp | testing | UDP transport |
+| uri | testing | Generic URI library |
+| websock | unstable | WebSocket Client and Server |
+
+legend:
+* *stable* - code complete; stable code and stable API
+* *testing* - code complete, but API might change
+* *unstable* - code complete but not completely tested
+* *development* - code is under development
+
+
+## Features
+
+* [RFC 1321](https://tools.ietf.org/html/rfc1321) - The MD5 Message-Digest Algorithm
+* [RFC 1886](https://tools.ietf.org/html/rfc1886) - DNS Extensions to support IP version 6
+* [RFC 2032](https://tools.ietf.org/html/rfc2032) - RTP Payload Format for H.261 Video Streams
+* [RFC 2616](https://tools.ietf.org/html/rfc2616) - Hypertext Transfer Protocol -- HTTP/1.1
+* [RFC 2617](https://tools.ietf.org/html/rfc2617) - HTTP Authentication: Basic and Digest Access Authentication
+* [RFC 2782](https://tools.ietf.org/html/rfc2782) - A DNS RR for Specifying the Location of Services (DNS SRV)
+* [RFC 2915](https://tools.ietf.org/html/rfc2915) - The Naming Authority Pointer (NAPTR) DNS Resource Record
+* [RFC 3261](https://tools.ietf.org/html/rfc3261) - SIP: Session Initiation Protocol
+* [RFC 3263](https://tools.ietf.org/html/rfc3263) - Locating SIP Servers
+* [RFC 3264](https://tools.ietf.org/html/rfc3264) - An Offer/Answer Model with SDP
+* [RFC 3265](https://tools.ietf.org/html/rfc3265) - SIP-Specific Event Notification
+* [RFC 3327](https://tools.ietf.org/html/rfc3327) - SIP Extension Header Field for Registering Non-Adjacent Contacts
+* [RFC 3428](https://tools.ietf.org/html/rfc3428) - SIP Extension for Instant Messaging
+* [RFC 3489](https://tools.ietf.org/html/rfc3489) - STUN - Simple Traversal of UDP Through NATs
+* [RFC 3515](https://tools.ietf.org/html/rfc3515) - The SIP Refer Method
+* [RFC 3550](https://tools.ietf.org/html/rfc3550) - RTP: A Transport Protocol for Real-Time Applications
+* [RFC 3551](https://tools.ietf.org/html/rfc3551) - RTP Profile for Audio and Video Conferences with Minimal Control
+* [RFC 3555](https://tools.ietf.org/html/rfc3555) - MIME Type Registration of RTP Payload Formats
+* [RFC 3556](https://tools.ietf.org/html/rfc3556) - SDP Bandwidth Modifiers for RTCP Bandwidth
+* [RFC 3581](https://tools.ietf.org/html/rfc3581) - An Extension to SIP for Symmetric Response Routing
+* [RFC 3605](https://tools.ietf.org/html/rfc3605) - RTCP attribute in SDP
+* [RFC 3711](https://tools.ietf.org/html/rfc3711) - The Secure Real-time Transport Protocol (SRTP)
+* [RFC 3969](https://tools.ietf.org/html/rfc3969) - The IANA URI Parameter Registry for SIP
+* [RFC 3994](https://tools.ietf.org/html/rfc3994) - Indication of Message Composition for Instant Messaging
+* [RFC 4346](https://tools.ietf.org/html/rfc4346) - The TLS Protocol Version 1.1
+* [RFC 4566](https://tools.ietf.org/html/rfc4566) - SDP: Session Description Protocol
+* [RFC 4582](https://tools.ietf.org/html/rfc4582) - The Binary Floor Control Protocol (BFCP)
+* [RFC 4582bis](https://tools.ietf.org/html/draft-ietf-bfcpbis-rfc4582bis-08) - The Binary Floor Control Protocol (BFCP)
+* [RFC 4585](https://tools.ietf.org/html/rfc4585) - Extended RTP Profile for RTCP-Based Feedback
+* [RFC 4733](https://tools.ietf.org/html/rfc4733) - RTP Payload for DTMF Digits, Telephony Tones, and Teleph. Signals
+* [RFC 4961](https://tools.ietf.org/html/rfc4961) - Symmetric RTP / RTP Control Protocol (RTCP)
+* [RFC 5118](https://tools.ietf.org/html/rfc5118) - SIP Torture Test Messages for IPv6
+* [RFC 5245](https://tools.ietf.org/html/rfc5245) - Interactive Connectivity Establishment (ICE)
+* [RFC 5389](https://tools.ietf.org/html/rfc5389) - Session Traversal Utilities for NAT (STUN)
+* [RFC 5626](https://tools.ietf.org/html/rfc5626) - Managing Client-Initiated Connections in SIP
+* [RFC 5761](https://tools.ietf.org/html/rfc5761) - Multiplexing RTP Data and Control Packets on a Single Port
+* [RFC 5766](https://tools.ietf.org/html/rfc5766) - Traversal Using Relays around NAT (TURN)
+* [RFC 5768](https://tools.ietf.org/html/rfc5768) - Indicating Support for ICE in SIP
+* [RFC 5769](https://tools.ietf.org/html/rfc5769) - Test vectors for STUN
+* [RFC 5780](https://tools.ietf.org/html/rfc5780) - NAT Behaviour Discovery Using STUN
+* [RFC 6026](https://tools.ietf.org/html/rfc6026) - Correct Transaction Handling for 2xx Resp. to SIP INVITE Requests
+* [RFC 6156](https://tools.ietf.org/html/rfc6156) - TURN Extension for IPv6
+* [RFC 6188](https://tools.ietf.org/html/rfc6188) - The Use of AES-192 and AES-256 in Secure RTP
+* [RFC 6455](https://tools.ietf.org/html/rfc6455) - The WebSocket Protocol
+* [RFC 7159](https://tools.ietf.org/html/rfc7159) - JavaScript Object Notation (JSON)
+* [RFC 7350](https://tools.ietf.org/html/rfc7350) - DTLS as Transport for STUN
+* [RFC 7714](https://tools.ietf.org/html/rfc7714) - AES-GCM Authenticated Encryption in SRTP
+
+
+## Supported platforms
+
+* Linux
+* FreeBSD
+* OpenBSD
+* NetBSD
+* Solaris 11
+* Windows
+* Apple Mac OS X and iOS
+* Android (5.0 or later)
+
+### Supported versions of C Standard library
+
+* Android bionic
+* BSD libc
+* GNU C Library (glibc)
+* Windows C Run-Time Libraries (CRT)
+* uClibc
+
+
+### Supported compilers:
+
+* gcc 3.x
+* gcc 4.x
+* gcc 5.x
+* gcc 6.x
+* ms vc2003 compiler
+* clang
+
+### Supported versions of OpenSSL
+
+* OpenSSL version 1.0.1
+* OpenSSL version 1.0.2
+* OpenSSL version 1.1.0 (supported since version 0.5.0)
+* LibreSSL version 2.x
+
+
+## Coding guidelines
+
+* Use enum for constants where appropriate
+* Use const as much as possible (where appropriate)
+* Use C99 data types (intN_t, uintN_t, bool)
+* Hide data-types in .c files where possible (use struct foo)
+* Avoid malloc/free, use mem_alloc/mem_deref instead
+* CVS/svn/git tags are NOT allowed in the code!
+* Avoid bit-fields in structs which are not portable
+* Use dummy handlers for timing-critical callbacks
+* return err, return alloced objects as pointer-pointers
+* in allocating functions, first arg is always double pointer
+* Use POSIX error-codes; EINVAL for invalid args, EBADMSG for
+ parse errors and EPROTO for protocol errors
+
+
+## Transport protocols
+
+
+| | TCP | UDP | TLS | DTLS|
+|:--------|:---:|:---:|:---:|:---:|
+| BFCP | - | yes | - | - |
+| DNS | yes | yes | - | - |
+| HTTP | yes | n/a | yes | n/a |
+| ICE | - | yes | - | - |
+| RTP | - | yes | - | - |
+| RTCP | - | yes | - | - |
+| RTMP | yes | - | - | - |
+| SIP | yes | yes | yes | - |
+| STUN | yes | yes | yes | yes |
+| TURN | yes | yes | yes | yes |
+| WEBSOCK | yes | n/a | yes | n/a |
+
+
+## Related projects
+
+* [librem](https://github.com/creytiv/rem)
+* [retest](https://github.com/creytiv/retest)
+* [baresip](https://github.com/alfredh/baresip)
+* [restund](http://creytiv.com/restund.html)
+
+
+
+## References
+
+http://creytiv.com/re.html
+
+https://github.com/creytiv/re
+
+http://lists.creytiv.com/mailman/listinfo/re-devel
+
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..e63c87c
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,227 @@
+libre (0.6.0-3) unstable; urgency=medium
+
+ * version 0.6.0-3
+
+ -- Richard Aas <richaraas@gmail.com> Fri, 1 Feb 2019 12:00:00 +0100
+
+libre (0.6.0-2) unstable; urgency=medium
+
+ * version 0.6.0-2
+
+ -- Richard Aas <richaraas@gmail.com> Tue, 11 Dec 2018 10:00:00 +0100
+
+libre (0.6.0-1) unstable; urgency=medium
+
+ * version 0.6.0-1
+
+ -- Richard Aas <richard@db.org> Fri, 30 Nov 2018 10:00:00 +0100
+
+libre (0.6.0) unstable; urgency=medium
+
+ * version 0.6.0
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sat, 24 Nov 2018 10:00:00 +0100
+
+libre (0.5.9) unstable; urgency=medium
+
+ * version 0.5.9
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sat, 1 Sep 2018 10:00:00 +0200
+
+libre (0.5.8) unstable; urgency=medium
+
+ * version 0.5.8
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Fri, 20 Apr 2018 16:00:00 +0200
+
+libre (0.5.7) unstable; urgency=medium
+
+ * version 0.5.7
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Fri, 12 Jan 2018 18:00:00 +0100
+
+libre (0.5.6) unstable; urgency=medium
+
+ * version 0.5.6
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Mon, 6 Nov 2017 15:00:00 +0100
+
+libre (0.5.5) unstable; urgency=medium
+
+ * version 0.5.5
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Tue, 5 Sep 2017 12:00:00 +0200
+
+libre (0.5.4) unstable; urgency=medium
+
+ * version 0.5.4
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sat, 24 Jun 2017 12:00:00 +0200
+
+libre (0.5.3) unstable; urgency=medium
+
+ * version 0.5.3
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sat, 13 May 2017 12:00:00 +0200
+
+libre (0.5.2) unstable; urgency=medium
+
+ * version 0.5.2
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sat, 08 Apr 2017 12:00:00 +0200
+
+libre (0.5.1) unstable; urgency=medium
+
+ * version 0.5.1
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Sat, 04 Feb 2017 12:00:00 +0100
+
+libre (0.5.0) unstable; urgency=medium
+
+ * version 0.5.0
+
+ -- Alfred E. Heggestad <alfred.heggestad@gmail.com> Fri, 25 Nov 2016 18:00:00 +0100
+
+libre (0.4.17) unstable; urgency=medium
+
+ * version 0.4.17
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 24 Jun 2016 15:00:00 +0100
+
+libre (0.4.16) unstable; urgency=medium
+
+ * version 0.4.16
+
+ -- Alfred E. Heggestad <aeh@db.org> Wed, 27 Apr 2016 20:00:00 +0100
+
+libre (0.4.15) unstable; urgency=medium
+
+ * version 0.4.15
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 06 Feb 2016 04:15:00 +0100
+
+libre (0.4.14) unstable; urgency=low
+
+ * version 0.4.14
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 24 Oct 2015 20:00:00 +0100
+
+libre (0.4.13-1) unstable; urgency=low
+
+ * version 0.4.13-1
+
+ -- Richard Aas <richard@db.org> Thu, 08 Oct 2015 10:35:00 +0100
+
+libre (0.4.13) unstable; urgency=low
+
+ * version 0.4.13
+
+ -- Alfred E. Heggestad <aeh@db.org> Wed, 01 July 2015 20:00:00 +0100
+
+libre (0.4.12) unstable; urgency=low
+
+ * version 0.4.12
+
+ -- Alfred E. Heggestad <aeh@db.org> Mon, 16 Mar 2015 20:00:00 +0100
+
+libre (0.4.11) unstable; urgency=low
+
+ * version 0.4.11
+
+ -- Alfred E. Heggestad <aeh@db.org> Tue, 9 Dec 2014 20:00:00 +0100
+
+libre (0.4.10-2) unstable; urgency=low
+
+ * version 0.4.10-2
+
+ -- Richard Aas <richard@db.org> Mon, 17 Nov 2014 09:09:00 +0100
+
+libre (0.4.10-1) unstable; urgency=low
+
+ * version 0.4.10-1
+
+ -- Richard Aas <richard@db.org> Fri, 14 Nov 2014 09:09:00 +0100
+
+libre (0.4.10) unstable; urgency=low
+
+ * version 0.4.10
+
+ -- Alfred E. Heggestad <aeh@db.org> Sun, 19 Oct 2014 19:00:00 +0100
+
+libre (0.4.9) unstable; urgency=low
+
+ * version 0.4.9
+
+ -- Alfred E. Heggestad <aeh@db.org> Wed, 18 Jun 2014 19:00:00 +0100
+
+libre (0.4.8) unstable; urgency=low
+
+ * version 0.4.8
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 11 Apr 2014 19:00:00 +0100
+
+libre (0.4.7) unstable; urgency=low
+
+ * version 0.4.7
+
+ -- Alfred E. Heggestad <aeh@db.org> Sun, 5 Jan 2014 20:00:00 +0100
+
+libre (0.4.6) unstable; urgency=low
+
+ * version 0.4.6
+
+ -- Alfred E. Heggestad <aeh@db.org> Tue, 12 Nov 2013 20:00:00 +0100
+
+libre (0.4.5) unstable; urgency=low
+
+ * version 0.4.5
+
+ -- Alfred E. Heggestad <aeh@db.org> Thu, 3 Oct 2013 20:00:00 +0100
+
+libre (0.4.4) unstable; urgency=low
+
+ * version 0.4.4
+
+ -- Alfred E. Heggestad <aeh@db.org> Tue, 27 Aug 2013 20:00:00 +0100
+
+libre (0.4.3) unstable; urgency=low
+
+ * version 0.4.3
+
+ -- Alfred E. Heggestad <aeh@db.org> Sun, 5 May 2013 15:00:00 +0100
+
+libre (0.4.2) unstable; urgency=low
+
+ * version 0.4.2
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 10 Aug 2012 10:08:00 +0100
+
+libre (0.4.1) unstable; urgency=low
+
+ * version 0.4.1
+
+ -- Alfred E. Heggestad <aeh@db.org> Sat, 21 Apr 2012 21:04:00 +0100
+
+libre (0.4.0) unstable; urgency=low
+
+ * version 0.4.0
+
+ -- Alfred E. Heggestad <aeh@db.org> Sun, 25 Dec 2011 12:25:00 +0100
+
+libre (0.3.0) unstable; urgency=low
+
+ * version 0.3.0
+
+ -- Alfred E. Heggestad <aeh@db.org> Wed, 7 Sept 2011 07:11:00 +0100
+
+libre (0.2.0) unstable; urgency=low
+
+ * version 0.2.0
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 20 May 2011 20:05:00 +0100
+
+libre (0.1.0) unstable; urgency=low
+
+ * version 0.1.0
+
+ -- Alfred E. Heggestad <aeh@db.org> Fri, 5 Nov 2010 05:11:10 +0100
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..7feb4a1
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,73 @@
+Source: libre
+Section: comm
+Priority: optional
+Maintainer: Alfred E. Heggestad <aeh@db.org>
+Standards-Version: 3.9.5
+Build-Depends: debhelper (>= 9.20120311)
+Homepage: http://www.creytiv.com/
+
+Package: libre
+Architecture: any
+Section: libs
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Generic library for real-time communications with async IO support
+ Libre is a portable and generic library for real-time communications
+ with async IO support and a complete SIP stack with support for protocols
+ such as SDP, RTP/RTCP, STUN/TURN/ICE, BFCP, HTTP and DNS Client.
+ .
+ The current features are:
+ .
+ SIP Stack (RFC 3261)
+ SDP parser
+ RTP and RTCP stack
+ DNS-Client
+ STUN/TURN/ICE stack
+ BFCP
+ Jitter-buffer
+ Async I/O (poll, epoll, select)
+ UDP/TCP/TLS/DTLS transport
+ HTTP Client and Server
+ Websocket Client and Server
+ JSON parser
+ SRTP and SRTCP (Secure RTP)
+ .
+ Design goals:
+ .
+ Portable POSIX source code (ANSI C89 and ISO C99 standard)
+ Robust, fast, low memory footprint
+ RFC compliance
+ IPv4 and IPv6 support
+
+Package: libre-dev
+Architecture: any
+Section: libdevel
+Depends: libre (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends}
+Description: Generic library for real-time communications (development files)
+ Libre is a portable and generic library for real-time communications
+ with async IO support and a complete SIP stack with support for protocols
+ such as SDP, RTP/RTCP, STUN/TURN/ICE, BFCP, HTTP and DNS Client.
+ .
+ The current features are:
+ .
+ SIP Stack (RFC 3261)
+ SDP parser
+ RTP and RTCP stack
+ DNS-Client
+ STUN/TURN/ICE stack
+ BFCP
+ Jitter-buffer
+ Async I/O (poll, epoll, select)
+ UDP/TCP/TLS/DTLS transport
+ HTTP Client and Server
+ Websocket Client and Server
+ JSON parser
+ SRTP and SRTCP (Secure RTP)
+ .
+ Design goals:
+ .
+ Portable POSIX source code (ANSI C89 and ISO C99 standard)
+ Robust, fast, low memory footprint
+ RFC compliance
+ IPv4 and IPv6 support
+ .
+ This package is only the library development files.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..ae8c514
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,37 @@
+This package was debianized by Alfred E. Heggestad <aeh@db.org>
+
+It was downloaded from http://www.creytiv.com/
+
+
+Copyright (c) 2010 - 2018, Alfred E. Heggestad
+Copyright (c) 2010 - 2018, Richard Aas
+Copyright (c) 2010 - 2018, Creytiv.com
+All rights reserved.
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the Creytiv.com nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..b468964
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,2 @@
+README.md
+docs/TODO
diff --git a/debian/libre-dev.dirs b/debian/libre-dev.dirs
new file mode 100644
index 0000000..3199aad
--- /dev/null
+++ b/debian/libre-dev.dirs
@@ -0,0 +1,3 @@
+usr/include
+usr/lib
+usr/share
diff --git a/debian/libre-dev.files b/debian/libre-dev.files
new file mode 100644
index 0000000..7e20828
--- /dev/null
+++ b/debian/libre-dev.files
@@ -0,0 +1,3 @@
+usr/include
+usr/lib/libre.a
+usr/share/re
diff --git a/debian/libre.dirs b/debian/libre.dirs
new file mode 100644
index 0000000..6845771
--- /dev/null
+++ b/debian/libre.dirs
@@ -0,0 +1 @@
+usr/lib
diff --git a/debian/libre.files b/debian/libre.files
new file mode 100644
index 0000000..153adbd
--- /dev/null
+++ b/debian/libre.files
@@ -0,0 +1 @@
+/usr/lib/libre.so
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..84ec8a1
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,97 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+
+EXTRA_CFLAGS:="$(shell dpkg-buildflags --get CFLAGS | sed -e 's/-O2//')"
+EXTRA_LFLAGS:="$(shell dpkg-buildflags --get LDFLAGS) -Wl,-soname,libre.so"
+
+
+configure: configure-stamp
+configure-stamp:
+ dh_testdir
+ # Add here commands to configure the package.
+
+ touch configure-stamp
+
+build: build-stamp
+build-stamp: configure-stamp
+ dh_testdir
+
+ # Add here commands to compile the package.
+ $(MAKE) RELEASE=1 \
+ EXTRA_CFLAGS=$(EXTRA_CFLAGS) \
+ EXTRA_LFLAGS=$(EXTRA_LFLAGS)
+
+ touch $@
+
+clean:
+ dh_testdir
+ dh_testroot
+ rm -f build-stamp configure-stamp
+
+ # Add here commands to clean up after the build process.
+ $(MAKE) clean
+
+ dh_clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_prep
+ dh_installdirs
+
+ # Add here commands to install the package into debian/tmp
+ mkdir $(CURDIR)/debian/tmp
+ $(MAKE) install DESTDIR=$(CURDIR)/debian/tmp
+
+ dh_movefiles
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+ dh_testdir
+ dh_testroot
+ dh_installchangelogs
+ dh_installdocs
+ dh_installexamples
+# dh_install
+# dh_installmenu
+# dh_installdebconf
+# dh_installlogrotate
+# dh_installemacsen
+# dh_installpam
+# dh_installmime
+# dh_installinit
+# dh_installcron
+# dh_installinfo
+ dh_installman
+ dh_link
+ dh_strip
+ dh_compress
+ dh_fixperms
+# dh_perl
+# dh_python
+ dh_makeshlibs
+ dh_installdeb
+ dh_shlibdeps
+ dh_gencontrol
+ dh_md5sums
+ dh_builddeb
+
+build-arch: build
+
+build-indep: build
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
diff --git a/docs/COPYING b/docs/COPYING
new file mode 100644
index 0000000..1b8c3af
--- /dev/null
+++ b/docs/COPYING
@@ -0,0 +1,32 @@
+Copyright (c) 2010 - 2019, Alfred E. Heggestad
+Copyright (c) 2010 - 2019, Richard Aas
+Copyright (c) 2010 - 2019, Creytiv.com
+All rights reserved.
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the Creytiv.com nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/docs/ChangeLog b/docs/ChangeLog
new file mode 100644
index 0000000..d2884d2
--- /dev/null
+++ b/docs/ChangeLog
@@ -0,0 +1,701 @@
+2018-11-24 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.6.0
+
+ * Project URL: https://github.com/creytiv/re
+
+ * build: add major,minor,patch versions to CFLAGS
+
+ * odict: add high-level odict helper functions
+
+ * rtmp: new module for Real Time Messaging Protocol (RTMP)
+
+ * uri: add uri_decode_hostport
+
+
+2018-09-01 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.9
+
+ * Project URL: https://github.com/creytiv/re
+
+ * build: Added support for 64-bit MINGW (#131)
+ (thanks Alexander Ushakov)
+
+ fixed inline issue when compiling VS as C++ (#143)
+ (thanks TheSil)
+
+ * jbuf: zero out jbuf_stat on jbuf flush (#147)
+ (thanks Christian Spielberger)
+
+ * net: remove net_conn api (old and unused) (#145)
+ fix bug in net_if_getname (#144)
+
+ * sip: get local TCP address in establish handler (#146)
+
+ * tls: add AES-GCM to DTLS-SRTP (#141)
+
+
+2018-04-20 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.8
+
+ * Project URL: https://github.com/creytiv/re
+
+ * build: update win32 files (thanks Encamy)
+
+ * aes: add support for AES-GCM (Galois Counter Mode)
+
+ * fmt: json/utf8: fix unescaping of unicode code points (#127)
+ add utf8_byteseq
+
+ * mqueue: set non-blocking mode for read/write file descriptors (#122)
+
+ * srtp: add support for AES-GCM cipher suite (RFC 7714)
+
+
+2018-01-12 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.7
+
+ * Project URL: https://github.com/creytiv/re
+
+ * build: remove support for Cygwin (#95)
+ remove support for splint (#96)
+
+ * mem: add secure memory functions (#102)
+
+ * net: larger buffer for net_if_list (#100)
+
+ * sipreg: add from_name (Display Name) (#104)
+
+ * tls: use per connection bio_method (fixes issue #92) (#93)
+
+
+2017-11-06 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.6
+
+ * Project URL: https://github.com/creytiv/re
+
+ * build: Update ar flags; use deterministic mode (#79)
+
+ * http: added support for chunked transfer encoding (#90)
+
+ * ice: Added functions to get selected candidates. (#72)
+ (thanks Joachim Bauch)
+
+ * json: improved performance for mypower10 (#88)
+ (thanks Chris Owen)
+
+ * mqueue: Pack struct of mqueue messages. (#62)
+ (thanks Joachim Bauch)
+
+ * odict: use int instead of enum to avoid vararg promotion (#81)
+
+ * tls: add dtls_recv_packet() (#89)
+
+
+2017-09-05 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.5
+
+ * Project URL: https://github.com/creytiv/re
+
+ * ice: move gathering to application
+
+ * mod: add accessor function to module list
+
+ * sipreg: Added function sipreg_laddr()
+
+ * sys: optimize rand_str() and rand_char()
+
+
+2017-06-24 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.4
+
+ * Project URL: https://github.com/creytiv/re
+
+ * rtp: add extension bit to the api
+
+
+2017-05-13 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.3
+
+ * Project URL: https://github.com/creytiv/re
+
+ * build: upgrade windows project to VS2015 (thanks Mikhail Barg)
+ makefile improvements (thanks Lennart Grahl)
+
+ * ice: remove session object "struct ice"
+
+ * telev: add telev_set_srate (thanks Jan Hoffmann)
+
+
+2017-04-08 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.2
+
+ * Project URL: https://github.com/creytiv/re
+
+ * build: add Debian/kFreeBSD+Hurd (thanks Vasudev Kamath)
+
+ * ice: make enum ice_role type public
+
+ * main: fix build for Solaris 11
+
+ * srtcp: use unsigned for encrypted bit
+
+ * tls: add tls_openssl_context() accessor function
+
+
+2017-02-04 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.1
+
+ * Project URL: https://github.com/creytiv/re
+
+ * fmt: print directly to stream using handler (#38)
+
+ * http: HTTP client improvements (#36)
+ - http client connection reuse
+ - retry failed requests using fresh connections
+ - Handle Connection: close response header
+
+
+2016-11-25 Alfred E. Heggestad <alfred.heggestad@gmail.com>
+
+ * Version 0.5.0
+
+ * Project URL: https://github.com/creytiv/re
+
+ * build: add Dragonfly BSD (thanks Dmitrij D. Czarkoff)
+ remove support for Symbian OS
+
+ * aes: add support for OpenSSL version 1.1.0
+
+ * dns: dns/resolv cleanup (#11)
+ (thanks Dmitrij D. Czarkoff)
+
+ * hmac: add support for OpenSSL version 1.1.0
+
+ * main: remove support for ActiveScheduler (SymbianOS)
+
+ * tls: add support for OpenSSL version 1.1.0
+ add tls_set_certificate_pem()
+ add tls_set_certificate_der()
+ add dtls_peer()
+ add dtls_set_peer()
+ add tls_flush_error to dump openssl errors
+ (thanks to Lennart Grahl)
+
+ * udp: add udp_helper_find()
+
+
+2016-06-24 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.17
+
+ * build: add USE_OPENSSL_AES and USE_OPENSSL_HMAC
+
+ * dns: add key to dns_rrlist_sort()
+ add dns_rrlist_sort_addr
+
+ * tls: add tls_set_ciphers()
+ add tls_set_servername()
+
+ * sip: fix for stateless SIP requests
+ sort DNS RR entries by a fixed key
+
+ * stun: fix bug with 8-bit and 16-bit attributes on certain
+ platforms, such as MIPS
+
+
+2016-04-27 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.16
+
+ * build: fix warnings about DEFAULT_SOURCE with new glibc
+
+ * lock: fix debian build without HAVE_PTHREAD_RWLOCK
+
+ * rand: add arc4random (based on patch from Dmitrij D. Czarkoff)
+
+ * rtcp: adjust mbuf positions for RTCP_PSFB_AFB decoding
+
+ * tls: add tls_cipher_name()
+ add dtls_set_mtu()
+
+
+2016-02-06 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.15
+
+ * build: fix warnings about DEFAULT_SOURCE with new glibc
+ fix compile error for mingw32 (thanks Dmitrij D. Czarkoff)
+
+ * aes: handle buffers with zero length
+
+ * dns: add support for multiple DNS name-servers on Android
+
+ * hmac: add support for HMAC-SHA256
+
+ * rtp: fix packet-loss calc when first packet has seq=0
+
+ * srtp: split error code in ENOSR and ENOMEM
+
+ * stun: keepalive: handle method BINDING only
+
+ * telev: add a maximum queue size
+
+ * uri: fix a potential read buffer overflow
+
+
+2015-10-24 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.14
+
+ * New modules: json and odict
+
+ * build: add pkg-config file (thanks to William King)
+
+ * re_types: added a portable __REFUNC__
+
+ * fmt: add utf8_encode/decode, used by JSON module
+
+ * hash: add hash_fast() function
+
+ * json: new JavaScript Object Notation (JSON) module
+
+ * main: fix order of kqueue setting events (WRITE,READ)
+
+ * odict: new Ordered Dictionary module
+
+ * sip: reverse order of transport enumeration for SRV-records
+
+ * udp,tcp,net: add __USE_XOPEN2K (thanks Dmitrij D. Czarkoff)
+
+
+2015-07-01 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.13
+
+ * aes: added support for CommonCrypto API
+
+ * fmt: pl_float() handles negative numbers now
+
+ * hmac: added support for CommonCrypto API
+
+ * main: added support for async I/O method `kqueue'
+ this is now the default on platforms like OSX/iOS,
+ FreeBSD, NetBSD and OpenBSD.
+
+ * mem: added mem_reallocarray(), inspired by OpenBSD
+
+ * net: added net_default_gateway_get()
+
+ * tls: use RSA_generate_key_ex() instead of deprecated functions
+
+
+2015-03-16 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.12
+
+ * ice: added ice_ prefix to some functions and types
+ fix bug in priority calculations (thanks to Daniel Ma)
+
+ * mqueue: fix bug with leaking sockets on Windows-32
+
+ * rtp: fix bug with RTCP timestamp calculation
+
+ * sip: export sip_transp_laddr()
+
+ * tls: added more TLS methods
+
+
+2014-12-09 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.11
+
+ * build: export USE_TLS and USE_DTLS flags in re.mk makefile
+ detect sysctl.h and epoll.h for multi-arch platforms
+ dont use libresolv for openbsd
+
+ * main: check that maxfds is less than FD_SETSIZE (for select method)
+
+ * dtls: added udp-socket accessor and function to set handlers
+
+ * stun: added support for DTLS-transport
+ added doxygen comments
+
+ * tls: added function to set certificate from a string
+
+ * turn: added support for DTLS-transport
+
+
+2014-10-19 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.10
+
+ * dns: added support for using multi-threaded libresolv
+ (thanks to Thomas Klausner)
+ (thanks to Dmitrij D. Czarkoff for testing on OpenBSD)
+
+ * dtls: added support for sending DTLS over e.g. TURN
+ (this is done by adding 4 bytes of headroom in the packet)
+
+ * ice: added ice_set_conf()
+ continue checklist if send fails (thanks to SnakE)
+
+ * mbuf: added mbuf_shift()
+
+ * sdp: added sdp_media_session_rattr()
+ added extmap decoding RFC 5285 (thanks to Jose Carlos Pujol)
+
+ * sip: added struct sip_contact and related functions
+
+ * sipevent: added support for URI in contact-user, used for GRUU
+ (thanks to Juha Heinanen)
+
+ * sipreg: added "gruu" to list of Supported extensions
+
+ * sipsess: added support for URI in contact-user, used for GRUU
+ (thanks to Juha Heinanen)
+
+
+2014-06-18 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.9
+
+ * aes: clear openssl error queue in error cases
+
+ * bfcp: disabled support for DTLS-transport
+
+ * hmac: clear openssl error queue in error cases
+
+ * http: make response parsing a bit more robust
+
+ * main: make use of openssl's multi-threading API
+
+ * rtcp: added Round-Trip Time (RTT) field to struct rtcp_stats
+ fix some rounding errors
+
+ * sa: added padding buffer to struct sa union
+
+ * sdp: improved handling of unsupported transport protocols
+ and sub-sequence offer/answer exchanges
+
+ * srtp: added support for Secure Real-time Transport Protocol (SRTP)
+ (RFC 3711 and RFC 6188)
+
+ * sys: rand -- clear openssl error queue in error cases
+
+ * tls: added support for generating self-signed certificates
+ added support for the SRTP-extension using openssl
+ clear openssl error queue in error cases
+ improved DTLS api
+
+
+2014-04-11 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.8
+
+ * build: added support for Apple in cmake
+
+ * debian: update package
+
+ * aes: added AES (Advanced Encryption Standard) wrapper
+
+ * hmac: added a stateful HMAC wrapper
+
+ * http: added a HTTP client
+
+ * ice: minor API improvements
+
+ * msg: added a Generic message component library
+ (shared by SIP module and HTTP module)
+
+ * sdp: added sdp_media_laddr() to get local transport address
+
+ * sip: change struct sip_msg to use new struct msg_ctype
+ fixed a bug in parsing of Via headers
+
+ * sipsess: added sipsess_set_close_headers() to set any additional
+ SIP headers for BYE or BYE-response
+
+ * net: fixed a bug in net_rt_list() for darwin
+
+ * websock: added WebSocket Protocol (RFC 6455)
+
+
+2014-01-05 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.7
+
+ * build: added support for LLVM clang compiler
+
+ * dns: added support for getting Android nameserver address
+
+ * ice: minor debug tuning
+
+ * sipsess: only send INFO when dialog is established
+
+
+2013-11-12 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.6
+
+ * bfcp: fix bitwise operator for bool (thanks Tomasz Ostrowski)
+
+ * dns: do not connect the UDP socket
+
+ * ice: fix deref of NULL-pointer (thanks Tomasz Ostrowski)
+
+ * rtp: add support for RTCP AFB (Application-layer Feedback)
+ make RTCP decoding more robust
+
+ * udp: udp_connect() -- add peer address
+ add udp_error_handler_set()
+
+
+2013-10-03 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.5
+
+ * udp: add functions for joining/leaving multicast groups
+
+ * fmt: re_regex() fix va_end robustness
+
+ * rtp: set sequence number in range 0-32767
+
+ * sa: sa_print_addr() fix build when HAVE_INET6 is not set
+
+
+2013-08-27 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.4
+
+ * base64: added base64_print()
+
+ * http: added HTTP (Hypertext Transfer Protocol) parser
+
+ * ice: cleanup and minor bug fixes
+
+ * main: added external mutex for re_main() loop
+
+ * sdp: added sdp_media_set_alt_protos()
+ added sdp_media_proto()
+
+ * stun: make API compatible with C++
+ fix endianess-bug in STUN attributes
+
+ * sys: sys_rel_get() detect new kernels
+
+ * tls: fingerprint: add SHA-256
+ proper error handling, call ERR_clear_error()
+
+
+2013-05-05 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.3
+
+ * bfcp: added udp
+
+ * dns: added doxygen comments
+
+ * fmt: added str_cmp()
+
+ * mbuf: make mbuf_get_left() and mbuf_get_space() more robust
+ added mbuf_fill()
+
+ * mod: fixed a bug in mod_find()
+
+ * mqueue: move handler to mqueue_alloc()
+
+ * sa: fix building on some Windows platforms
+
+ * sdp: added sdp_session_lbandwidth()
+ added sdp_media_set_fmt_ignore()
+
+ * stun: make stun_msg_vencode() public
+ unlink element from attribute-list in destructor
+
+ * tcp: make fd handling more robust
+
+ * tls: added tls_get_remote_fingerprint()
+
+
+2012-08-10 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.2
+
+ * added debian build
+
+ * build: fix building for Ubuntu 12.04
+
+ * re_types: increase ERRNO values
+
+ * fmt: re_printf() add support for %m to print errno description
+ added str_error()
+
+ * hash: added hash_clear()
+
+ * list: added list_clear()
+
+ * net: added net_if_getlinklocal()
+
+ * rtp: added rtcp_set_srate_tx/rx()
+ rtcp_msg_print(): add all types
+
+ * sa: added sa_print_addr()
+
+ * sdp: added media encode handler
+ sdp_format_add(): added fmtp encode handler (breaks API)
+
+ * sip: handle merged SIP requests (482 Loop Detected)
+ added doxygen comments
+
+ * sipevent: fix bug in handler argument
+
+ * sys: added sys_username()
+ added fs_mkdir() and fs_gethome()
+
+ * tcp: added tcp_conn_txqsz()
+ fix enqueue buffer size
+ handle scopeid for IPv6 linklocal
+
+ * tmr: added tmr_status %H handler
+
+ * udp: handle scopeid for IPv6 linklocal
+
+
+2012-04-21 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.1
+
+ * updated doxygen comments for sdp and tls
+
+ * dns: dnsc_srv_set: copy DNS servers to fixed-size array
+
+ * fmt: added str_isset()
+ added fmt_param_exists()
+
+ * rtp: fix lock protection of RTCP txstat during read
+
+ * sdp: sdp_media_align_formats: move unsupported codecs to end of list
+
+ * sip: limit startline to max 8192 bytes
+ limit tcp buffersize to max 65536 bytes
+
+ * tcp: limit the size of the tcp send queue
+
+
+2011-12-25 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.4.0
+
+ * updated doxygen comments
+
+ * build: add support for CMake (thanks to Stefan Radomski)
+ clean up OS and ARCH detection
+
+ * dns: fix potential infinite loop in dname decode
+
+ * sip: change struct sip_via transp to enum sip_transp (breaks API)
+ added sip_transp_isladdr() and sip_transp_port()
+ added sip_dialog_fork(), sip_dialog_lseq(),
+ sip_dialog_established(), sip_dialog_cmp_half()
+
+ * sipevent: new module for SIP Event framework (RFC 3265, RFC 3515)
+
+ * sys: add portable sys_usleep() and sys_msleep()
+
+ * tcp: add tcp_send_helper()
+
+ * tls: add support for DTLSv1 (Datagram TLS)
+ tls_alloc: add tls_method and layer (breaks API)
+ tls_tcp: use custom BIO to send data
+
+ * tmr: optimize tmr_start() where delay == 0
+
+ * turn: add stun_msg to turnc handler (breaks API)
+
+ * udp: add udp_send_helper()
+
+
+2011-09-07 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.3.0
+
+ * build support for native mingw32 (thanks to Michael Erskine)
+
+ * bfcp: new module for The Binary Floor Control Protocol (RFC 4582)
+
+ * g711: module moved to librem
+
+ * sipreg: fix a bug in failwait() calculation
+
+ * stun: add support for STUNS (secure STUN)
+
+ * tcp: added tcp_set_handlers()
+
+ * turn: added send/recv functions
+
+
+2011-05-20 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.2.0
+
+ * updated doxygen comments
+
+ * conf: added conf_get_bool()
+
+ * dns: fixed a bug in get_resolv_dns()
+
+ * fmt: added pl_x64() pl_float() fmt_gmtime()
+
+ * hash: added hash_valid_size()
+
+ * httpauth: clean up API
+
+ * ice: many improvements and bugfixes
+
+ * main: fix a bug if re_main() fails
+
+ * mbuf: added mbuf_debug()
+
+ * natbd: fixed some race conditions and memory leaks
+
+ * rtp: added rtcp_enable_mux() (RFC 5761; RTP and RTCP multiplexing)
+
+ * sdp: fixed setting RTCP port if RTP port is zero
+
+ * sip: added support for SIP Outbound (RFC 5626)
+ added sip_msg_hdr_count() sip_msg_xhdr_count()
+ added sip_msg_hdr_has_value() sip_msg_xhdr_has_value()
+ added sip_auth_reset()
+ handle multiple authenticate headers with equal realm value
+ fixed a bug with loose-routing in Route header
+ fixed decoding of Via header
+
+ * sipreg: added support for SIP Outbound (breaks API compatibility)
+
+ * sipsess: fix a bug in sipsess_reject() if fmt is NULL
+
+ * tcp: update tcp_register_helper() (breaks API)
+
+ * tmr: removed tmrl from 'struct tmr' (breaks ABI)
+ added tmr_isrunning()
+
+ * udp: update udp_register_helper() (breaks API)
+
+ * uri: fix optional username in uri_decode()
+
+
+2010-11-05 Alfred E. Heggestad <aeh@db.org>
+
+ * Version 0.1.0
+
+ * Initial Release
diff --git a/docs/TODO b/docs/TODO
new file mode 100644
index 0000000..6f5d897
--- /dev/null
+++ b/docs/TODO
@@ -0,0 +1,8 @@
+TODO
+
+-------------------------------------------------------------------------------
+Version v0.x.y
+
+ tmr: scaling using binary heap or hash
+
+-------------------------------------------------------------------------------
diff --git a/docs/main.dox b/docs/main.dox
new file mode 100644
index 0000000..ec71c54
--- /dev/null
+++ b/docs/main.dox
@@ -0,0 +1,11 @@
+/**
+ * \mainpage libre Development Documentation
+ *
+ * Development documentation for libre
+ *
+ *
+ * \include README.md
+ *
+ *
+ * \section modules modules
+ */
diff --git a/docs/symbols.txt b/docs/symbols.txt
new file mode 100644
index 0000000..60f9c40
--- /dev/null
+++ b/docs/symbols.txt
@@ -0,0 +1,137 @@
+BIO_new@@OPENSSL_0.9.8
+BIO_new_socket@@OPENSSL_0.9.8
+BIO_s_mem@@OPENSSL_0.9.8
+BIO_write@@OPENSSL_0.9.8
+ERR_free_strings@@OPENSSL_0.9.8
+ERR_get_error@@OPENSSL_0.9.8
+EVP_sha1@@OPENSSL_0.9.8
+HMAC@@OPENSSL_0.9.8
+MD5@@OPENSSL_0.9.8
+RAND_bytes@@OPENSSL_0.9.8
+SSL_CTX_free@@OPENSSL_0.9.8
+SSL_CTX_load_verify_locations@@OPENSSL_0.9.8
+SSL_CTX_new@@OPENSSL_0.9.8
+SSL_CTX_set_default_passwd_cb@@OPENSSL_0.9.8
+SSL_CTX_set_default_passwd_cb_userdata@@OPENSSL_0.9.8
+SSL_CTX_use_PrivateKey_file@@OPENSSL_0.9.8
+SSL_CTX_use_certificate_chain_file@@OPENSSL_0.9.8
+SSL_accept@@OPENSSL_0.9.8
+SSL_connect@@OPENSSL_0.9.8
+SSL_free@@OPENSSL_0.9.8
+SSL_get_error@@OPENSSL_0.9.8
+SSL_get_peer_certificate@@OPENSSL_0.9.8
+SSL_get_verify_result@@OPENSSL_0.9.8
+SSL_library_init@@OPENSSL_0.9.8
+SSL_load_error_strings@@OPENSSL_0.9.8
+SSL_new@@OPENSSL_0.9.8
+SSL_read@@OPENSSL_0.9.8
+SSL_set_bio@@OPENSSL_0.9.8
+SSL_shutdown@@OPENSSL_0.9.8
+SSL_state@@OPENSSL_0.9.8
+SSL_write@@OPENSSL_0.9.8
+SSLv23_method@@OPENSSL_0.9.8
+X509_NAME_get_text_by_NID@@OPENSSL_0.9.8
+X509_get_subject_name@@OPENSSL_0.9.8
+__ctype_b_loc@@GLIBC_2.3
+__errno_location@@GLIBC_2.2.5
+__isinf@@GLIBC_2.2.5
+__isnan@@GLIBC_2.2.5
+__isoc99_fscanf@@GLIBC_2.7
+__res_close@@GLIBC_2.2.5
+__res_init@@GLIBC_2.2.5
+__res_state@@GLIBC_2.2.5
+__sysv_signal@@GLIBC_2.2.5
+accept@@GLIBC_2.2.5
+atoi@@GLIBC_2.2.5
+bind@@GLIBC_2.2.5
+chdir@@GLIBC_2.2.5
+close@@GLIBC_2.2.5
+connect@@GLIBC_2.2.5
+crc32
+ctime@@GLIBC_2.2.5
+dlclose@@GLIBC_2.2.5
+dlerror@@GLIBC_2.2.5
+dlopen@@GLIBC_2.2.5
+dlsym@@GLIBC_2.2.5
+epoll_create@@GLIBC_2.3.2
+epoll_ctl@@GLIBC_2.3.2
+epoll_wait@@GLIBC_2.3.2
+exit@@GLIBC_2.2.5
+fclose@@GLIBC_2.2.5
+fcntl@@GLIBC_2.2.5
+fflush@@GLIBC_2.2.5
+fopen@@GLIBC_2.2.5
+fork@@GLIBC_2.2.5
+free@@GLIBC_2.2.5
+freeaddrinfo@@GLIBC_2.2.5
+freeifaddrs@@GLIBC_2.3
+freopen@@GLIBC_2.2.5
+fwrite@@GLIBC_2.2.5
+gai_strerror@@GLIBC_2.2.5
+getaddrinfo@@GLIBC_2.2.5
+gethostbyname@@GLIBC_2.2.5
+gethostname@@GLIBC_2.2.5
+getifaddrs@@GLIBC_2.3
+getpeername@@GLIBC_2.2.5
+getpid@@GLIBC_2.2.5
+getsockname@@GLIBC_2.2.5
+getsockopt@@GLIBC_2.2.5
+gettimeofday@@GLIBC_2.2.5
+htonl@@GLIBC_2.2.5
+htons@@GLIBC_2.2.5
+if_indextoname@@GLIBC_2.2.5
+inet_ntop@@GLIBC_2.2.5
+inet_pton@@GLIBC_2.2.5
+ioctl@@GLIBC_2.2.5
+listen@@GLIBC_2.2.5
+malloc@@GLIBC_2.2.5
+memcmp@@GLIBC_2.2.5
+memcpy@@GLIBC_2.2.5
+memmove@@GLIBC_2.2.5
+memset@@GLIBC_2.2.5
+ntohl@@GLIBC_2.2.5
+ntohs@@GLIBC_2.2.5
+open@@GLIBC_2.2.5
+pipe@@GLIBC_2.2.5
+poll@@GLIBC_2.2.5
+pthread_getspecific@@GLIBC_2.2.5
+pthread_key_create@@GLIBC_2.2.5
+pthread_mutex_init@@GLIBC_2.2.5
+pthread_mutex_lock@@GLIBC_2.2.5
+pthread_mutex_unlock@@GLIBC_2.2.5
+pthread_once@@GLIBC_2.2.5
+pthread_rwlock_destroy@@GLIBC_2.2.5
+pthread_rwlock_init@@GLIBC_2.2.5
+pthread_rwlock_rdlock@@GLIBC_2.2.5
+pthread_rwlock_tryrdlock@@GLIBC_2.2.5
+pthread_rwlock_trywrlock@@GLIBC_2.2.5
+pthread_rwlock_unlock@@GLIBC_2.2.5
+pthread_rwlock_wrlock@@GLIBC_2.2.5
+pthread_self@@GLIBC_2.2.5
+pthread_setspecific@@GLIBC_2.2.5
+read@@GLIBC_2.2.5
+realloc@@GLIBC_2.2.5
+recv@@GLIBC_2.2.5
+recvfrom@@GLIBC_2.2.5
+select@@GLIBC_2.2.5
+send@@GLIBC_2.2.5
+sendto@@GLIBC_2.2.5
+setrlimit@@GLIBC_2.2.5
+setsid@@GLIBC_2.2.5
+setsockopt@@GLIBC_2.2.5
+socket@@GLIBC_2.2.5
+srand@@GLIBC_2.2.5
+stderr@@GLIBC_2.2.5
+stdin@@GLIBC_2.2.5
+stdout@@GLIBC_2.2.5
+strcasecmp@@GLIBC_2.2.5
+strcmp@@GLIBC_2.2.5
+strerror@@GLIBC_2.2.5
+strlen@@GLIBC_2.2.5
+strncasecmp@@GLIBC_2.2.5
+strncpy@@GLIBC_2.2.5
+time@@GLIBC_2.2.5
+tolower@@GLIBC_2.2.5
+umask@@GLIBC_2.2.5
+uname@@GLIBC_2.2.5
+write@@GLIBC_2.2.5
diff --git a/include/re.h b/include/re.h
new file mode 100644
index 0000000..942083c
--- /dev/null
+++ b/include/re.h
@@ -0,0 +1,68 @@
+/**
+ * @file re.h Wrapper for all header files
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#ifndef RE_H__
+#define RE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Basic types */
+#include "re_types.h"
+#include "re_fmt.h"
+#include "re_mbuf.h"
+#include "re_msg.h"
+#include "re_list.h"
+#include "re_sa.h"
+
+/* Library modules */
+#include "re_aes.h"
+#include "re_base64.h"
+#include "re_bfcp.h"
+#include "re_conf.h"
+#include "re_crc32.h"
+#include "re_dns.h"
+#include "re_hash.h"
+#include "re_hmac.h"
+#include "re_http.h"
+#include "re_httpauth.h"
+#include "re_ice.h"
+#include "re_jbuf.h"
+#include "re_lock.h"
+#include "re_main.h"
+#include "re_md5.h"
+#include "re_mem.h"
+#include "re_mod.h"
+#include "re_mqueue.h"
+#include "re_net.h"
+#include "re_odict.h"
+#include "re_json.h"
+#include "re_rtmp.h"
+#include "re_rtp.h"
+#include "re_sdp.h"
+#include "re_uri.h"
+#include "re_sip.h"
+#include "re_sipevent.h"
+#include "re_sipreg.h"
+#include "re_sipsess.h"
+#include "re_stun.h"
+#include "re_natbd.h"
+#include "re_srtp.h"
+#include "re_sys.h"
+#include "re_tcp.h"
+#include "re_telev.h"
+#include "re_tmr.h"
+#include "re_tls.h"
+#include "re_turn.h"
+#include "re_udp.h"
+#include "re_websock.h"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/re_aes.h b/include/re_aes.h
new file mode 100644
index 0000000..4559bad
--- /dev/null
+++ b/include/re_aes.h
@@ -0,0 +1,27 @@
+/**
+ * @file re_aes.h Interface to AES (Advanced Encryption Standard)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifndef AES_BLOCK_SIZE
+#define AES_BLOCK_SIZE 16
+#endif
+
+/** AES mode */
+enum aes_mode {
+ AES_MODE_CTR, /**< AES Counter mode (CTR) */
+ AES_MODE_GCM, /**< AES Galois Counter Mode (GCM) */
+};
+
+struct aes;
+
+int aes_alloc(struct aes **stp, enum aes_mode mode,
+ const uint8_t *key, size_t key_bits,
+ const uint8_t *iv);
+void aes_set_iv(struct aes *aes, const uint8_t *iv);
+int aes_encr(struct aes *aes, uint8_t *out, const uint8_t *in, size_t len);
+int aes_decr(struct aes *aes, uint8_t *out, const uint8_t *in, size_t len);
+int aes_get_authtag(struct aes *aes, uint8_t *tag, size_t taglen);
+int aes_authenticate(struct aes *aes, const uint8_t *tag, size_t taglen);
diff --git a/include/re_base64.h b/include/re_base64.h
new file mode 100644
index 0000000..dce7682
--- /dev/null
+++ b/include/re_base64.h
@@ -0,0 +1,10 @@
+/**
+ * @file re_base64.h Interface to Base64 encoding/decoding functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+int base64_encode(const uint8_t *in, size_t ilen, char *out, size_t *olen);
+int base64_print(struct re_printf *pf, const uint8_t *ptr, size_t len);
+int base64_decode(const char *in, size_t ilen, uint8_t *out, size_t *olen);
diff --git a/include/re_bfcp.h b/include/re_bfcp.h
new file mode 100644
index 0000000..cfeefd1
--- /dev/null
+++ b/include/re_bfcp.h
@@ -0,0 +1,286 @@
+/**
+ * @file re_bfcp.h Interface to Binary Floor Control Protocol (BFCP)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** BFCP Versions */
+enum {
+ BFCP_VER1 = 1,
+ BFCP_VER2 = 2,
+};
+
+/** BFCP Primitives */
+enum bfcp_prim {
+ BFCP_FLOOR_REQUEST = 1,
+ BFCP_FLOOR_RELEASE = 2,
+ BFCP_FLOOR_REQUEST_QUERY = 3,
+ BFCP_FLOOR_REQUEST_STATUS = 4,
+ BFCP_USER_QUERY = 5,
+ BFCP_USER_STATUS = 6,
+ BFCP_FLOOR_QUERY = 7,
+ BFCP_FLOOR_STATUS = 8,
+ BFCP_CHAIR_ACTION = 9,
+ BFCP_CHAIR_ACTION_ACK = 10,
+ BFCP_HELLO = 11,
+ BFCP_HELLO_ACK = 12,
+ BFCP_ERROR = 13,
+ BFCP_FLOOR_REQ_STATUS_ACK = 14,
+ BFCP_FLOOR_STATUS_ACK = 15,
+ BFCP_GOODBYE = 16,
+ BFCP_GOODBYE_ACK = 17,
+};
+
+/** BFCP Attributes */
+enum bfcp_attrib {
+ BFCP_BENEFICIARY_ID = 1,
+ BFCP_FLOOR_ID = 2,
+ BFCP_FLOOR_REQUEST_ID = 3,
+ BFCP_PRIORITY = 4,
+ BFCP_REQUEST_STATUS = 5,
+ BFCP_ERROR_CODE = 6,
+ BFCP_ERROR_INFO = 7,
+ BFCP_PART_PROV_INFO = 8,
+ BFCP_STATUS_INFO = 9,
+ BFCP_SUPPORTED_ATTRS = 10,
+ BFCP_SUPPORTED_PRIMS = 11,
+ BFCP_USER_DISP_NAME = 12,
+ BFCP_USER_URI = 13,
+ /* grouped: */
+ BFCP_BENEFICIARY_INFO = 14,
+ BFCP_FLOOR_REQ_INFO = 15,
+ BFCP_REQUESTED_BY_INFO = 16,
+ BFCP_FLOOR_REQ_STATUS = 17,
+ BFCP_OVERALL_REQ_STATUS = 18,
+
+ /** Mandatory Attribute */
+ BFCP_MANDATORY = 1<<7,
+ /** Encode Handler */
+ BFCP_ENCODE_HANDLER = 1<<8,
+};
+
+/** BFCP Request Status */
+enum bfcp_reqstat {
+ BFCP_PENDING = 1,
+ BFCP_ACCEPTED = 2,
+ BFCP_GRANTED = 3,
+ BFCP_DENIED = 4,
+ BFCP_CANCELLED = 5,
+ BFCP_RELEASED = 6,
+ BFCP_REVOKED = 7
+};
+
+/** BFCP Error Codes */
+enum bfcp_err {
+ BFCP_CONF_NOT_EXIST = 1,
+ BFCP_USER_NOT_EXIST = 2,
+ BFCP_UNKNOWN_PRIM = 3,
+ BFCP_UNKNOWN_MAND_ATTR = 4,
+ BFCP_UNAUTH_OPERATION = 5,
+ BFCP_INVALID_FLOOR_ID = 6,
+ BFCP_FLOOR_REQ_ID_NOT_EXIST = 7,
+ BFCP_MAX_FLOOR_REQ_REACHED = 8,
+ BFCP_USE_TLS = 9,
+ BFCP_PARSE_ERROR = 10,
+ BFCP_USE_DTLS = 11,
+ BFCP_UNSUPPORTED_VERSION = 12,
+ BFCP_BAD_LENGTH = 13,
+ BFCP_GENERIC_ERROR = 14,
+};
+
+/** BFCP Priority */
+enum bfcp_priority {
+ BFCP_PRIO_LOWEST = 0,
+ BFCP_PRIO_LOW = 1,
+ BFCP_PRIO_NORMAL = 2,
+ BFCP_PRIO_HIGH = 3,
+ BFCP_PRIO_HIGHEST = 4
+};
+
+/** BFCP Transport */
+enum bfcp_transp {
+ BFCP_UDP,
+ BFCP_DTLS,
+};
+
+/** BFCP Request Status */
+struct bfcp_reqstatus {
+ enum bfcp_reqstat status;
+ uint8_t qpos;
+};
+
+/** BFCP Error Code */
+struct bfcp_errcode {
+ enum bfcp_err code;
+ uint8_t *details; /* optional */
+ size_t len;
+};
+
+/** BFCP Supported Attributes */
+struct bfcp_supattr {
+ enum bfcp_attrib *attrv;
+ size_t attrc;
+};
+
+/** BFCP Supported Primitives */
+struct bfcp_supprim {
+ enum bfcp_prim *primv;
+ size_t primc;
+};
+
+/** BFCP Attribute */
+struct bfcp_attr {
+ struct le le;
+ struct list attrl;
+ enum bfcp_attrib type;
+ bool mand;
+ union bfcp_union {
+ /* generic types */
+ char *str;
+ uint16_t u16;
+
+ /* actual attributes */
+ uint16_t beneficiaryid;
+ uint16_t floorid;
+ uint16_t floorreqid;
+ enum bfcp_priority priority;
+ struct bfcp_reqstatus reqstatus;
+ struct bfcp_errcode errcode;
+ char *errinfo;
+ char *partprovinfo;
+ char *statusinfo;
+ struct bfcp_supattr supattr;
+ struct bfcp_supprim supprim;
+ char *userdname;
+ char *useruri;
+ uint16_t reqbyid;
+ } v;
+};
+
+/** BFCP unknown attributes */
+struct bfcp_unknown_attr {
+ uint8_t typev[16];
+ size_t typec;
+};
+
+/** BFCP Message */
+struct bfcp_msg {
+ struct bfcp_unknown_attr uma;
+ struct sa src;
+ uint8_t ver;
+ unsigned r:1;
+ unsigned f:1;
+ enum bfcp_prim prim;
+ uint16_t len;
+ uint32_t confid;
+ uint16_t tid;
+ uint16_t userid;
+ struct list attrl;
+};
+
+struct tls;
+struct bfcp_conn;
+
+
+/**
+ * Defines the BFCP encode handler
+ *
+ * @param mb Mbuf to encode into
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+typedef int (bfcp_encode_h)(struct mbuf *mb, void *arg);
+
+/** BFCP Encode */
+struct bfcp_encode {
+ bfcp_encode_h *ench;
+ void *arg;
+};
+
+
+/**
+ * Defines the BFCP attribute handler
+ *
+ * @param attr BFCP attribute
+ * @param arg Handler argument
+ *
+ * @return True to stop processing, false to continue
+ */
+typedef bool (bfcp_attr_h)(const struct bfcp_attr *attr, void *arg);
+
+
+/**
+ * Defines the BFCP receive handler
+ *
+ * @param msg BFCP message
+ * @param arg Handler argument
+ */
+typedef void (bfcp_recv_h)(const struct bfcp_msg *msg, void *arg);
+
+
+/**
+ * Defines the BFCP response handler
+ *
+ * @param err Error code
+ * @param msg BFCP message
+ * @param arg Handler argument
+ */
+typedef void (bfcp_resp_h)(int err, const struct bfcp_msg *msg, void *arg);
+
+
+/* attr */
+int bfcp_attrs_vencode(struct mbuf *mb, unsigned attrc, va_list *ap);
+int bfcp_attrs_encode(struct mbuf *mb, unsigned attrc, ...);
+struct bfcp_attr *bfcp_attr_subattr(const struct bfcp_attr *attr,
+ enum bfcp_attrib type);
+struct bfcp_attr *bfcp_attr_subattr_apply(const struct bfcp_attr *attr,
+ bfcp_attr_h *h, void *arg);
+int bfcp_attr_print(struct re_printf *pf, const struct bfcp_attr *attr);
+const char *bfcp_attr_name(enum bfcp_attrib type);
+const char *bfcp_reqstatus_name(enum bfcp_reqstat status);
+const char *bfcp_errcode_name(enum bfcp_err code);
+
+
+/* msg */
+int bfcp_msg_vencode(struct mbuf *mb, uint8_t ver, bool r, enum bfcp_prim prim,
+ uint32_t confid, uint16_t tid, uint16_t userid,
+ unsigned attrc, va_list *ap);
+int bfcp_msg_encode(struct mbuf *mb, uint8_t ver, bool r, enum bfcp_prim prim,
+ uint32_t confid, uint16_t tid, uint16_t userid,
+ unsigned attrc, ...);
+int bfcp_msg_decode(struct bfcp_msg **msgp, struct mbuf *mb);
+struct bfcp_attr *bfcp_msg_attr(const struct bfcp_msg *msg,
+ enum bfcp_attrib type);
+struct bfcp_attr *bfcp_msg_attr_apply(const struct bfcp_msg *msg,
+ bfcp_attr_h *h, void *arg);
+int bfcp_msg_print(struct re_printf *pf, const struct bfcp_msg *msg);
+const char *bfcp_prim_name(enum bfcp_prim prim);
+
+
+/* conn */
+int bfcp_listen(struct bfcp_conn **bcp, enum bfcp_transp tp, struct sa *laddr,
+ struct tls *tls, bfcp_recv_h *recvh, void *arg);
+void *bfcp_sock(const struct bfcp_conn *bc);
+
+
+/* request */
+int bfcp_request(struct bfcp_conn *bc, const struct sa *dst, uint8_t ver,
+ enum bfcp_prim prim, uint32_t confid, uint16_t userid,
+ bfcp_resp_h *resph, void *arg, unsigned attrc, ...);
+
+
+/* notify */
+int bfcp_notify(struct bfcp_conn *bc, const struct sa *dst, uint8_t ver,
+ enum bfcp_prim prim, uint32_t confid, uint16_t userid,
+ unsigned attrc, ...);
+
+
+/* reply */
+int bfcp_reply(struct bfcp_conn *bc, const struct bfcp_msg *req,
+ enum bfcp_prim prim, unsigned attrc, ...);
+int bfcp_edreply(struct bfcp_conn *bc, const struct bfcp_msg *req,
+ enum bfcp_err code, const uint8_t *details, size_t len);
+int bfcp_ereply(struct bfcp_conn *bc, const struct bfcp_msg *req,
+ enum bfcp_err code);
diff --git a/include/re_bitv.h b/include/re_bitv.h
new file mode 100644
index 0000000..7ed774a
--- /dev/null
+++ b/include/re_bitv.h
@@ -0,0 +1,73 @@
+/**
+ * @file re_bitv.h Interface to Bit Vector functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+typedef unsigned long bitv_t;
+
+enum {
+ BITS_SZ = (8*sizeof(bitv_t)),
+ BITS_MASK = (BITS_SZ-1)
+};
+
+#define BITV_NELEM(nbits) (((nbits) + (BITS_SZ) - 1) / (BITS_SZ))
+#define BITV_DECL(name, nbits) bitv_t (name)[BITV_NELEM(nbits)]
+
+
+static inline uint32_t index2offset(uint32_t i)
+{
+ return i/BITS_SZ;
+}
+
+static inline bitv_t index2bit(uint32_t i)
+{
+ return (bitv_t)1<<(i & BITS_MASK);
+}
+
+
+/*
+ * Public API
+ */
+
+
+static inline void bitv_init(bitv_t *bv, uint32_t nbits, bool val)
+{
+ memset(bv, val?0xff:0x00, BITV_NELEM(nbits)*sizeof(bitv_t));
+}
+
+static inline void bitv_set(bitv_t *bv, uint32_t i)
+{
+ bv[index2offset(i)] |= index2bit(i);
+}
+
+static inline void bitv_clr(bitv_t *bv, uint32_t i)
+{
+ bv[index2offset(i)] &= ~index2bit(i);
+}
+
+static inline void bitv_assign(bitv_t *bv, uint32_t i, bool val)
+{
+ if (val)
+ bitv_set(bv, i);
+ else
+ bitv_clr(bv, i);
+}
+
+static inline bool bitv_val(const bitv_t *bv, uint32_t i)
+{
+ return 0 != (bv[index2offset(i)] & index2bit(i));
+}
+
+static inline void bitv_toggle(bitv_t *bv, uint32_t i)
+{
+ bv[index2offset(i)] ^= index2bit(i);
+}
+
+static inline void bitv_assign_range(bitv_t *bv, uint32_t i, uint32_t n,
+ bool val)
+{
+ while (n--)
+ bitv_assign(bv, i+n, val);
+}
diff --git a/include/re_conf.h b/include/re_conf.h
new file mode 100644
index 0000000..8c5d008
--- /dev/null
+++ b/include/re_conf.h
@@ -0,0 +1,20 @@
+/**
+ * @file re_conf.h Interface to configuration
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct conf;
+
+typedef int (conf_h)(const struct pl *val, void *arg);
+
+int conf_alloc(struct conf **confp, const char *filename);
+int conf_alloc_buf(struct conf **confp, const uint8_t *buf, size_t sz);
+int conf_get(const struct conf *conf, const char *name, struct pl *pl);
+int conf_get_str(const struct conf *conf, const char *name, char *str,
+ size_t size);
+int conf_get_u32(const struct conf *conf, const char *name, uint32_t *num);
+int conf_get_bool(const struct conf *conf, const char *name, bool *val);
+int conf_apply(const struct conf *conf, const char *name,
+ conf_h *ch, void *arg);
diff --git a/include/re_crc32.h b/include/re_crc32.h
new file mode 100644
index 0000000..06bbce2
--- /dev/null
+++ b/include/re_crc32.h
@@ -0,0 +1,12 @@
+/**
+ * @file re_crc32.h Interface to CRC-32 functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifdef USE_ZLIB
+#include <zlib.h>
+#else
+uint32_t crc32(uint32_t crc, const void *buf, uint32_t size);
+#endif
diff --git a/include/re_dbg.h b/include/re_dbg.h
new file mode 100644
index 0000000..a73670e
--- /dev/null
+++ b/include/re_dbg.h
@@ -0,0 +1,193 @@
+/**
+ * @file re_dbg.h Interface to debugging module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Debug levels */
+enum {
+ DBG_EMERG = 0, /**< System is unusable */
+ DBG_ALERT = 1, /**< Action must be taken immediately */
+ DBG_CRIT = 2, /**< Critical conditions */
+ DBG_ERR = 3, /**< Error conditions */
+ DBG_WARNING = 4, /**< Warning conditions */
+ DBG_NOTICE = 5, /**< Normal but significant condition */
+ DBG_INFO = 6, /**< Informational */
+ DBG_DEBUG = 7 /**< Debug-level messages */
+};
+
+
+/**
+ * @def DEBUG_MODULE
+ *
+ * Module name defined by application
+ */
+
+/**
+ * @def DEBUG_LEVEL
+ *
+ * Debug level defined by application
+ */
+
+#ifndef DEBUG_MODULE
+# warning "DEBUG_MODULE is not defined"
+#define DEBUG_MODULE "?"
+#endif
+
+#ifndef DEBUG_LEVEL
+# warning "DEBUG_LEVEL is not defined"
+#define DEBUG_LEVEL 7
+#endif
+
+
+/**
+ * @def DEBUG_WARNING(...)
+ *
+ * Print warning message
+ */
+
+/**
+ * @def DEBUG_NOTICE(...)
+ *
+ * Print notice message
+ */
+
+/**
+ * @def DEBUG_INFO(...)
+ *
+ * Print info message
+ */
+
+/**
+ * @def DEBUG_PRINTF(...)
+ *
+ * Print debug message
+ */
+
+
+/* Check for ISO C99 variable argument macros */
+#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) \
+ || (__GNUC__ >= 3)
+
+#if (DEBUG_LEVEL >= 4)
+#define DEBUG_WARNING(...) \
+ dbg_printf(DBG_WARNING, DEBUG_MODULE ": " __VA_ARGS__)
+#else
+#define DEBUG_WARNING(...)
+#endif
+
+#if (DEBUG_LEVEL >= 5)
+#define DEBUG_NOTICE(...) \
+ dbg_printf(DBG_NOTICE, DEBUG_MODULE ": " __VA_ARGS__)
+#else
+#define DEBUG_NOTICE(...)
+#endif
+
+#if (DEBUG_LEVEL >= 6)
+#define DEBUG_INFO(...) \
+ dbg_printf(DBG_INFO, DEBUG_MODULE ": " __VA_ARGS__)
+#else
+#define DEBUG_INFO(...)
+#endif
+
+#if (DEBUG_LEVEL >= 7)
+#define DEBUG_PRINTF(...) \
+ dbg_printf(DBG_DEBUG, DEBUG_MODULE ": " __VA_ARGS__)
+#else
+#define DEBUG_PRINTF(...)
+#endif
+
+/* GNU extensions for variable argument macros */
+#elif defined(__GNUC__)
+
+#if (DEBUG_LEVEL >= 4)
+#define DEBUG_WARNING(a...) dbg_printf(DBG_WARNING, DEBUG_MODULE ": " a)
+#else
+#define DEBUG_WARNING(a...)
+#endif
+
+#if (DEBUG_LEVEL >= 5)
+#define DEBUG_NOTICE(a...) dbg_printf(DBG_NOTICE, DEBUG_MODULE ": " a)
+#else
+#define DEBUG_NOTICE(a...)
+#endif
+
+#if (DEBUG_LEVEL >= 6)
+#define DEBUG_INFO(a...) dbg_printf(DBG_INFO, DEBUG_MODULE ": " a)
+#else
+#define DEBUG_INFO(a...)
+#endif
+
+#if (DEBUG_LEVEL >= 7)
+#define DEBUG_PRINTF(a...) dbg_printf(DBG_DEBUG, DEBUG_MODULE ": " a)
+#else
+#define DEBUG_PRINTF(a...)
+#endif
+
+/* No variable argument macros */
+#else
+
+#if (DEBUG_LEVEL >= 4)
+#define DEBUG_WARNING dbg_warning
+#else
+#define DEBUG_WARNING dbg_noprintf
+#endif
+
+#if (DEBUG_LEVEL >= 5)
+#define DEBUG_NOTICE dbg_notice
+#else
+#define DEBUG_NOTICE dbg_noprintf
+#endif
+
+#if (DEBUG_LEVEL >= 6)
+#define DEBUG_INFO dbg_info
+#else
+#define DEBUG_INFO dbg_noprintf
+#endif
+
+#if (DEBUG_LEVEL >= 7)
+#define DEBUG_PRINTF dbg_noprintf
+#else
+#define DEBUG_PRINTF dbg_noprintf
+#endif
+
+#endif
+
+
+/** Debug flags */
+enum dbg_flags {
+ DBG_NONE = 0, /**< No debug flags */
+ DBG_TIME = 1<<0, /**< Print timestamp flag */
+ DBG_ANSI = 1<<1, /**< Print ANSI color codes */
+ DBG_ALL = DBG_TIME|DBG_ANSI /**< All flags enabled */
+};
+
+
+/**
+ * Defines the debug print handler
+ *
+ * @param level Debug level
+ * @param p Debug string
+ * @param len String length
+ * @param arg Handler argument
+ */
+typedef void (dbg_print_h)(int level, const char *p, size_t len, void *arg);
+
+void dbg_init(int level, enum dbg_flags flags);
+void dbg_close(void);
+int dbg_logfile_set(const char *name);
+void dbg_handler_set(dbg_print_h *ph, void *arg);
+void dbg_printf(int level, const char *fmt, ...);
+void dbg_noprintf(const char *fmt, ...);
+void dbg_warning(const char *fmt, ...);
+void dbg_notice(const char *fmt, ...);
+void dbg_info(const char *fmt, ...);
+const char *dbg_level_str(int level);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/include/re_dns.h b/include/re_dns.h
new file mode 100644
index 0000000..73ecd08
--- /dev/null
+++ b/include/re_dns.h
@@ -0,0 +1,218 @@
+/**
+ * @file re_dns.h Interface to DNS module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+enum {
+ DNS_PORT = 53,
+ DNS_HEADER_SIZE = 12
+};
+
+
+/** DNS Opcodes */
+enum {
+ DNS_OPCODE_QUERY = 0,
+ DNS_OPCODE_IQUERY = 1,
+ DNS_OPCODE_STATUS = 2,
+ DNS_OPCODE_NOTIFY = 4
+};
+
+
+/** DNS Response codes */
+enum {
+ DNS_RCODE_OK = 0,
+ DNS_RCODE_FMT_ERR = 1,
+ DNS_RCODE_SRV_FAIL = 2,
+ DNS_RCODE_NAME_ERR = 3,
+ DNS_RCODE_NOT_IMPL = 4,
+ DNS_RCODE_REFUSED = 5,
+ DNS_RCODE_NOT_AUTH = 9
+};
+
+
+/** DNS Resource Record types */
+enum {
+ DNS_TYPE_A = 0x0001,
+ DNS_TYPE_NS = 0x0002,
+ DNS_TYPE_CNAME = 0x0005,
+ DNS_TYPE_SOA = 0x0006,
+ DNS_TYPE_PTR = 0x000c,
+ DNS_TYPE_MX = 0x000f,
+ DNS_TYPE_AAAA = 0x001c,
+ DNS_TYPE_SRV = 0x0021,
+ DNS_TYPE_NAPTR = 0x0023,
+ DNS_QTYPE_IXFR = 0x00fb,
+ DNS_QTYPE_AXFR = 0x00fc,
+ DNS_QTYPE_ANY = 0x00ff
+};
+
+
+/** DNS Classes */
+enum {
+ DNS_CLASS_IN = 0x0001,
+ DNS_QCLASS_ANY = 0x00ff
+};
+
+
+/** Defines a DNS Header */
+struct dnshdr {
+ uint16_t id;
+ bool qr;
+ uint8_t opcode;
+ bool aa;
+ bool tc;
+ bool rd;
+ bool ra;
+ uint8_t z;
+ uint8_t rcode;
+ uint16_t nq;
+ uint16_t nans;
+ uint16_t nauth;
+ uint16_t nadd;
+};
+
+
+/** Defines a DNS Resource Record (RR) */
+struct dnsrr {
+ struct le le;
+ struct le le_priv;
+ char *name;
+ uint16_t type;
+ uint16_t dnsclass;
+ int64_t ttl;
+ uint16_t rdlen;
+ union {
+ struct {
+ uint32_t addr;
+ } a;
+ struct {
+ char *nsdname;
+ } ns;
+ struct {
+ char *cname;
+ } cname;
+ struct {
+ char *mname;
+ char *rname;
+ uint32_t serial;
+ uint32_t refresh;
+ uint32_t retry;
+ uint32_t expire;
+ uint32_t ttlmin;
+ } soa;
+ struct {
+ char *ptrdname;
+ } ptr;
+ struct {
+ uint16_t pref;
+ char *exchange;
+ } mx;
+ struct {
+ uint8_t addr[16];
+ } aaaa;
+ struct {
+ uint16_t pri;
+ uint16_t weight;
+ uint16_t port;
+ char *target;
+ } srv;
+ struct {
+ uint16_t order;
+ uint16_t pref;
+ char *flags;
+ char *services;
+ char *regexp;
+ char *replace;
+ } naptr;
+ } rdata;
+};
+
+struct hash;
+
+/**
+ * Defines the DNS Query handler
+ *
+ * @param err 0 if success, otherwise errorcode
+ * @param hdr DNS Header
+ * @param ansl List of Answer records
+ * @param authl List of Authoritive records
+ * @param addl List of Additional records
+ * @param arg Handler argument
+ */
+typedef void(dns_query_h)(int err, const struct dnshdr *hdr,
+ struct list *ansl, struct list *authl,
+ struct list *addl, void *arg);
+
+/**
+ * Defines the DNS Resource Record list handler
+ *
+ * @param rr DNS Resource Record
+ * @param arg Handler argument
+ *
+ * @return True to stop traversing, False to continue
+ */
+typedef bool(dns_rrlist_h)(struct dnsrr *rr, void *arg);
+
+int dns_hdr_encode(struct mbuf *mb, const struct dnshdr *hdr);
+int dns_hdr_decode(struct mbuf *mb, struct dnshdr *hdr);
+const char *dns_hdr_opcodename(uint8_t opcode);
+const char *dns_hdr_rcodename(uint8_t rcode);
+struct dnsrr *dns_rr_alloc(void);
+int dns_rr_encode(struct mbuf *mb, const struct dnsrr *rr, int64_t ttl_offs,
+ struct hash *ht_dname, size_t start);
+int dns_rr_decode(struct mbuf *mb, struct dnsrr **rr, size_t start);
+bool dns_rr_cmp(const struct dnsrr *rr1, const struct dnsrr *rr2, bool rdata);
+const char *dns_rr_typename(uint16_t type);
+const char *dns_rr_classname(uint16_t dnsclass);
+int dns_rr_print(struct re_printf *pf, const struct dnsrr *rr);
+int dns_dname_encode(struct mbuf *mb, const char *name,
+ struct hash *ht_dname, size_t start, bool comp);
+int dns_dname_decode(struct mbuf *mb, char **name, size_t start);
+int dns_cstr_encode(struct mbuf *mb, const char *str);
+int dns_cstr_decode(struct mbuf *mb, char **str);
+void dns_rrlist_sort(struct list *rrl, uint16_t type, size_t key);
+void dns_rrlist_sort_addr(struct list *rrl, size_t key);
+struct dnsrr *dns_rrlist_apply(struct list *rrl, const char *name,
+ uint16_t type, uint16_t dnsclass,
+ bool recurse, dns_rrlist_h *rrlh, void *arg);
+struct dnsrr *dns_rrlist_apply2(struct list *rrl, const char *name,
+ uint16_t type1, uint16_t type2,
+ uint16_t dnsclass, bool recurse,
+ dns_rrlist_h *rrlh, void *arg);
+struct dnsrr *dns_rrlist_find(struct list *rrl, const char *name,
+ uint16_t type, uint16_t dnsclass, bool recurse);
+
+
+/* DNS Client */
+struct sa;
+struct dnsc;
+struct dns_query;
+
+/** DNS Client configuration */
+struct dnsc_conf {
+ uint32_t query_hash_size;
+ uint32_t tcp_hash_size;
+ uint32_t conn_timeout; /* in [ms] */
+ uint32_t idle_timeout; /* in [ms] */
+};
+
+int dnsc_alloc(struct dnsc **dcpp, const struct dnsc_conf *conf,
+ const struct sa *srvv, uint32_t srvc);
+int dnsc_srv_set(struct dnsc *dnsc, const struct sa *srvv, uint32_t srvc);
+int dnsc_query(struct dns_query **qp, struct dnsc *dnsc, const char *name,
+ uint16_t type, uint16_t dnsclass,
+ bool rd, dns_query_h *qh, void *arg);
+int dnsc_query_srv(struct dns_query **qp, struct dnsc *dnsc, const char *name,
+ uint16_t type, uint16_t dnsclass, int proto,
+ const struct sa *srvv, const uint32_t *srvc,
+ bool rd, dns_query_h *qh, void *arg);
+int dnsc_notify(struct dns_query **qp, struct dnsc *dnsc, const char *name,
+ uint16_t type, uint16_t dnsclass, const struct dnsrr *ans_rr,
+ int proto, const struct sa *srvv, const uint32_t *srvc,
+ dns_query_h *qh, void *arg);
+
+
+/* DNS System functions */
+int dns_srv_get(char *domain, size_t dsize, struct sa *srvv, uint32_t *n);
diff --git a/include/re_fmt.h b/include/re_fmt.h
new file mode 100644
index 0000000..55806bf
--- /dev/null
+++ b/include/re_fmt.h
@@ -0,0 +1,147 @@
+/**
+ * @file re_fmt.h Interface to formatted text functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#include <stdarg.h>
+#include <stdio.h>
+
+
+struct mbuf;
+
+
+/** Defines a pointer-length string type */
+struct pl {
+ const char *p; /**< Pointer to string */
+ size_t l; /**< Length of string */
+};
+
+/** Initialise a pointer-length object from a constant string */
+#define PL(s) {(s), sizeof((s))-1}
+
+/** Pointer-length Initializer */
+#define PL_INIT {NULL, 0}
+
+extern const struct pl pl_null;
+
+void pl_set_str(struct pl *pl, const char *str);
+void pl_set_mbuf(struct pl *pl, const struct mbuf *mb);
+uint32_t pl_u32(const struct pl *pl);
+uint32_t pl_x32(const struct pl *pl);
+uint64_t pl_u64(const struct pl *pl);
+uint64_t pl_x64(const struct pl *pl);
+double pl_float(const struct pl *pl);
+bool pl_isset(const struct pl *pl);
+int pl_strcpy(const struct pl *pl, char *str, size_t size);
+int pl_strdup(char **dst, const struct pl *src);
+int pl_dup(struct pl *dst, const struct pl *src);
+int pl_strcmp(const struct pl *pl, const char *str);
+int pl_strcasecmp(const struct pl *pl, const char *str);
+int pl_cmp(const struct pl *pl1, const struct pl *pl2);
+int pl_casecmp(const struct pl *pl1, const struct pl *pl2);
+const char *pl_strchr(const struct pl *pl, char c);
+
+/** Advance pl position/length by +/- N bytes */
+static inline void pl_advance(struct pl *pl, ssize_t n)
+{
+ pl->p += n;
+ pl->l -= n;
+}
+
+
+/* Formatted printing */
+
+/**
+ * Defines the re_vhprintf() print handler
+ *
+ * @param p String to print
+ * @param size Size of string to print
+ * @param arg Handler argument
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+typedef int(re_vprintf_h)(const char *p, size_t size, void *arg);
+
+/** Defines a print backend */
+struct re_printf {
+ re_vprintf_h *vph; /**< Print handler */
+ void *arg; /**< Handler agument */
+};
+
+/**
+ * Defines the %H print handler
+ *
+ * @param pf Print backend
+ * @param arg Handler argument
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+typedef int(re_printf_h)(struct re_printf *pf, void *arg);
+
+int re_vhprintf(const char *fmt, va_list ap, re_vprintf_h *vph, void *arg);
+int re_vfprintf(FILE *stream, const char *fmt, va_list ap);
+int re_vprintf(const char *fmt, va_list ap);
+int re_vsnprintf(char *str, size_t size, const char *fmt, va_list ap);
+int re_vsdprintf(char **strp, const char *fmt, va_list ap);
+
+int re_hprintf(struct re_printf *pf, const char *fmt, ...);
+int re_fprintf(FILE *stream, const char *fmt, ...);
+int re_printf(const char *fmt, ...);
+int re_snprintf(char *str, size_t size, const char *fmt, ...);
+int re_sdprintf(char **strp, const char *fmt, ...);
+
+
+/* Regular expressions */
+int re_regex(const char *ptr, size_t len, const char *expr, ...);
+
+
+/* Character functions */
+uint8_t ch_hex(char ch);
+
+
+/* String functions */
+int str_hex(uint8_t *hex, size_t len, const char *str);
+void str_ncpy(char *dst, const char *src, size_t n);
+int str_dup(char **dst, const char *src);
+int str_cmp(const char *s1, const char *s2);
+int str_casecmp(const char *s1, const char *s2);
+size_t str_len(const char *s);
+const char *str_error(int errnum, char *buf, size_t sz);
+
+
+/**
+ * Check if string is set
+ *
+ * @param s Zero-terminated string
+ *
+ * @return true if set, false if not set
+ */
+static inline bool str_isset(const char *s)
+{
+ return s && s[0] != '\0';
+}
+
+
+/* time */
+int fmt_gmtime(struct re_printf *pf, void *ts);
+int fmt_human_time(struct re_printf *pf, const uint32_t *seconds);
+
+
+void hexdump(FILE *f, const void *p, size_t len);
+
+
+/* param */
+typedef void (fmt_param_h)(const struct pl *name, const struct pl *val,
+ void *arg);
+
+bool fmt_param_exists(const struct pl *pl, const char *pname);
+bool fmt_param_get(const struct pl *pl, const char *pname, struct pl *val);
+void fmt_param_apply(const struct pl *pl, fmt_param_h *ph, void *arg);
+
+
+/* unicode */
+int utf8_encode(struct re_printf *pf, const char *str);
+int utf8_decode(struct re_printf *pf, const struct pl *pl);
+size_t utf8_byteseq(char u[4], unsigned cp);
diff --git a/include/re_hash.h b/include/re_hash.h
new file mode 100644
index 0000000..8858dbc
--- /dev/null
+++ b/include/re_hash.h
@@ -0,0 +1,33 @@
+/**
+ * @file re_hash.h Interface to hashmap table
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct hash;
+struct pl;
+
+
+int hash_alloc(struct hash **hp, uint32_t bsize);
+void hash_append(struct hash *h, uint32_t key, struct le *le, void *data);
+void hash_unlink(struct le *le);
+struct le *hash_lookup(const struct hash *h, uint32_t key, list_apply_h *ah,
+ void *arg);
+struct le *hash_apply(const struct hash *h, list_apply_h *ah, void *arg);
+struct list *hash_list(const struct hash *h, uint32_t key);
+uint32_t hash_bsize(const struct hash *h);
+void hash_flush(struct hash *h);
+void hash_clear(struct hash *h);
+uint32_t hash_valid_size(uint32_t size);
+
+
+/* Hash functions */
+uint32_t hash_joaat(const uint8_t *key, size_t len);
+uint32_t hash_joaat_ci(const char *str, size_t len);
+uint32_t hash_joaat_str(const char *str);
+uint32_t hash_joaat_str_ci(const char *str);
+uint32_t hash_joaat_pl(const struct pl *pl);
+uint32_t hash_joaat_pl_ci(const struct pl *pl);
+uint32_t hash_fast(const char *k, size_t len);
+uint32_t hash_fast_str(const char *str);
diff --git a/include/re_hmac.h b/include/re_hmac.h
new file mode 100644
index 0000000..25e7e2e
--- /dev/null
+++ b/include/re_hmac.h
@@ -0,0 +1,26 @@
+/**
+ * @file re_hmac.h Interface to HMAC functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+void hmac_sha1(const uint8_t *k, /* secret key */
+ size_t lk, /* length of the key in bytes */
+ const uint8_t *d, /* data */
+ size_t ld, /* length of data in bytes */
+ uint8_t* out, /* output buffer, at least "t" bytes */
+ size_t t);
+
+
+enum hmac_hash {
+ HMAC_HASH_SHA1,
+ HMAC_HASH_SHA256
+};
+
+struct hmac;
+
+int hmac_create(struct hmac **hmacp, enum hmac_hash hash,
+ const uint8_t *key, size_t key_len);
+int hmac_digest(struct hmac *hmac, uint8_t *md, size_t md_len,
+ const uint8_t *data, size_t data_len);
diff --git a/include/re_http.h b/include/re_http.h
new file mode 100644
index 0000000..2789442
--- /dev/null
+++ b/include/re_http.h
@@ -0,0 +1,171 @@
+/**
+ * @file re_http.h Hypertext Transfer Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** HTTP Header ID (perfect hash value) */
+enum http_hdrid {
+ HTTP_HDR_ACCEPT = 3186,
+ HTTP_HDR_ACCEPT_CHARSET = 24,
+ HTTP_HDR_ACCEPT_ENCODING = 708,
+ HTTP_HDR_ACCEPT_LANGUAGE = 2867,
+ HTTP_HDR_ACCEPT_RANGES = 3027,
+ HTTP_HDR_AGE = 742,
+ HTTP_HDR_ALLOW = 2429,
+ HTTP_HDR_AUTHORIZATION = 2503,
+ HTTP_HDR_CACHE_CONTROL = 2530,
+ HTTP_HDR_CONNECTION = 865,
+ HTTP_HDR_CONTENT_ENCODING = 580,
+ HTTP_HDR_CONTENT_LANGUAGE = 3371,
+ HTTP_HDR_CONTENT_LENGTH = 3861,
+ HTTP_HDR_CONTENT_LOCATION = 3927,
+ HTTP_HDR_CONTENT_MD5 = 406,
+ HTTP_HDR_CONTENT_RANGE = 2846,
+ HTTP_HDR_CONTENT_TYPE = 809,
+ HTTP_HDR_DATE = 1027,
+ HTTP_HDR_ETAG = 2392,
+ HTTP_HDR_EXPECT = 1550,
+ HTTP_HDR_EXPIRES = 1983,
+ HTTP_HDR_FROM = 1963,
+ HTTP_HDR_HOST = 3191,
+ HTTP_HDR_IF_MATCH = 2684,
+ HTTP_HDR_IF_MODIFIED_SINCE = 2187,
+ HTTP_HDR_IF_NONE_MATCH = 4030,
+ HTTP_HDR_IF_RANGE = 2220,
+ HTTP_HDR_IF_UNMODIFIED_SINCE = 962,
+ HTTP_HDR_LAST_MODIFIED = 2946,
+ HTTP_HDR_LOCATION = 2514,
+ HTTP_HDR_MAX_FORWARDS = 3549,
+ HTTP_HDR_PRAGMA = 1673,
+ HTTP_HDR_PROXY_AUTHENTICATE = 116,
+ HTTP_HDR_PROXY_AUTHORIZATION = 2363,
+ HTTP_HDR_RANGE = 4004,
+ HTTP_HDR_REFERER = 2991,
+ HTTP_HDR_RETRY_AFTER = 409,
+ HTTP_HDR_SEC_WEBSOCKET_ACCEPT = 2959,
+ HTTP_HDR_SEC_WEBSOCKET_EXTENSIONS = 2937,
+ HTTP_HDR_SEC_WEBSOCKET_KEY = 746,
+ HTTP_HDR_SEC_WEBSOCKET_PROTOCOL = 2076,
+ HTTP_HDR_SEC_WEBSOCKET_VERSION = 3158,
+ HTTP_HDR_SERVER = 973,
+ HTTP_HDR_TE = 2035,
+ HTTP_HDR_TRAILER = 2577,
+ HTTP_HDR_TRANSFER_ENCODING = 2115,
+ HTTP_HDR_UPGRADE = 717,
+ HTTP_HDR_USER_AGENT = 4064,
+ HTTP_HDR_VARY = 3076,
+ HTTP_HDR_VIA = 3961,
+ HTTP_HDR_WARNING = 2108,
+ HTTP_HDR_WWW_AUTHENTICATE = 2763,
+
+ HTTP_HDR_NONE = -1
+};
+
+
+/** HTTP Header */
+struct http_hdr {
+ struct le le; /**< Linked-list element */
+ struct pl name; /**< HTTP Header name */
+ struct pl val; /**< HTTP Header value */
+ enum http_hdrid id; /**< HTTP Header id (unique) */
+};
+
+/** HTTP Message */
+struct http_msg {
+ struct pl ver; /**< HTTP Version number */
+ struct pl met; /**< Request Method */
+ struct pl path; /**< Request path/resource */
+ struct pl prm; /**< Request parameters */
+ uint16_t scode; /**< Response Status code */
+ struct pl reason; /**< Response Reason phrase */
+ struct list hdrl; /**< List of HTTP headers (struct http_hdr) */
+ struct msg_ctype ctyp; /**< Content-type */
+ struct mbuf *_mb; /**< Buffer containing the HTTP message */
+ struct mbuf *mb; /**< Buffer containing the HTTP body */
+ uint32_t clen; /**< Content length */
+};
+
+typedef bool(http_hdr_h)(const struct http_hdr *hdr, void *arg);
+
+int http_msg_decode(struct http_msg **msgp, struct mbuf *mb, bool req);
+
+
+const struct http_hdr *http_msg_hdr(const struct http_msg *msg,
+ enum http_hdrid id);
+const struct http_hdr *http_msg_hdr_apply(const struct http_msg *msg,
+ bool fwd, enum http_hdrid id,
+ http_hdr_h *h, void *arg);
+const struct http_hdr *http_msg_xhdr(const struct http_msg *msg,
+ const char *name);
+const struct http_hdr *http_msg_xhdr_apply(const struct http_msg *msg,
+ bool fwd, const char *name,
+ http_hdr_h *h, void *arg);
+uint32_t http_msg_hdr_count(const struct http_msg *msg, enum http_hdrid id);
+uint32_t http_msg_xhdr_count(const struct http_msg *msg, const char *name);
+bool http_msg_hdr_has_value(const struct http_msg *msg, enum http_hdrid id,
+ const char *value);
+bool http_msg_xhdr_has_value(const struct http_msg *msg, const char *name,
+ const char *value);
+int http_msg_print(struct re_printf *pf, const struct http_msg *msg);
+
+
+/* Client */
+struct http_cli;
+struct http_req;
+struct dnsc;
+struct tcp_conn;
+struct tls_conn;
+
+typedef void (http_resp_h)(int err, const struct http_msg *msg, void *arg);
+typedef int (http_data_h)(const uint8_t *buf, size_t size,
+ const struct http_msg *msg, void *arg);
+typedef void (http_conn_h)(struct tcp_conn *tc, struct tls_conn *sc,
+ void *arg);
+
+int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc);
+int http_request(struct http_req **reqp, struct http_cli *cli, const char *met,
+ const char *uri, http_resp_h *resph, http_data_h *datah,
+ void *arg, const char *fmt, ...);
+void http_req_set_conn_handler(struct http_req *req, http_conn_h *connh);
+
+
+/* Server */
+struct http_sock;
+struct http_conn;
+
+typedef void (http_req_h)(struct http_conn *conn, const struct http_msg *msg,
+ void *arg);
+
+int http_listen(struct http_sock **sockp, const struct sa *laddr,
+ http_req_h *reqh, void *arg);
+int https_listen(struct http_sock **sockp, const struct sa *laddr,
+ const char *cert, http_req_h *reqh, void *arg);
+struct tcp_sock *http_sock_tcp(struct http_sock *sock);
+const struct sa *http_conn_peer(const struct http_conn *conn);
+struct tcp_conn *http_conn_tcp(struct http_conn *conn);
+struct tls_conn *http_conn_tls(struct http_conn *conn);
+void http_conn_close(struct http_conn *conn);
+int http_reply(struct http_conn *conn, uint16_t scode, const char *reason,
+ const char *fmt, ...);
+int http_creply(struct http_conn *conn, uint16_t scode, const char *reason,
+ const char *ctype, const char *fmt, ...);
+int http_ereply(struct http_conn *conn, uint16_t scode, const char *reason);
+
+
+/* Authentication */
+struct http_auth {
+ const char *realm;
+ bool stale;
+};
+
+typedef int (http_auth_h)(const struct pl *username, uint8_t *ha1, void *arg);
+
+int http_auth_print_challenge(struct re_printf *pf,
+ const struct http_auth *auth);
+bool http_auth_check(const struct pl *hval, const struct pl *method,
+ struct http_auth *auth, http_auth_h *authh, void *arg);
+bool http_auth_check_request(const struct http_msg *msg,
+ struct http_auth *auth,
+ http_auth_h *authh, void *arg);
diff --git a/include/re_httpauth.h b/include/re_httpauth.h
new file mode 100644
index 0000000..32786e3
--- /dev/null
+++ b/include/re_httpauth.h
@@ -0,0 +1,40 @@
+/**
+ * @file re_httpauth.h Interface to HTTP Authentication
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** HTTP Digest Challenge */
+struct httpauth_digest_chall {
+ struct pl realm;
+ struct pl nonce;
+
+ /* optional */
+ struct pl opaque;
+ struct pl stale;
+ struct pl algorithm;
+ struct pl qop;
+};
+
+/** HTTP Digest response */
+struct httpauth_digest_resp {
+ struct pl realm;
+ struct pl nonce;
+ struct pl response;
+ struct pl username;
+ struct pl uri;
+
+ /* optional */
+ struct pl nc;
+ struct pl cnonce;
+ struct pl qop;
+};
+
+
+int httpauth_digest_challenge_decode(struct httpauth_digest_chall *chall,
+ const struct pl *hval);
+int httpauth_digest_response_decode(struct httpauth_digest_resp *resp,
+ const struct pl *hval);
+int httpauth_digest_response_auth(const struct httpauth_digest_resp *resp,
+ const struct pl *method, const uint8_t *ha1);
diff --git a/include/re_ice.h b/include/re_ice.h
new file mode 100644
index 0000000..6c72856
--- /dev/null
+++ b/include/re_ice.h
@@ -0,0 +1,161 @@
+/**
+ * @file re_ice.h Interface to Interactive Connectivity Establishment (ICE)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** ICE mode */
+enum ice_mode {
+ ICE_MODE_FULL,
+ ICE_MODE_LITE
+};
+
+/** ICE Role */
+enum ice_role {
+ ICE_ROLE_UNKNOWN = 0,
+ ICE_ROLE_CONTROLLING,
+ ICE_ROLE_CONTROLLED
+};
+
+/** ICE Component ID */
+enum ice_compid {
+ ICE_COMPID_RTP = 1,
+ ICE_COMPID_RTCP = 2
+};
+
+/** ICE Nomination */
+enum ice_nomination {
+ ICE_NOMINATION_REGULAR = 0,
+ ICE_NOMINATION_AGGRESSIVE
+};
+
+/** ICE Candidate type */
+enum ice_cand_type {
+ ICE_CAND_TYPE_HOST, /**< Host candidate */
+ ICE_CAND_TYPE_SRFLX, /**< Server Reflexive candidate */
+ ICE_CAND_TYPE_PRFLX, /**< Peer Reflexive candidate */
+ ICE_CAND_TYPE_RELAY /**< Relayed candidate */
+};
+
+/** ICE TCP protocol type */
+enum ice_tcptype {
+ ICE_TCP_ACTIVE, /**< Active TCP client */
+ ICE_TCP_PASSIVE, /**< Passive TCP server */
+ ICE_TCP_SO /**< Simultaneous-open TCP client/server */
+};
+
+/** Candidate pair states */
+enum ice_candpair_state {
+ ICE_CANDPAIR_FROZEN = 0, /**< Frozen state (default) */
+ ICE_CANDPAIR_WAITING, /**< Waiting to become highest on list */
+ ICE_CANDPAIR_INPROGRESS, /**< In-Progress state;transac. in progress */
+ ICE_CANDPAIR_SUCCEEDED, /**< Succeeded state; successful result */
+ ICE_CANDPAIR_FAILED /**< Failed state; check failed */
+};
+
+struct ice;
+struct ice_cand;
+struct icem;
+struct turnc;
+
+/** ICE Configuration */
+struct ice_conf {
+ enum ice_nomination nom; /**< Nomination algorithm */
+ uint32_t rto; /**< STUN Retransmission TimeOut */
+ uint32_t rc; /**< STUN Retransmission Count */
+ bool debug; /**< Enable ICE debugging */
+};
+
+typedef void (ice_connchk_h)(int err, bool update, void *arg);
+
+
+/* ICE Media */
+int icem_alloc(struct icem **icemp, enum ice_mode mode,
+ enum ice_role role, int proto, int layer,
+ uint64_t tiebrk, const char *lufrag, const char *lpwd,
+ ice_connchk_h *chkh, void *arg);
+struct ice_conf *icem_conf(struct icem *icem);
+enum ice_role icem_local_role(const struct icem *icem);
+void icem_set_conf(struct icem *icem, const struct ice_conf *conf);
+void icem_set_role(struct icem *icem, enum ice_role role);
+void icem_set_name(struct icem *icem, const char *name);
+int icem_comp_add(struct icem *icem, unsigned compid, void *sock);
+int icem_cand_add(struct icem *icem, unsigned compid, uint16_t lprio,
+ const char *ifname, const struct sa *addr);
+
+int icem_lite_set_default_candidates(struct icem *icem);
+bool icem_verify_support(struct icem *icem, unsigned compid,
+ const struct sa *raddr);
+int icem_conncheck_start(struct icem *icem);
+void icem_conncheck_stop(struct icem *icem, int err);
+int icem_add_chan(struct icem *icem, unsigned compid, const struct sa *raddr);
+bool icem_mismatch(const struct icem *icem);
+void icem_update(struct icem *icem);
+int ice_sdp_decode(struct icem *ice, const char *name, const char *value);
+int icem_sdp_decode(struct icem *icem, const char *name, const char *value);
+int icem_debug(struct re_printf *pf, const struct icem *icem);
+struct list *icem_lcandl(const struct icem *icem);
+struct list *icem_rcandl(const struct icem *icem);
+struct list *icem_checkl(const struct icem *icem);
+struct list *icem_validl(const struct icem *icem);
+const struct sa *icem_cand_default(struct icem *icem, unsigned compid);
+const struct sa *icem_selected_laddr(const struct icem *icem, unsigned compid);
+const struct ice_cand *icem_selected_lcand(const struct icem *icem,
+ unsigned compid);
+const struct ice_cand *icem_selected_rcand(const struct icem *icem,
+ unsigned compid);
+void ice_candpair_set_states(struct icem *icem);
+void icem_cand_redund_elim(struct icem *icem);
+int icem_comps_set_default_cand(struct icem *icem);
+struct stun *icem_stun(struct icem *icem);
+int icem_set_turn_client(struct icem *icem, unsigned compid,
+ struct turnc *turnc);
+
+
+bool ice_remotecands_avail(const struct icem *icem);
+int ice_cand_encode(struct re_printf *pf, const struct ice_cand *cand);
+int ice_remotecands_encode(struct re_printf *pf, const struct icem *icem);
+struct ice_cand *icem_cand_find(const struct list *lst, unsigned compid,
+ const struct sa *addr);
+int icem_lcand_add(struct icem *icem, struct ice_cand *base,
+ enum ice_cand_type type,
+ const struct sa *addr);
+struct ice_cand *icem_lcand_base(struct ice_cand *lcand);
+const struct sa *icem_lcand_addr(const struct ice_cand *cand);
+enum ice_cand_type icem_cand_type(const struct ice_cand *cand);
+
+
+extern const char ice_attr_cand[];
+extern const char ice_attr_lite[];
+extern const char ice_attr_mismatch[];
+extern const char ice_attr_pwd[];
+extern const char ice_attr_remote_cand[];
+extern const char ice_attr_ufrag[];
+
+
+const char *ice_cand_type2name(enum ice_cand_type type);
+enum ice_cand_type ice_cand_name2type(const char *name);
+const char *ice_role2name(enum ice_role role);
+const char *ice_candpair_state2name(enum ice_candpair_state st);
+
+
+uint32_t ice_cand_calc_prio(enum ice_cand_type type, uint16_t local,
+ unsigned compid);
+
+
+/** Defines an SDP candidate attribute */
+struct ice_cand_attr {
+ char foundation[32]; /**< Foundation string */
+ unsigned compid; /**< Component ID (1-256) */
+ int proto; /**< Transport protocol */
+ uint32_t prio; /**< Priority of this candidate */
+ struct sa addr; /**< Transport address */
+ enum ice_cand_type type; /**< Candidate type */
+ struct sa rel_addr; /**< Related transport address (optional) */
+ enum ice_tcptype tcptype; /**< TCP candidate type (TCP-only) */
+};
+
+int ice_cand_attr_encode(struct re_printf *pf,
+ const struct ice_cand_attr *cand);
+int ice_cand_attr_decode(struct ice_cand_attr *cand, const char *val);
diff --git a/include/re_jbuf.h b/include/re_jbuf.h
new file mode 100644
index 0000000..a99ae2f
--- /dev/null
+++ b/include/re_jbuf.h
@@ -0,0 +1,28 @@
+/**
+ * @file re_jbuf.h Interface to Jitter Buffer
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+struct jbuf;
+struct rtp_header;
+
+/** Jitter buffer statistics */
+struct jbuf_stat {
+ uint32_t n_put; /**< Number of frames put into jitter buffer */
+ uint32_t n_get; /**< Number of frames got from jitter buffer */
+ uint32_t n_oos; /**< Number of out-of-sequence frames */
+ uint32_t n_dups; /**< Number of duplicate frames detected */
+ uint32_t n_late; /**< Number of frames arriving too late */
+ uint32_t n_lost; /**< Number of lost frames */
+ uint32_t n_overflow; /**< Number of overflows */
+ uint32_t n_underflow; /**< Number of underflows */
+ uint32_t n_flush; /**< Number of times jitter buffer flushed */
+};
+
+
+int jbuf_alloc(struct jbuf **jbp, uint32_t min, uint32_t max);
+int jbuf_put(struct jbuf *jb, const struct rtp_header *hdr, void *mem);
+int jbuf_get(struct jbuf *jb, struct rtp_header *hdr, void **mem);
+void jbuf_flush(struct jbuf *jb);
+int jbuf_stats(const struct jbuf *jb, struct jbuf_stat *jstat);
+int jbuf_debug(struct re_printf *pf, const struct jbuf *jb);
diff --git a/include/re_json.h b/include/re_json.h
new file mode 100644
index 0000000..0cc43db
--- /dev/null
+++ b/include/re_json.h
@@ -0,0 +1,50 @@
+/**
+ * @file re_json.h Interface to JavaScript Object Notation (JSON) -- RFC 7159
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+enum json_typ {
+ JSON_STRING,
+ JSON_INT,
+ JSON_DOUBLE,
+ JSON_BOOL,
+ JSON_NULL,
+};
+
+struct json_value {
+ union {
+ char *str;
+ int64_t integer;
+ double dbl;
+ bool boolean;
+ } v;
+ enum json_typ type;
+};
+
+struct json_handlers;
+
+typedef int (json_object_entry_h)(const char *name,
+ const struct json_value *value, void *arg);
+typedef int (json_array_entry_h)(unsigned idx,
+ const struct json_value *value, void *arg);
+typedef int (json_object_h)(const char *name, unsigned idx,
+ struct json_handlers *h);
+typedef int (json_array_h)(const char *name, unsigned idx,
+ struct json_handlers *h);
+
+struct json_handlers {
+ json_object_h *oh;
+ json_array_h *ah;
+ json_object_entry_h *oeh;
+ json_array_entry_h *aeh;
+ void *arg;
+};
+
+int json_decode(const char *str, size_t len, unsigned maxdepth,
+ json_object_h *oh, json_array_h *ah,
+ json_object_entry_h *oeh, json_array_entry_h *aeh, void *arg);
+
+int json_decode_odict(struct odict **op, uint32_t hash_size, const char *str,
+ size_t len, unsigned maxdepth);
+int json_encode_odict(struct re_printf *pf, const struct odict *o);
diff --git a/include/re_list.h b/include/re_list.h
new file mode 100644
index 0000000..3045453
--- /dev/null
+++ b/include/re_list.h
@@ -0,0 +1,96 @@
+/**
+ * @file re_list.h Interface to Linked List
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** Linked-list element */
+struct le {
+ struct le *prev; /**< Previous element */
+ struct le *next; /**< Next element */
+ struct list *list; /**< Parent list (NULL if not linked-in) */
+ void *data; /**< User-data */
+};
+
+/** List Element Initializer */
+#define LE_INIT {NULL, NULL, NULL, NULL}
+
+
+/** Defines a linked list */
+struct list {
+ struct le *head; /**< First list element */
+ struct le *tail; /**< Last list element */
+};
+
+/** Linked list Initializer */
+#define LIST_INIT {NULL, NULL}
+
+
+/**
+ * Defines the list apply handler
+ *
+ * @param le List element
+ * @param arg Handler argument
+ *
+ * @return true to stop traversing, false to continue
+ */
+typedef bool (list_apply_h)(struct le *le, void *arg);
+
+/**
+ * Defines the list sort handler
+ *
+ * @param le1 Current list element
+ * @param le2 Next list element
+ * @param arg Handler argument
+ *
+ * @return true if sorted, otherwise false
+ */
+typedef bool (list_sort_h)(struct le *le1, struct le *le2, void *arg);
+
+
+void list_init(struct list *list);
+void list_flush(struct list *list);
+void list_clear(struct list *list);
+void list_append(struct list *list, struct le *le, void *data);
+void list_prepend(struct list *list, struct le *le, void *data);
+void list_insert_before(struct list *list, struct le *le, struct le *ile,
+ void *data);
+void list_insert_after(struct list *list, struct le *le, struct le *ile,
+ void *data);
+void list_unlink(struct le *le);
+void list_sort(struct list *list, list_sort_h *sh, void *arg);
+struct le *list_apply(const struct list *list, bool fwd, list_apply_h *ah,
+ void *arg);
+struct le *list_head(const struct list *list);
+struct le *list_tail(const struct list *list);
+uint32_t list_count(const struct list *list);
+
+
+/**
+ * Get the user-data from a list element
+ *
+ * @param le List element
+ *
+ * @return Pointer to user-data
+ */
+static inline void *list_ledata(const struct le *le)
+{
+ return le ? le->data : NULL;
+}
+
+
+static inline bool list_contains(const struct list *list, const struct le *le)
+{
+ return le ? le->list == list : false;
+}
+
+
+static inline bool list_isempty(const struct list *list)
+{
+ return list ? list->head == NULL : true;
+}
+
+
+#define LIST_FOREACH(list, le) \
+ for ((le) = list_head((list)); (le); (le) = (le)->next)
diff --git a/include/re_lock.h b/include/re_lock.h
new file mode 100644
index 0000000..66cab82
--- /dev/null
+++ b/include/re_lock.h
@@ -0,0 +1,18 @@
+/**
+ * @file re_lock.h Interface to locking functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct lock;
+
+int lock_alloc(struct lock **lp);
+
+void lock_read_get(struct lock *l);
+void lock_write_get(struct lock *l);
+
+int lock_read_try(struct lock *l);
+int lock_write_try(struct lock *l);
+
+void lock_rel(struct lock *l);
diff --git a/include/re_main.h b/include/re_main.h
new file mode 100644
index 0000000..cc9ac4b
--- /dev/null
+++ b/include/re_main.h
@@ -0,0 +1,69 @@
+/**
+ * @file re_main.h Interface to main polling routine
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+enum {
+#ifndef FD_READ
+ FD_READ = 1<<0,
+#endif
+#ifndef FD_WRITE
+ FD_WRITE = 1<<1,
+#endif
+ FD_EXCEPT = 1<<2
+};
+
+
+/**
+ * File descriptor event handler
+ *
+ * @param flags Event flags
+ * @param arg Handler argument
+ */
+typedef void (fd_h)(int flags, void *arg);
+
+/**
+ * Thread-safe signal handler
+ *
+ * @param sig Signal number
+ */
+typedef void (re_signal_h)(int sig);
+
+
+int fd_listen(int fd, int flags, fd_h *fh, void *arg);
+void fd_close(int fd);
+int fd_setsize(int maxfds);
+void fd_debug(void);
+
+int libre_init(void);
+void libre_close(void);
+
+int re_main(re_signal_h *signalh);
+void re_cancel(void);
+int re_debug(struct re_printf *pf, void *unused);
+
+int re_thread_init(void);
+void re_thread_close(void);
+void re_thread_enter(void);
+void re_thread_leave(void);
+
+void re_set_mutex(void *mutexp);
+
+
+/** Polling methods */
+enum poll_method {
+ METHOD_NULL = 0,
+ METHOD_POLL,
+ METHOD_SELECT,
+ METHOD_EPOLL,
+ METHOD_KQUEUE,
+ /* sep */
+ METHOD_MAX
+};
+
+int poll_method_set(enum poll_method method);
+enum poll_method poll_method_best(void);
+const char *poll_method_name(enum poll_method method);
+int poll_method_type(enum poll_method *method, const struct pl *name);
diff --git a/include/re_mbuf.h b/include/re_mbuf.h
new file mode 100644
index 0000000..feac6fd
--- /dev/null
+++ b/include/re_mbuf.h
@@ -0,0 +1,169 @@
+/**
+ * @file re_mbuf.h Interface to memory buffers
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#include <stdarg.h>
+
+
+#ifndef RELEASE
+#define MBUF_DEBUG 1 /**< Mbuf debugging (0 or 1) */
+#endif
+
+#if MBUF_DEBUG
+/** Check that mbuf position does not exceed end */
+#define MBUF_CHECK_POS(mb) \
+ if ((mb) && (mb)->pos > (mb)->end) { \
+ BREAKPOINT; \
+ }
+/** Check that mbuf end does not exceed size */
+#define MBUF_CHECK_END(mb) \
+ if ((mb) && (mb)->end > (mb)->size) { \
+ BREAKPOINT; \
+ }
+#else
+#define MBUF_CHECK_POS(mb)
+#define MBUF_CHECK_END(mb)
+#endif
+
+/** Defines a memory buffer */
+struct mbuf {
+ uint8_t *buf; /**< Buffer memory */
+ size_t size; /**< Size of buffer */
+ size_t pos; /**< Position in buffer */
+ size_t end; /**< End of buffer */
+};
+
+
+struct pl;
+struct re_printf;
+
+struct mbuf *mbuf_alloc(size_t size);
+struct mbuf *mbuf_alloc_ref(struct mbuf *mbr);
+void mbuf_init(struct mbuf *mb);
+void mbuf_reset(struct mbuf *mb);
+int mbuf_resize(struct mbuf *mb, size_t size);
+void mbuf_trim(struct mbuf *mb);
+int mbuf_shift(struct mbuf *mb, ssize_t shift);
+int mbuf_write_mem(struct mbuf *mb, const uint8_t *buf, size_t size);
+int mbuf_write_u8(struct mbuf *mb, uint8_t v);
+int mbuf_write_u16(struct mbuf *mb, uint16_t v);
+int mbuf_write_u32(struct mbuf *mb, uint32_t v);
+int mbuf_write_u64(struct mbuf *mb, uint64_t v);
+int mbuf_write_str(struct mbuf *mb, const char *str);
+int mbuf_write_pl(struct mbuf *mb, const struct pl *pl);
+int mbuf_read_mem(struct mbuf *mb, uint8_t *buf, size_t size);
+uint8_t mbuf_read_u8(struct mbuf *mb);
+uint16_t mbuf_read_u16(struct mbuf *mb);
+uint32_t mbuf_read_u32(struct mbuf *mb);
+uint64_t mbuf_read_u64(struct mbuf *mb);
+int mbuf_read_str(struct mbuf *mb, char *str, size_t size);
+int mbuf_strdup(struct mbuf *mb, char **strp, size_t len);
+int mbuf_vprintf(struct mbuf *mb, const char *fmt, va_list ap);
+int mbuf_printf(struct mbuf *mb, const char *fmt, ...);
+int mbuf_write_pl_skip(struct mbuf *mb, const struct pl *pl,
+ const struct pl *skip);
+int mbuf_fill(struct mbuf *mb, uint8_t c, size_t n);
+int mbuf_debug(struct re_printf *pf, const struct mbuf *mb);
+
+
+/**
+ * Get the buffer from the current position
+ *
+ * @param mb Memory buffer
+ *
+ * @return Current buffer
+ */
+static inline uint8_t *mbuf_buf(const struct mbuf *mb)
+{
+ return mb ? mb->buf + mb->pos : (uint8_t *)NULL;
+}
+
+
+/**
+ * Get number of bytes left in a memory buffer, from current position to end
+ *
+ * @param mb Memory buffer
+ *
+ * @return Number of bytes left
+ */
+static inline size_t mbuf_get_left(const struct mbuf *mb)
+{
+ return (mb && (mb->end > mb->pos)) ? (mb->end - mb->pos) : 0;
+}
+
+
+/**
+ * Get available space in buffer (size - pos)
+ *
+ * @param mb Memory buffer
+ *
+ * @return Number of bytes available in buffer
+ */
+static inline size_t mbuf_get_space(const struct mbuf *mb)
+{
+ return (mb && (mb->size > mb->pos)) ? (mb->size - mb->pos) : 0;
+}
+
+
+/**
+ * Set absolute position
+ *
+ * @param mb Memory buffer
+ * @param pos Position
+ */
+static inline void mbuf_set_pos(struct mbuf *mb, size_t pos)
+{
+ mb->pos = pos;
+ MBUF_CHECK_POS(mb);
+}
+
+
+/**
+ * Set absolute end
+ *
+ * @param mb Memory buffer
+ * @param end End position
+ */
+static inline void mbuf_set_end(struct mbuf *mb, size_t end)
+{
+ mb->end = end;
+ MBUF_CHECK_END(mb);
+}
+
+
+/**
+ * Advance position +/- N bytes
+ *
+ * @param mb Memory buffer
+ * @param n Number of bytes to advance
+ */
+static inline void mbuf_advance(struct mbuf *mb, ssize_t n)
+{
+ mb->pos += n;
+ MBUF_CHECK_POS(mb);
+}
+
+
+/**
+ * Rewind position and end to the beginning of buffer
+ *
+ * @param mb Memory buffer
+ */
+static inline void mbuf_rewind(struct mbuf *mb)
+{
+ mb->pos = mb->end = 0;
+}
+
+
+/**
+ * Set position to the end of the buffer
+ *
+ * @param mb Memory buffer
+ */
+static inline void mbuf_skip_to_end(struct mbuf *mb)
+{
+ mb->pos = mb->end;
+}
diff --git a/include/re_md5.h b/include/re_md5.h
new file mode 100644
index 0000000..df26d59
--- /dev/null
+++ b/include/re_md5.h
@@ -0,0 +1,15 @@
+/**
+ * @file re_md5.h Interface to MD5 functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** MD5 values */
+enum {
+ MD5_SIZE = 16, /**< Number of bytes in MD5 hash */
+ MD5_STR_SIZE = 2*MD5_SIZE + 1 /**< Number of bytes in MD5 string */
+};
+
+void md5(const uint8_t *d, size_t n, uint8_t *md);
+int md5_printf(uint8_t *md, const char *fmt, ...);
diff --git a/include/re_mem.h b/include/re_mem.h
new file mode 100644
index 0000000..d09d5e2
--- /dev/null
+++ b/include/re_mem.h
@@ -0,0 +1,45 @@
+/**
+ * @file re_mem.h Interface to Memory management with reference counting
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/**
+ * Defines the memory destructor handler, which is called when the reference
+ * of a memory object goes down to zero
+ *
+ * @param data Pointer to memory object
+ */
+typedef void (mem_destroy_h)(void *data);
+
+/** Memory Statistics */
+struct memstat {
+ size_t bytes_cur; /**< Current bytes allocated */
+ size_t bytes_peak; /**< Peak bytes allocated */
+ size_t blocks_cur; /**< Current blocks allocated */
+ size_t blocks_peak; /**< Peak blocks allocated */
+ size_t size_min; /**< Lowest block size allocated */
+ size_t size_max; /**< Largest block size allocated */
+};
+
+void *mem_alloc(size_t size, mem_destroy_h *dh);
+void *mem_zalloc(size_t size, mem_destroy_h *dh);
+void *mem_realloc(void *data, size_t size);
+void *mem_reallocarray(void *ptr, size_t nmemb,
+ size_t membsize, mem_destroy_h *dh);
+void *mem_ref(void *data);
+void *mem_deref(void *data);
+uint32_t mem_nrefs(const void *data);
+
+void mem_debug(void);
+void mem_threshold_set(ssize_t n);
+struct re_printf;
+int mem_status(struct re_printf *pf, void *unused);
+int mem_get_stat(struct memstat *mstat);
+
+
+/* Secure memory functions */
+int mem_seccmp(const volatile uint8_t *volatile s1,
+ const volatile uint8_t *volatile s2,
+ size_t n);
diff --git a/include/re_mod.h b/include/re_mod.h
new file mode 100644
index 0000000..4b132c4
--- /dev/null
+++ b/include/re_mod.h
@@ -0,0 +1,75 @@
+/**
+ * @file re_mod.h Interface to loadable modules
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/**
+ * @def MOD_PRE
+ *
+ * Module Prefix
+ *
+ * @def MOD_EXT
+ *
+ * Module Extension
+ */
+#if defined (WIN32)
+#define MOD_PRE ""
+#define MOD_EXT ".dll"
+#else
+#define MOD_PRE ""
+#define MOD_EXT ".so"
+#endif
+
+
+/** Symbol to enable exporting of functions from a module */
+#ifdef WIN32
+#define EXPORT_SYM __declspec(dllexport)
+#else
+#define EXPORT_SYM
+#endif
+
+
+/* ----- Module API ----- */
+
+
+/**
+ * Defines the module initialisation handler
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+typedef int (mod_init_h)(void);
+
+/**
+ * Defines the module close handler
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+typedef int (mod_close_h)(void);
+
+
+struct mod;
+struct re_printf;
+
+
+/** Defines the module export */
+struct mod_export {
+ const char *name; /**< Module name */
+ const char *type; /**< Module type */
+ mod_init_h *init; /**< Module init handler */
+ mod_close_h *close; /**< Module close handler */
+};
+
+
+/* ----- Application API ----- */
+
+void mod_init(void);
+void mod_close(void);
+
+int mod_load(struct mod **mp, const char *name);
+int mod_add(struct mod **mp, const struct mod_export *me);
+struct mod *mod_find(const char *name);
+const struct mod_export *mod_export(const struct mod *m);
+struct list *mod_list(void);
+int mod_debug(struct re_printf *pf, void *unused);
diff --git a/include/re_mqueue.h b/include/re_mqueue.h
new file mode 100644
index 0000000..ddd9aa7
--- /dev/null
+++ b/include/re_mqueue.h
@@ -0,0 +1,12 @@
+/**
+ * @file re_mqueue.h Thread Safe Message Queue
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+struct mqueue;
+
+typedef void (mqueue_h)(int id, void *data, void *arg);
+
+int mqueue_alloc(struct mqueue **mqp, mqueue_h *h, void *arg);
+int mqueue_push(struct mqueue *mq, int id, void *data);
diff --git a/include/re_msg.h b/include/re_msg.h
new file mode 100644
index 0000000..9c52ee9
--- /dev/null
+++ b/include/re_msg.h
@@ -0,0 +1,21 @@
+/**
+ * @file re_msg.h Interface to generic message components
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** Content-Type */
+struct msg_ctype {
+ struct pl type;
+ struct pl subtype;
+ struct pl params;
+};
+
+
+int msg_ctype_decode(struct msg_ctype *ctype, const struct pl *pl);
+bool msg_ctype_cmp(const struct msg_ctype *ctype,
+ const char *type, const char *subtype);
+
+int msg_param_decode(const struct pl *pl, const char *name, struct pl *val);
+int msg_param_exists(const struct pl *pl, const char *name, struct pl *end);
diff --git a/include/re_natbd.h b/include/re_natbd.h
new file mode 100644
index 0000000..05b27f7
--- /dev/null
+++ b/include/re_natbd.h
@@ -0,0 +1,128 @@
+/**
+ * @file re_natbd.h NAT Behavior Discovery Using STUN (RFC 5780)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** NAT Mapping/Filtering types - See RFC 4787 for definitions */
+enum nat_type {
+ NAT_TYPE_UNKNOWN = 0, /**< Unknown type */
+ NAT_TYPE_ENDP_INDEP = 1, /**< Endpoint-Independent */
+ NAT_TYPE_ADDR_DEP = 2, /**< Address-Dependent */
+ NAT_TYPE_ADDR_PORT_DEP = 3 /**< Address and Port-Dependent */
+};
+
+
+/* Strings */
+const char *nat_type_str(enum nat_type type);
+
+
+/*
+ * Diagnosing NAT Hairpinning
+ */
+struct nat_hairpinning;
+/**
+ * Defines the NAT Hairpinning handler
+ */
+typedef void (nat_hairpinning_h)(int err, bool supported, void *arg);
+
+int nat_hairpinning_alloc(struct nat_hairpinning **nhp,
+ const struct sa *srv, int proto,
+ const struct stun_conf *conf,
+ nat_hairpinning_h *hph, void *arg);
+int nat_hairpinning_start(struct nat_hairpinning *nh);
+
+
+/*
+ * Determining NAT Mapping Behavior
+ */
+struct nat_mapping;
+
+/**
+ * Defines the NAT Mapping handler
+ *
+ * @param err Errorcode
+ * @param type NAT Mapping type
+ * @param arg Handler argument
+ */
+typedef void (nat_mapping_h)(int err, enum nat_type map, void *arg);
+
+int nat_mapping_alloc(struct nat_mapping **nmp, const struct sa *laddr,
+ const struct sa *srv, int proto,
+ const struct stun_conf *conf,
+ nat_mapping_h *mh, void *arg);
+int nat_mapping_start(struct nat_mapping *nm);
+
+
+/*
+ * Determining NAT Filtering Behavior
+ */
+struct nat_filtering;
+
+/**
+ * Defines the NAT Filtering handler
+ *
+ * @param err Errorcode
+ * @param type NAT Filtering type
+ * @param arg Handler argument
+ */
+typedef void (nat_filtering_h)(int err, enum nat_type filt, void *arg);
+
+int nat_filtering_alloc(struct nat_filtering **nfp, const struct sa *srv,
+ const struct stun_conf *conf,
+ nat_filtering_h *fh, void *arg);
+int nat_filtering_start(struct nat_filtering *nf);
+
+
+/*
+ * Binding Lifetime Discovery
+ */
+
+struct nat_lifetime;
+
+/** Defines the NAT lifetime interval */
+struct nat_lifetime_interval {
+ uint32_t min; /**< Minimum lifetime interval in [seconds] */
+ uint32_t cur; /**< Current lifetime interval in [seconds] */
+ uint32_t max; /**< Maximum lifetime interval in [seconds] */
+};
+
+
+/**
+ * Defines the NAT Lifetime handler
+ *
+ * @param err Errorcode
+ * @param i NAT Lifetime intervals
+ * @param arg Handler argument
+ */
+typedef void (nat_lifetime_h)(int err, const struct nat_lifetime_interval *i,
+ void *arg);
+
+int nat_lifetime_alloc(struct nat_lifetime **nlp, const struct sa *srv,
+ uint32_t interval, const struct stun_conf *conf,
+ nat_lifetime_h *lh, void *arg);
+int nat_lifetime_start(struct nat_lifetime *nl);
+
+
+/*
+ * Detecting Generic ALGs
+ */
+struct nat_genalg;
+
+/**
+ * Defines the NAT Generic ALG handler
+ *
+ * @param err Errorcode
+ * @param errcode STUN Error code (if set)
+ * @param status Generic ALG status (-1=not detected, 1=detected)
+ * @param map Mapped network address
+ * @param arg Handler argument
+ */
+typedef void (nat_genalg_h)(int err, uint16_t scode, const char *reason,
+ int status, const struct sa *map, void *arg);
+
+int nat_genalg_alloc(struct nat_genalg **ngp, const struct sa *srv, int proto,
+ const struct stun_conf *conf,
+ nat_genalg_h *gh, void *arg);
+int nat_genalg_start(struct nat_genalg *ng);
diff --git a/include/re_net.h b/include/re_net.h
new file mode 100644
index 0000000..04d87f3
--- /dev/null
+++ b/include/re_net.h
@@ -0,0 +1,107 @@
+/**
+ * @file re_net.h Interface to Networking module.
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#if defined(WIN32)
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/types.h>
+#ifndef _BSD_SOCKLEN_T_
+#define _BSD_SOCKLEN_T_ int /**< Defines the BSD socket length type */
+#endif
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+
+
+/** Length of IPv4 address string */
+#ifndef INET_ADDRSTRLEN
+#define INET_ADDRSTRLEN 16
+#endif
+
+/** Length of IPv6 address string */
+#ifndef INET6_ADDRSTRLEN
+#define INET6_ADDRSTRLEN 46
+#endif
+
+/** Length of IPv4/v6 address string */
+#ifdef HAVE_INET6
+#define NET_ADDRSTRLEN INET6_ADDRSTRLEN
+#else
+#define NET_ADDRSTRLEN INET_ADDRSTRLEN
+#endif
+
+/* forward declarations */
+struct sa;
+
+
+/* Net generic */
+int net_hostaddr(int af, struct sa *ip);
+int net_default_source_addr_get(int af, struct sa *ip);
+int net_default_gateway_get(int af, struct sa *gw);
+
+
+/* Net sockets */
+int net_sock_init(void);
+void net_sock_close(void);
+
+
+/* Net socket options */
+int net_sockopt_blocking_set(int fd, bool blocking);
+int net_sockopt_reuse_set(int fd, bool reuse);
+
+
+/* Net interface (if.c) */
+
+/**
+ * Defines the interface address handler - called once per interface
+ *
+ * @param ifname Name of the interface
+ * @param sa IP address of the interface
+ * @param arg Handler argument
+ *
+ * @return true to stop traversing, false to continue
+ */
+typedef bool (net_ifaddr_h)(const char *ifname, const struct sa *sa,
+ void *arg);
+
+int net_if_getname(char *ifname, size_t sz, int af, const struct sa *ip);
+int net_if_getaddr(const char *ifname, int af, struct sa *ip);
+int net_if_getaddr4(const char *ifname, int af, struct sa *ip);
+int net_if_list(net_ifaddr_h *ifh, void *arg);
+int net_if_apply(net_ifaddr_h *ifh, void *arg);
+int net_if_debug(struct re_printf *pf, void *unused);
+int net_if_getlinklocal(const char *ifname, int af, struct sa *ip);
+
+
+/* Net interface (ifaddrs.c) */
+int net_getifaddrs(net_ifaddr_h *ifh, void *arg);
+
+
+/* Net route */
+
+/**
+ * Defines the routing table handler - called once per route entry
+ *
+ * @param ifname Interface name
+ * @param dst Destination IP address/network
+ * @param dstlen Prefix length of destination
+ * @param gw Gateway IP address
+ * @param arg Handler argument
+ *
+ * @return true to stop traversing, false to continue
+ */
+typedef bool (net_rt_h)(const char *ifname, const struct sa *dst,
+ int dstlen, const struct sa *gw, void *arg);
+
+int net_rt_list(net_rt_h *rth, void *arg);
+int net_rt_default_get(int af, char *ifname, size_t size);
+int net_rt_debug(struct re_printf *pf, void *unused);
+
+
+/* Net strings */
+const char *net_proto2name(int proto);
+const char *net_af2name(int af);
diff --git a/include/re_odict.h b/include/re_odict.h
new file mode 100644
index 0000000..763d7da
--- /dev/null
+++ b/include/re_odict.h
@@ -0,0 +1,56 @@
+/**
+ * @file re_odict.h Interface to Ordered Dictionary
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+enum odict_type {
+ ODICT_OBJECT,
+ ODICT_ARRAY,
+ ODICT_STRING,
+ ODICT_INT,
+ ODICT_DOUBLE,
+ ODICT_BOOL,
+ ODICT_NULL,
+};
+
+struct odict {
+ struct list lst;
+ struct hash *ht;
+};
+
+struct odict_entry {
+ struct le le, he;
+ char *key;
+ union {
+ struct odict *odict; /* ODICT_OBJECT / ODICT_ARRAY */
+ char *str; /* ODICT_STRING */
+ int64_t integer; /* ODICT_INT */
+ double dbl; /* ODICT_DOUBLE */
+ bool boolean; /* ODICT_BOOL */
+ } u;
+ enum odict_type type;
+};
+
+int odict_alloc(struct odict **op, uint32_t hash_size);
+const struct odict_entry *odict_lookup(const struct odict *o, const char *key);
+size_t odict_count(const struct odict *o, bool nested);
+int odict_debug(struct re_printf *pf, const struct odict *o);
+
+int odict_entry_add(struct odict *o, const char *key,
+ int type, ...);
+void odict_entry_del(struct odict *o, const char *key);
+int odict_entry_debug(struct re_printf *pf, const struct odict_entry *e);
+
+bool odict_type_iscontainer(enum odict_type type);
+bool odict_type_isreal(enum odict_type type);
+const char *odict_type_name(enum odict_type type);
+
+
+/* Helpers */
+
+const struct odict_entry *odict_get_type(const struct odict *o,
+ enum odict_type type, const char *key);
+const char *odict_string(const struct odict *o, const char *key);
+bool odict_get_number(const struct odict *o, uint64_t *num, const char *key);
+bool odict_get_boolean(const struct odict *o, bool *value, const char *key);
diff --git a/include/re_rtmp.h b/include/re_rtmp.h
new file mode 100644
index 0000000..4def1d1
--- /dev/null
+++ b/include/re_rtmp.h
@@ -0,0 +1,139 @@
+/**
+ * @file re_rtmp.h Interface to Real Time Messaging Protocol (RTMP)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** RTMP Protocol values */
+enum {
+ RTMP_PORT = 1935,
+};
+
+/** RTMP Stream IDs */
+enum {
+
+ /* User Control messages SHOULD use message stream ID 0
+ (known as the control stream) */
+ RTMP_CONTROL_STREAM_ID = 0
+};
+
+/** RTMP Packet types */
+enum rtmp_packet_type {
+ RTMP_TYPE_SET_CHUNK_SIZE = 1, /**< Set Chunk Size */
+ RTMP_TYPE_ACKNOWLEDGEMENT = 3, /**< Acknowledgement */
+ RTMP_TYPE_USER_CONTROL_MSG = 4, /**< User Control Messages */
+ RTMP_TYPE_WINDOW_ACK_SIZE = 5, /**< Window Acknowledgement Size */
+ RTMP_TYPE_SET_PEER_BANDWIDTH = 6, /**< Set Peer Bandwidth */
+ RTMP_TYPE_AUDIO = 8, /**< Audio Message */
+ RTMP_TYPE_VIDEO = 9, /**< Video Message */
+ RTMP_TYPE_DATA = 18, /**< Data Message */
+ RTMP_TYPE_AMF0 = 20, /**< Action Message Format (AMF) */
+};
+
+/** RTMP AMF types */
+enum rtmp_amf_type {
+ RTMP_AMF_TYPE_ROOT = -1, /**< Special internal type */
+ RTMP_AMF_TYPE_NUMBER = 0x00, /**< Number Type */
+ RTMP_AMF_TYPE_BOOLEAN = 0x01, /**< Boolean Type */
+ RTMP_AMF_TYPE_STRING = 0x02, /**< String Type */
+ RTMP_AMF_TYPE_OBJECT = 0x03, /**< Object Type */
+ RTMP_AMF_TYPE_NULL = 0x05, /**< Null type */
+ RTMP_AMF_TYPE_ECMA_ARRAY = 0x08, /**< ECMA 'associative' Array */
+ RTMP_AMF_TYPE_OBJECT_END = 0x09, /**< Object End Type */
+ RTMP_AMF_TYPE_STRICT_ARRAY = 0x0a, /**< Array with ordinal indices */
+};
+
+/** RTMP Event types */
+enum rtmp_event_type {
+ RTMP_EVENT_STREAM_BEGIN = 0, /**< Stream begin */
+ RTMP_EVENT_STREAM_EOF = 1, /**< Stream End-Of-File */
+ RTMP_EVENT_STREAM_DRY = 2, /**< No more data on the stream */
+ RTMP_EVENT_SET_BUFFER_LENGTH = 3, /**< Set buffer size in [ms] */
+ RTMP_EVENT_STREAM_IS_RECORDED = 4, /**< Stream is recorded */
+ RTMP_EVENT_PING_REQUEST = 6, /**< Ping Request from server */
+ RTMP_EVENT_PING_RESPONSE = 7, /**< Ping Response to server */
+};
+
+
+/* forward declarations */
+struct dnsc;
+struct odict;
+struct tcp_sock;
+
+
+/*
+ * RTMP High-level API (connection, stream)
+ */
+
+
+/* conn */
+struct rtmp_conn;
+
+typedef void (rtmp_estab_h)(void *arg);
+typedef void (rtmp_command_h)(const struct odict *msg, void *arg);
+typedef void (rtmp_close_h)(int err, void *arg);
+
+int rtmp_connect(struct rtmp_conn **connp, struct dnsc *dnsc, const char *uri,
+ rtmp_estab_h *estabh, rtmp_command_h *cmdh,
+ rtmp_close_h *closeh, void *arg);
+int rtmp_accept(struct rtmp_conn **connp, struct tcp_sock *ts,
+ rtmp_command_h *cmdh, rtmp_close_h *closeh, void *arg);
+int rtmp_control(const struct rtmp_conn *conn,
+ enum rtmp_packet_type type, ...);
+void rtmp_set_handlers(struct rtmp_conn *conn, rtmp_command_h *cmdh,
+ rtmp_close_h *closeh, void *arg);
+struct tcp_conn *rtmp_conn_tcpconn(const struct rtmp_conn *conn);
+const char *rtmp_conn_stream(const struct rtmp_conn *conn);
+int rtmp_conn_debug(struct re_printf *pf, const struct rtmp_conn *conn);
+
+
+typedef void (rtmp_resp_h)(bool success, const struct odict *msg,
+ void *arg);
+
+/* amf */
+int rtmp_amf_command(const struct rtmp_conn *conn, uint32_t stream_id,
+ const char *command,
+ unsigned body_propc, ...);
+int rtmp_amf_request(struct rtmp_conn *conn, uint32_t stream_id,
+ const char *command,
+ rtmp_resp_h *resph, void *arg, unsigned body_propc, ...);
+int rtmp_amf_reply(struct rtmp_conn *conn, uint32_t stream_id, bool success,
+ const struct odict *req,
+ unsigned body_propc, ...);
+int rtmp_amf_data(const struct rtmp_conn *conn, uint32_t stream_id,
+ const char *command, unsigned body_propc, ...);
+
+
+/* stream */
+struct rtmp_stream;
+
+typedef void (rtmp_control_h)(enum rtmp_event_type event, struct mbuf *mb,
+ void *arg);
+typedef void (rtmp_audio_h)(uint32_t timestamp,
+ const uint8_t *pld, size_t len, void *arg);
+typedef void (rtmp_video_h)(uint32_t timestamp,
+ const uint8_t *pld, size_t len, void *arg);
+
+int rtmp_stream_alloc(struct rtmp_stream **strmp, struct rtmp_conn *conn,
+ uint32_t stream_id, rtmp_command_h *cmdh,
+ rtmp_control_h *ctrlh, rtmp_audio_h *auh,
+ rtmp_video_h *vidh, rtmp_command_h *datah,
+ void *arg);
+int rtmp_stream_create(struct rtmp_stream **strmp, struct rtmp_conn *conn,
+ rtmp_resp_h *resph, rtmp_command_h *cmdh,
+ rtmp_control_h *ctrlh, rtmp_audio_h *auh,
+ rtmp_video_h *vidh, rtmp_command_h *datah,
+ void *arg);
+int rtmp_play(struct rtmp_stream *strm, const char *name);
+int rtmp_publish(struct rtmp_stream *strm, const char *name);
+int rtmp_meta(struct rtmp_stream *strm);
+int rtmp_send_audio(struct rtmp_stream *strm, uint32_t timestamp,
+ const uint8_t *pld, size_t len);
+int rtmp_send_video(struct rtmp_stream *strm, uint32_t timestamp,
+ const uint8_t *pld, size_t len);
+struct rtmp_stream *rtmp_stream_find(const struct rtmp_conn *conn,
+ uint32_t stream_id);
+
+
+const char *rtmp_event_name(enum rtmp_event_type event);
diff --git a/include/re_rtp.h b/include/re_rtp.h
new file mode 100644
index 0000000..06c7ff3
--- /dev/null
+++ b/include/re_rtp.h
@@ -0,0 +1,239 @@
+/**
+ * @file re_rtp.h Interface to Real-time Transport Protocol and RTCP
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** RTP protocol values */
+enum {
+ RTP_VERSION = 2, /**< Defines the RTP version we support */
+ RTCP_VERSION = 2, /**< Supported RTCP Version */
+ RTP_HEADER_SIZE = 12 /**< Number of bytes in RTP Header */
+};
+
+
+/** Defines the RTP header */
+struct rtp_header {
+ uint8_t ver; /**< RTP version number */
+ bool pad; /**< Padding bit */
+ bool ext; /**< Extension bit */
+ uint8_t cc; /**< CSRC count */
+ bool m; /**< Marker bit */
+ uint8_t pt; /**< Payload type */
+ uint16_t seq; /**< Sequence number */
+ uint32_t ts; /**< Timestamp */
+ uint32_t ssrc; /**< Synchronization source */
+ uint32_t csrc[16]; /**< Contributing sources */
+ struct {
+ uint16_t type; /**< Defined by profile */
+ uint16_t len; /**< Number of 32-bit words */
+ } x;
+};
+
+/** RTCP Packet Types */
+enum rtcp_type {
+ RTCP_FIR = 192, /**< Full INTRA-frame Request (RFC 2032) */
+ RTCP_NACK = 193, /**< Negative Acknowledgement (RFC 2032) */
+ RTCP_SR = 200, /**< Sender Report */
+ RTCP_RR = 201, /**< Receiver Report */
+ RTCP_SDES = 202, /**< Source Description */
+ RTCP_BYE = 203, /**< Goodbye */
+ RTCP_APP = 204, /**< Application-defined */
+ RTCP_RTPFB = 205, /**< Transport layer FB message (RFC 4585) */
+ RTCP_PSFB = 206, /**< Payload-specific FB message (RFC 4585) */
+ RTCP_XR = 207, /**< Extended Report (RFC 3611) */
+ RTCP_AVB = 208, /**< AVB RTCP Packet (IEEE1733) */
+};
+
+/** SDES Types */
+enum rtcp_sdes_type {
+ RTCP_SDES_END = 0, /**< End of SDES list */
+ RTCP_SDES_CNAME = 1, /**< Canonical name */
+ RTCP_SDES_NAME = 2, /**< User name */
+ RTCP_SDES_EMAIL = 3, /**< User's electronic mail address */
+ RTCP_SDES_PHONE = 4, /**< User's phone number */
+ RTCP_SDES_LOC = 5, /**< Geographic user location */
+ RTCP_SDES_TOOL = 6, /**< Name of application or tool */
+ RTCP_SDES_NOTE = 7, /**< Notice about the source */
+ RTCP_SDES_PRIV = 8 /**< Private extension */
+};
+
+/** Transport Layer Feedback Messages */
+enum rtcp_rtpfb {
+ RTCP_RTPFB_GNACK = 1 /**< Generic NACK */
+};
+
+/** Payload-Specific Feedback Messages */
+enum rtcp_psfb {
+ RTCP_PSFB_PLI = 1, /**< Picture Loss Indication (PLI) */
+ RTCP_PSFB_SLI = 2, /**< Slice Loss Indication (SLI) */
+ RTCP_PSFB_AFB = 15, /**< Application layer Feedback Messages */
+};
+
+/** Reception report block */
+struct rtcp_rr {
+ uint32_t ssrc; /**< Data source being reported */
+ unsigned int fraction:8; /**< Fraction lost since last SR/RR */
+ int lost:24; /**< Cumul. no. pkts lost (signed!) */
+ uint32_t last_seq; /**< Extended last seq. no. received */
+ uint32_t jitter; /**< Interarrival jitter */
+ uint32_t lsr; /**< Last SR packet from this source */
+ uint32_t dlsr; /**< Delay since last SR packet */
+};
+
+/** SDES item */
+struct rtcp_sdes_item {
+ enum rtcp_sdes_type type; /**< Type of item (enum rtcp_sdes_type) */
+ uint8_t length; /**< Length of item (in octets) */
+ char *data; /**< Text, not null-terminated */
+};
+
+/** One RTCP Message */
+struct rtcp_msg {
+ /** RTCP Header */
+ struct rtcp_hdr {
+ unsigned int version:2; /**< Protocol version */
+ unsigned int p:1; /**< Padding flag */
+ unsigned int count:5; /**< Varies by packet type */
+ unsigned int pt:8; /**< RTCP packet type */
+ uint16_t length; /**< Packet length in words */
+ } hdr;
+ union {
+ /** Sender report (SR) */
+ struct {
+ uint32_t ssrc; /**< Sender generating report */
+ uint32_t ntp_sec; /**< NTP timestamp - seconds */
+ uint32_t ntp_frac; /**< NTP timestamp - fractions */
+ uint32_t rtp_ts; /**< RTP timestamp */
+ uint32_t psent; /**< RTP packets sent */
+ uint32_t osent; /**< RTP octets sent */
+ struct rtcp_rr *rrv; /**< Reception report blocks */
+ } sr;
+
+ /** Reception report (RR) */
+ struct {
+ uint32_t ssrc; /**< Receiver generating report*/
+ struct rtcp_rr *rrv; /**< Reception report blocks */
+ } rr;
+
+ /** Source Description (SDES) */
+ struct rtcp_sdes {
+ uint32_t src; /**< First SSRC/CSRC */
+ struct rtcp_sdes_item *itemv; /**< SDES items */
+ uint32_t n; /**< Number of SDES items */
+ } *sdesv;
+
+ /** BYE */
+ struct {
+ uint32_t *srcv; /**< List of sources */
+ char *reason; /**< Reason for leaving (opt.) */
+ } bye;
+
+ /** Application-defined (APP) */
+ struct {
+ uint32_t src; /**< SSRC/CSRC */
+ char name[4]; /**< Name (ASCII) */
+ uint8_t *data; /**< Application data (32 bits) */
+ size_t data_len; /**< Number of data bytes */
+ } app;
+
+ /** Full INTRA-frame Request (FIR) packet */
+ struct {
+ uint32_t ssrc; /**< SSRC for sender of this packet */
+ } fir;
+
+ /** Negative ACKnowledgements (NACK) packet */
+ struct {
+ uint32_t ssrc; /**< SSRC for sender of this packet */
+ uint16_t fsn; /**< First Sequence Number lost */
+ uint16_t blp; /**< Bitmask of lost packets */
+ } nack;
+
+ /** Feedback (RTPFB or PSFB) packet */
+ struct {
+ uint32_t ssrc_packet;
+ uint32_t ssrc_media;
+ uint32_t n;
+ /** Feedback Control Information (FCI) */
+ union {
+ struct gnack {
+ uint16_t pid;
+ uint16_t blp;
+ } *gnackv;
+ struct sli {
+ uint16_t first;
+ uint16_t number;
+ uint8_t picid;
+ } *sliv;
+ struct mbuf *afb;
+ void *p;
+ } fci;
+ } fb;
+ } r;
+};
+
+/** RTCP Statistics */
+struct rtcp_stats {
+ struct {
+ uint32_t sent; /**< Tx RTP Packets */
+ int lost; /**< Tx RTP Packets Lost */
+ uint32_t jit; /**< Tx Inter-arrival Jitter in [us] */
+ } tx;
+ struct {
+ uint32_t sent; /**< Rx RTP Packets */
+ int lost; /**< Rx RTP Packets Lost */
+ uint32_t jit; /**< Rx Inter-Arrival Jitter in [us] */
+ } rx;
+ uint32_t rtt; /**< Current Round-Trip Time in [us] */
+};
+
+struct sa;
+struct re_printf;
+struct rtp_sock;
+
+typedef void (rtp_recv_h)(const struct sa *src, const struct rtp_header *hdr,
+ struct mbuf *mb, void *arg);
+typedef void (rtcp_recv_h)(const struct sa *src, struct rtcp_msg *msg,
+ void *arg);
+
+/* RTP api */
+int rtp_alloc(struct rtp_sock **rsp);
+int rtp_listen(struct rtp_sock **rsp, int proto, const struct sa *ip,
+ uint16_t min_port, uint16_t max_port, bool enable_rtcp,
+ rtp_recv_h *recvh, rtcp_recv_h *rtcph, void *arg);
+int rtp_hdr_encode(struct mbuf *mb, const struct rtp_header *hdr);
+int rtp_hdr_decode(struct rtp_header *hdr, struct mbuf *mb);
+int rtp_encode(struct rtp_sock *rs, bool ext, bool marker, uint8_t pt,
+ uint32_t ts, struct mbuf *mb);
+int rtp_decode(struct rtp_sock *rs, struct mbuf *mb, struct rtp_header *hdr);
+int rtp_send(struct rtp_sock *rs, const struct sa *dst, bool ext,
+ bool marker, uint8_t pt, uint32_t ts, struct mbuf *mb);
+int rtp_debug(struct re_printf *pf, const struct rtp_sock *rs);
+void *rtp_sock(const struct rtp_sock *rs);
+uint32_t rtp_sess_ssrc(const struct rtp_sock *rs);
+const struct sa *rtp_local(const struct rtp_sock *rs);
+
+/* RTCP session api */
+void rtcp_start(struct rtp_sock *rs, const char *cname,
+ const struct sa *peer);
+void rtcp_enable_mux(struct rtp_sock *rs, bool enabled);
+void rtcp_set_srate(struct rtp_sock *rs, uint32_t sr_tx, uint32_t sr_rx);
+void rtcp_set_srate_tx(struct rtp_sock *rs, uint32_t srate_tx);
+void rtcp_set_srate_rx(struct rtp_sock *rs, uint32_t srate_rx);
+int rtcp_send_app(struct rtp_sock *rs, const char name[4],
+ const uint8_t *data, size_t len);
+int rtcp_send_fir(struct rtp_sock *rs, uint32_t ssrc);
+int rtcp_send_nack(struct rtp_sock *rs, uint16_t fsn, uint16_t blp);
+int rtcp_send_pli(struct rtp_sock *rs, uint32_t fb_ssrc);
+int rtcp_debug(struct re_printf *pf, const struct rtp_sock *rs);
+void *rtcp_sock(const struct rtp_sock *rs);
+int rtcp_stats(struct rtp_sock *rs, uint32_t ssrc, struct rtcp_stats *stats);
+
+/* RTCP utils */
+int rtcp_encode(struct mbuf *mb, enum rtcp_type type, uint32_t count, ...);
+int rtcp_decode(struct rtcp_msg **msgp, struct mbuf *mb);
+int rtcp_msg_print(struct re_printf *pf, const struct rtcp_msg *msg);
+int rtcp_sdes_encode(struct mbuf *mb, uint32_t src, uint32_t itemc, ...);
+const char *rtcp_type_name(enum rtcp_type type);
+const char *rtcp_sdes_name(enum rtcp_sdes_type sdes);
diff --git a/include/re_sa.h b/include/re_sa.h
new file mode 100644
index 0000000..4492922
--- /dev/null
+++ b/include/re_sa.h
@@ -0,0 +1,63 @@
+/**
+ * @file re_sa.h Interface to Socket Address
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#if defined(WIN32)
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+
+
+struct pl;
+
+/** Socket Address flags */
+enum sa_flag {
+ SA_ADDR = 1<<0,
+ SA_PORT = 1<<1,
+ SA_ALL = SA_ADDR | SA_PORT
+};
+
+/** Defines a Socket Address */
+struct sa {
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in in;
+#ifdef HAVE_INET6
+ struct sockaddr_in6 in6;
+#endif
+ uint8_t padding[28];
+ } u;
+ socklen_t len;
+};
+
+void sa_init(struct sa *sa, int af);
+int sa_set(struct sa *sa, const struct pl *addr, uint16_t port);
+int sa_set_str(struct sa *sa, const char *addr, uint16_t port);
+void sa_set_in(struct sa *sa, uint32_t addr, uint16_t port);
+void sa_set_in6(struct sa *sa, const uint8_t *addr, uint16_t port);
+int sa_set_sa(struct sa *sa, const struct sockaddr *s);
+void sa_set_port(struct sa *sa, uint16_t port);
+int sa_decode(struct sa *sa, const char *str, size_t len);
+
+int sa_af(const struct sa *sa);
+uint32_t sa_in(const struct sa *sa);
+void sa_in6(const struct sa *sa, uint8_t *addr);
+int sa_ntop(const struct sa *sa, char *buf, int size);
+uint16_t sa_port(const struct sa *sa);
+bool sa_isset(const struct sa *sa, int flag);
+uint32_t sa_hash(const struct sa *sa, int flag);
+
+void sa_cpy(struct sa *dst, const struct sa *src);
+bool sa_cmp(const struct sa *l, const struct sa *r, int flag);
+
+bool sa_is_linklocal(const struct sa *sa);
+bool sa_is_loopback(const struct sa *sa);
+bool sa_is_any(const struct sa *sa);
+
+struct re_printf;
+int sa_print_addr(struct re_printf *pf, const struct sa *sa);
diff --git a/include/re_sdp.h b/include/re_sdp.h
new file mode 100644
index 0000000..f34bab5
--- /dev/null
+++ b/include/re_sdp.h
@@ -0,0 +1,185 @@
+/**
+ * @file re_sdp.h Interface to Session Description Protocol (SDP)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+enum {
+ SDP_VERSION = 0
+};
+
+/** SDP Direction */
+enum sdp_dir {
+ SDP_INACTIVE = 0,
+ SDP_RECVONLY = 1,
+ SDP_SENDONLY = 2,
+ SDP_SENDRECV = 3,
+};
+
+/** SDP Bandwidth type */
+enum sdp_bandwidth {
+ SDP_BANDWIDTH_MIN = 0,
+ SDP_BANDWIDTH_CT = 0, /**< [kbit/s] Conference Total */
+ SDP_BANDWIDTH_AS, /**< [kbit/s] Application Specific */
+ SDP_BANDWIDTH_RS, /**< [bit/s] RTCP Senders (RFC 3556) */
+ SDP_BANDWIDTH_RR, /**< [bit/s] RTCP Receivers (RFC 3556) */
+ SDP_BANDWIDTH_TIAS, /**< [bit/s] Transport Independent Application
+ Specific Maximum (RFC 3890) */
+ SDP_BANDWIDTH_MAX,
+};
+
+
+struct sdp_format;
+
+typedef int(sdp_media_enc_h)(struct mbuf *mb, bool offer, void *arg);
+typedef int(sdp_fmtp_enc_h)(struct mbuf *mb, const struct sdp_format *fmt,
+ bool offer, void *data);
+typedef bool(sdp_fmtp_cmp_h)(const char *params1, const char *params2,
+ void *data);
+typedef bool(sdp_format_h)(struct sdp_format *fmt, void *arg);
+typedef bool(sdp_attr_h)(const char *name, const char *value, void *arg);
+
+/** SDP Format */
+struct sdp_format {
+ struct le le;
+ char *id;
+ char *params;
+ char *rparams;
+ char *name;
+ sdp_fmtp_enc_h *ench;
+ sdp_fmtp_cmp_h *cmph;
+ void *data;
+ bool ref;
+ bool sup;
+ int pt;
+ uint32_t srate;
+ uint8_t ch;
+};
+
+
+/* session */
+struct sdp_session;
+
+int sdp_session_alloc(struct sdp_session **sessp, const struct sa *laddr);
+void sdp_session_set_laddr(struct sdp_session *sess, const struct sa *laddr);
+void sdp_session_set_lbandwidth(struct sdp_session *sess,
+ enum sdp_bandwidth type, int32_t bw);
+int sdp_session_set_lattr(struct sdp_session *sess, bool replace,
+ const char *name, const char *value, ...);
+void sdp_session_del_lattr(struct sdp_session *sess, const char *name);
+int32_t sdp_session_lbandwidth(const struct sdp_session *sess,
+ enum sdp_bandwidth type);
+int32_t sdp_session_rbandwidth(const struct sdp_session *sess,
+ enum sdp_bandwidth type);
+const char *sdp_session_rattr(const struct sdp_session *sess,
+ const char *name);
+const char *sdp_session_rattr_apply(const struct sdp_session *sess,
+ const char *name,
+ sdp_attr_h *attrh, void *arg);
+const struct list *sdp_session_medial(const struct sdp_session *sess,
+ bool local);
+int sdp_session_debug(struct re_printf *pf, const struct sdp_session *sess);
+
+
+/* media */
+struct sdp_media;
+
+int sdp_media_add(struct sdp_media **mp, struct sdp_session *sess,
+ const char *name, uint16_t port, const char *proto);
+int sdp_media_set_alt_protos(struct sdp_media *m, unsigned protoc, ...);
+void sdp_media_set_encode_handler(struct sdp_media *m, sdp_media_enc_h *ench,
+ void *arg);
+void sdp_media_set_fmt_ignore(struct sdp_media *m, bool fmt_ignore);
+void sdp_media_set_disabled(struct sdp_media *m, bool disabled);
+void sdp_media_set_lport(struct sdp_media *m, uint16_t port);
+void sdp_media_set_laddr(struct sdp_media *m, const struct sa *laddr);
+void sdp_media_set_lbandwidth(struct sdp_media *m, enum sdp_bandwidth type,
+ int32_t bw);
+void sdp_media_set_lport_rtcp(struct sdp_media *m, uint16_t port);
+void sdp_media_set_laddr_rtcp(struct sdp_media *m, const struct sa *laddr);
+void sdp_media_set_ldir(struct sdp_media *m, enum sdp_dir dir);
+int sdp_media_set_lattr(struct sdp_media *m, bool replace,
+ const char *name, const char *value, ...);
+void sdp_media_del_lattr(struct sdp_media *m, const char *name);
+const char *sdp_media_proto(const struct sdp_media *m);
+uint16_t sdp_media_rport(const struct sdp_media *m);
+const struct sa *sdp_media_raddr(const struct sdp_media *m);
+const struct sa *sdp_media_laddr(const struct sdp_media *m);
+void sdp_media_raddr_rtcp(const struct sdp_media *m, struct sa *raddr);
+int32_t sdp_media_rbandwidth(const struct sdp_media *m,
+ enum sdp_bandwidth type);
+enum sdp_dir sdp_media_ldir(const struct sdp_media *m);
+enum sdp_dir sdp_media_rdir(const struct sdp_media *m);
+enum sdp_dir sdp_media_dir(const struct sdp_media *m);
+const struct sdp_format *sdp_media_lformat(const struct sdp_media *m, int pt);
+const struct sdp_format *sdp_media_rformat(const struct sdp_media *m,
+ const char *name);
+struct sdp_format *sdp_media_format(const struct sdp_media *m,
+ bool local, const char *id,
+ int pt, const char *name,
+ int32_t srate, int8_t ch);
+struct sdp_format *sdp_media_format_apply(const struct sdp_media *m,
+ bool local, const char *id,
+ int pt, const char *name,
+ int32_t srate, int8_t ch,
+ sdp_format_h *fmth, void *arg);
+const struct list *sdp_media_format_lst(const struct sdp_media *m, bool local);
+const char *sdp_media_rattr(const struct sdp_media *m, const char *name);
+const char *sdp_media_session_rattr(const struct sdp_media *m,
+ const struct sdp_session *sess,
+ const char *name);
+const char *sdp_media_rattr_apply(const struct sdp_media *m, const char *name,
+ sdp_attr_h *attrh, void *arg);
+const char *sdp_media_name(const struct sdp_media *m);
+int sdp_media_debug(struct re_printf *pf, const struct sdp_media *m);
+
+
+/* format */
+int sdp_format_add(struct sdp_format **fmtp, struct sdp_media *m,
+ bool prepend, const char *id, const char *name,
+ uint32_t srate, uint8_t ch, sdp_fmtp_enc_h *ench,
+ sdp_fmtp_cmp_h *cmph, void *data, bool ref,
+ const char *params, ...);
+int sdp_format_set_params(struct sdp_format *fmt, const char *params, ...);
+bool sdp_format_cmp(const struct sdp_format *fmt1,
+ const struct sdp_format *fmt2);
+int sdp_format_debug(struct re_printf *pf, const struct sdp_format *fmt);
+
+
+/* encode/decode */
+int sdp_encode(struct mbuf **mbp, struct sdp_session *sess, bool offer);
+int sdp_decode(struct sdp_session *sess, struct mbuf *mb, bool offer);
+
+
+/* strings */
+const char *sdp_dir_name(enum sdp_dir dir);
+const char *sdp_bandwidth_name(enum sdp_bandwidth type);
+
+
+extern const char sdp_attr_fmtp[];
+extern const char sdp_attr_maxptime[];
+extern const char sdp_attr_ptime[];
+extern const char sdp_attr_rtcp[];
+extern const char sdp_attr_rtpmap[];
+
+extern const char sdp_media_audio[];
+extern const char sdp_media_video[];
+extern const char sdp_media_text[];
+
+extern const char sdp_proto_rtpavp[];
+extern const char sdp_proto_rtpsavp[];
+
+
+/* utility functions */
+
+/** RTP Header Extensions, as defined in RFC 5285 */
+struct sdp_extmap {
+ struct pl name;
+ struct pl attrs;
+ enum sdp_dir dir;
+ bool dir_set;
+ uint32_t id;
+};
+
+int sdp_extmap_decode(struct sdp_extmap *ext, const char *val);
diff --git a/include/re_sha.h b/include/re_sha.h
new file mode 100644
index 0000000..8fdd632
--- /dev/null
+++ b/include/re_sha.h
@@ -0,0 +1,34 @@
+/**
+ * @file re_sha.h Interface to SHA (Secure Hash Standard) functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifdef USE_OPENSSL
+#include <openssl/sha.h>
+#else
+
+/* public api for steve reid's public domain SHA-1 implementation */
+/* this file is in the public domain */
+
+/** SHA-1 Context */
+typedef struct {
+ uint32_t state[5]; /**< Context state */
+ uint32_t count[2]; /**< Counter */
+ uint8_t buffer[64]; /**< SHA-1 buffer */
+} SHA1_CTX;
+
+/** SHA-1 Context (OpenSSL compat) */
+typedef SHA1_CTX SHA_CTX;
+
+/** SHA-1 Digest size in bytes */
+#define SHA1_DIGEST_SIZE 20
+/** SHA-1 Digest size in bytes (OpenSSL compat) */
+#define SHA_DIGEST_LENGTH SHA1_DIGEST_SIZE
+
+void SHA1_Init(SHA1_CTX* context);
+void SHA1_Update(SHA1_CTX* context, const void *p, size_t len);
+void SHA1_Final(uint8_t digest[SHA1_DIGEST_SIZE], SHA1_CTX* context);
+
+#endif
diff --git a/include/re_sip.h b/include/re_sip.h
new file mode 100644
index 0000000..9a2af7a
--- /dev/null
+++ b/include/re_sip.h
@@ -0,0 +1,370 @@
+/**
+ * @file re_sip.h Session Initiation Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+enum {
+ SIP_PORT = 5060,
+ SIP_PORT_TLS = 5061,
+};
+
+/** SIP Transport */
+enum sip_transp {
+ SIP_TRANSP_NONE = -1,
+ SIP_TRANSP_UDP = 0,
+ SIP_TRANSP_TCP,
+ SIP_TRANSP_TLS,
+ SIP_TRANSPC,
+};
+
+
+/** SIP Header ID (perfect hash value) */
+enum sip_hdrid {
+ SIP_HDR_ACCEPT = 3186,
+ SIP_HDR_ACCEPT_CONTACT = 232,
+ SIP_HDR_ACCEPT_ENCODING = 708,
+ SIP_HDR_ACCEPT_LANGUAGE = 2867,
+ SIP_HDR_ACCEPT_RESOURCE_PRIORITY = 1848,
+ SIP_HDR_ALERT_INFO = 274,
+ SIP_HDR_ALLOW = 2429,
+ SIP_HDR_ALLOW_EVENTS = 66,
+ SIP_HDR_ANSWER_MODE = 2905,
+ SIP_HDR_AUTHENTICATION_INFO = 3144,
+ SIP_HDR_AUTHORIZATION = 2503,
+ SIP_HDR_CALL_ID = 3095,
+ SIP_HDR_CALL_INFO = 586,
+ SIP_HDR_CONTACT = 229,
+ SIP_HDR_CONTENT_DISPOSITION = 1425,
+ SIP_HDR_CONTENT_ENCODING = 580,
+ SIP_HDR_CONTENT_LANGUAGE = 3371,
+ SIP_HDR_CONTENT_LENGTH = 3861,
+ SIP_HDR_CONTENT_TYPE = 809,
+ SIP_HDR_CSEQ = 746,
+ SIP_HDR_DATE = 1027,
+ SIP_HDR_ENCRYPTION = 3125,
+ SIP_HDR_ERROR_INFO = 21,
+ SIP_HDR_EVENT = 3286,
+ SIP_HDR_EXPIRES = 1983,
+ SIP_HDR_FLOW_TIMER = 584,
+ SIP_HDR_FROM = 1963,
+ SIP_HDR_HIDE = 283,
+ SIP_HDR_HISTORY_INFO = 2582,
+ SIP_HDR_IDENTITY = 2362,
+ SIP_HDR_IDENTITY_INFO = 980,
+ SIP_HDR_IN_REPLY_TO = 1577,
+ SIP_HDR_JOIN = 3479,
+ SIP_HDR_MAX_BREADTH = 3701,
+ SIP_HDR_MAX_FORWARDS = 3549,
+ SIP_HDR_MIME_VERSION = 3659,
+ SIP_HDR_MIN_EXPIRES = 1121,
+ SIP_HDR_MIN_SE = 2847,
+ SIP_HDR_ORGANIZATION = 3247,
+ SIP_HDR_P_ACCESS_NETWORK_INFO = 1662,
+ SIP_HDR_P_ANSWER_STATE = 42,
+ SIP_HDR_P_ASSERTED_IDENTITY = 1233,
+ SIP_HDR_P_ASSOCIATED_URI = 900,
+ SIP_HDR_P_CALLED_PARTY_ID = 3347,
+ SIP_HDR_P_CHARGING_FUNCTION_ADDRESSES = 2171,
+ SIP_HDR_P_CHARGING_VECTOR = 25,
+ SIP_HDR_P_DCS_TRACE_PARTY_ID = 3027,
+ SIP_HDR_P_DCS_OSPS = 1788,
+ SIP_HDR_P_DCS_BILLING_INFO = 2017,
+ SIP_HDR_P_DCS_LAES = 693,
+ SIP_HDR_P_DCS_REDIRECT = 1872,
+ SIP_HDR_P_EARLY_MEDIA = 2622,
+ SIP_HDR_P_MEDIA_AUTHORIZATION = 1035,
+ SIP_HDR_P_PREFERRED_IDENTITY = 1263,
+ SIP_HDR_P_PROFILE_KEY = 1904,
+ SIP_HDR_P_REFUSED_URI_LIST = 1047,
+ SIP_HDR_P_SERVED_USER = 1588,
+ SIP_HDR_P_USER_DATABASE = 2827,
+ SIP_HDR_P_VISITED_NETWORK_ID = 3867,
+ SIP_HDR_PATH = 2741,
+ SIP_HDR_PERMISSION_MISSING = 1409,
+ SIP_HDR_PRIORITY = 3520,
+ SIP_HDR_PRIV_ANSWER_MODE = 2476,
+ SIP_HDR_PRIVACY = 3150,
+ SIP_HDR_PROXY_AUTHENTICATE = 116,
+ SIP_HDR_PROXY_AUTHORIZATION = 2363,
+ SIP_HDR_PROXY_REQUIRE = 3562,
+ SIP_HDR_RACK = 2523,
+ SIP_HDR_REASON = 3732,
+ SIP_HDR_RECORD_ROUTE = 278,
+ SIP_HDR_REFER_SUB = 2458,
+ SIP_HDR_REFER_TO = 1521,
+ SIP_HDR_REFERRED_BY = 3456,
+ SIP_HDR_REJECT_CONTACT = 285,
+ SIP_HDR_REPLACES = 2534,
+ SIP_HDR_REPLY_TO = 2404,
+ SIP_HDR_REQUEST_DISPOSITION = 3715,
+ SIP_HDR_REQUIRE = 3905,
+ SIP_HDR_RESOURCE_PRIORITY = 1643,
+ SIP_HDR_RESPONSE_KEY = 1548,
+ SIP_HDR_RETRY_AFTER = 409,
+ SIP_HDR_ROUTE = 661,
+ SIP_HDR_RSEQ = 445,
+ SIP_HDR_SECURITY_CLIENT = 1358,
+ SIP_HDR_SECURITY_SERVER = 811,
+ SIP_HDR_SECURITY_VERIFY = 519,
+ SIP_HDR_SERVER = 973,
+ SIP_HDR_SERVICE_ROUTE = 1655,
+ SIP_HDR_SESSION_EXPIRES = 1979,
+ SIP_HDR_SIP_ETAG = 1997,
+ SIP_HDR_SIP_IF_MATCH = 3056,
+ SIP_HDR_SUBJECT = 1043,
+ SIP_HDR_SUBSCRIPTION_STATE = 2884,
+ SIP_HDR_SUPPORTED = 119,
+ SIP_HDR_TARGET_DIALOG = 3450,
+ SIP_HDR_TIMESTAMP = 938,
+ SIP_HDR_TO = 1449,
+ SIP_HDR_TRIGGER_CONSENT = 3180,
+ SIP_HDR_UNSUPPORTED = 982,
+ SIP_HDR_USER_AGENT = 4064,
+ SIP_HDR_VIA = 3961,
+ SIP_HDR_WARNING = 2108,
+ SIP_HDR_WWW_AUTHENTICATE = 2763,
+
+ SIP_HDR_NONE = -1
+};
+
+
+enum {
+ SIP_T1 = 500,
+ SIP_T2 = 4000,
+ SIP_T4 = 5000,
+};
+
+
+/** SIP Via header */
+struct sip_via {
+ struct pl sentby;
+ struct sa addr;
+ struct pl params;
+ struct pl branch;
+ struct pl val;
+ enum sip_transp tp;
+};
+
+/** SIP Address */
+struct sip_addr {
+ struct pl dname;
+ struct pl auri;
+ struct uri uri;
+ struct pl params;
+};
+
+/** SIP Tag address */
+struct sip_taddr {
+ struct pl dname;
+ struct pl auri;
+ struct uri uri;
+ struct pl params;
+ struct pl tag;
+ struct pl val;
+};
+
+/** SIP CSeq header */
+struct sip_cseq {
+ struct pl met;
+ uint32_t num;
+};
+
+/** SIP Header */
+struct sip_hdr {
+ struct le le; /**< Linked-list element */
+ struct le he; /**< Hash-table element */
+ struct pl name; /**< SIP Header name */
+ struct pl val; /**< SIP Header value */
+ enum sip_hdrid id; /**< SIP Header id (unique) */
+};
+
+/** SIP Message */
+struct sip_msg {
+ struct sa src; /**< Source network address */
+ struct sa dst; /**< Destination network address */
+ struct pl ver; /**< SIP Version number */
+ struct pl met; /**< Request method */
+ struct pl ruri; /**< Raw request URI */
+ struct uri uri; /**< Parsed request URI */
+ uint16_t scode; /**< Response status code */
+ struct pl reason; /**< Response reason phrase */
+ struct list hdrl; /**< List of SIP Headers (struct sip_hdr) */
+ struct sip_via via; /**< Parsed first Via header */
+ struct sip_taddr to; /**< Parsed To header */
+ struct sip_taddr from; /**< Parsed From header */
+ struct sip_cseq cseq; /**< Parsed CSeq header */
+ struct msg_ctype ctyp; /**< Content Type */
+ struct pl callid; /**< Cached Call-ID header */
+ struct pl maxfwd; /**< Cached Max-Forwards header */
+ struct pl expires; /**< Cached Expires header */
+ struct pl clen; /**< Cached Content-Length header */
+ struct hash *hdrht; /**< Hash-table with all SIP headers */
+ struct mbuf *mb; /**< Buffer containing the SIP message */
+ void *sock; /**< Transport socket */
+ uint64_t tag; /**< Opaque tag */
+ enum sip_transp tp; /**< SIP Transport */
+ bool req; /**< True if Request, False if Response */
+};
+
+/** SIP Loop-state */
+struct sip_loopstate {
+ uint32_t failc;
+ uint16_t last_scode;
+};
+
+/** SIP Contact */
+struct sip_contact {
+ const char *uri;
+ const struct sa *addr;
+ enum sip_transp tp;
+};
+
+struct sip;
+struct sip_lsnr;
+struct sip_request;
+struct sip_strans;
+struct sip_auth;
+struct sip_dialog;
+struct sip_keepalive;
+struct dnsc;
+
+typedef bool(sip_msg_h)(const struct sip_msg *msg, void *arg);
+typedef int(sip_send_h)(enum sip_transp tp, const struct sa *src,
+ const struct sa *dst, struct mbuf *mb, void *arg);
+typedef void(sip_resp_h)(int err, const struct sip_msg *msg, void *arg);
+typedef void(sip_cancel_h)(void *arg);
+typedef void(sip_exit_h)(void *arg);
+typedef int(sip_auth_h)(char **username, char **password, const char *realm,
+ void *arg);
+typedef bool(sip_hdr_h)(const struct sip_hdr *hdr, const struct sip_msg *msg,
+ void *arg);
+typedef void(sip_keepalive_h)(int err, void *arg);
+
+
+/* sip */
+int sip_alloc(struct sip **sipp, struct dnsc *dnsc, uint32_t ctsz,
+ uint32_t stsz, uint32_t tcsz, const char *software,
+ sip_exit_h *exith, void *arg);
+void sip_close(struct sip *sip, bool force);
+int sip_listen(struct sip_lsnr **lsnrp, struct sip *sip, bool req,
+ sip_msg_h *msgh, void *arg);
+int sip_debug(struct re_printf *pf, const struct sip *sip);
+int sip_send(struct sip *sip, void *sock, enum sip_transp tp,
+ const struct sa *dst, struct mbuf *mb);
+
+
+/* transport */
+int sip_transp_add(struct sip *sip, enum sip_transp tp,
+ const struct sa *laddr, ...);
+void sip_transp_flush(struct sip *sip);
+bool sip_transp_isladdr(const struct sip *sip, enum sip_transp tp,
+ const struct sa *laddr);
+const char *sip_transp_name(enum sip_transp tp);
+const char *sip_transp_param(enum sip_transp tp);
+uint16_t sip_transp_port(enum sip_transp tp, uint16_t port);
+int sip_transp_laddr(struct sip *sip, struct sa *laddr, enum sip_transp tp,
+ const struct sa *dst);
+
+
+/* request */
+int sip_request(struct sip_request **reqp, struct sip *sip, bool stateful,
+ const char *met, int metl, const char *uri, int uril,
+ const struct uri *route, struct mbuf *mb, size_t sortkey,
+ sip_send_h *sendh, sip_resp_h *resph, void *arg);
+int sip_requestf(struct sip_request **reqp, struct sip *sip, bool stateful,
+ const char *met, const char *uri, const struct uri *route,
+ struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph,
+ void *arg, const char *fmt, ...);
+int sip_drequestf(struct sip_request **reqp, struct sip *sip, bool stateful,
+ const char *met, struct sip_dialog *dlg, uint32_t cseq,
+ struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph,
+ void *arg, const char *fmt, ...);
+void sip_request_cancel(struct sip_request *req);
+bool sip_request_loops(struct sip_loopstate *ls, uint16_t scode);
+void sip_loopstate_reset(struct sip_loopstate *ls);
+
+
+/* reply */
+int sip_strans_alloc(struct sip_strans **stp, struct sip *sip,
+ const struct sip_msg *msg, sip_cancel_h *cancelh,
+ void *arg);
+int sip_strans_reply(struct sip_strans **stp, struct sip *sip,
+ const struct sip_msg *msg, const struct sa *dst,
+ uint16_t scode, struct mbuf *mb);
+int sip_treplyf(struct sip_strans **stp, struct mbuf **mbp, struct sip *sip,
+ const struct sip_msg *msg, bool rec_route, uint16_t scode,
+ const char *reason, const char *fmt, ...);
+int sip_treply(struct sip_strans **stp, struct sip *sip,
+ const struct sip_msg *msg, uint16_t scode, const char *reason);
+int sip_replyf(struct sip *sip, const struct sip_msg *msg, uint16_t scode,
+ const char *reason, const char *fmt, ...);
+int sip_reply(struct sip *sip, const struct sip_msg *msg, uint16_t scode,
+ const char *reason);
+void sip_reply_addr(struct sa *addr, const struct sip_msg *msg, bool rport);
+
+
+/* auth */
+int sip_auth_authenticate(struct sip_auth *auth, const struct sip_msg *msg);
+int sip_auth_alloc(struct sip_auth **authp, sip_auth_h *authh,
+ void *arg, bool ref);
+void sip_auth_reset(struct sip_auth *auth);
+
+
+/* contact */
+void sip_contact_set(struct sip_contact *contact, const char *uri,
+ const struct sa *addr, enum sip_transp tp);
+int sip_contact_print(struct re_printf *pf,
+ const struct sip_contact *contact);
+
+
+/* dialog */
+int sip_dialog_alloc(struct sip_dialog **dlgp,
+ const char *uri, const char *to_uri,
+ const char *from_name, const char *from_uri,
+ const char *routev[], uint32_t routec);
+int sip_dialog_accept(struct sip_dialog **dlgp, const struct sip_msg *msg);
+int sip_dialog_create(struct sip_dialog *dlg, const struct sip_msg *msg);
+int sip_dialog_fork(struct sip_dialog **dlgp, struct sip_dialog *odlg,
+ const struct sip_msg *msg);
+int sip_dialog_update(struct sip_dialog *dlg, const struct sip_msg *msg);
+bool sip_dialog_rseq_valid(struct sip_dialog *dlg, const struct sip_msg *msg);
+const char *sip_dialog_callid(const struct sip_dialog *dlg);
+uint32_t sip_dialog_lseq(const struct sip_dialog *dlg);
+bool sip_dialog_established(const struct sip_dialog *dlg);
+bool sip_dialog_cmp(const struct sip_dialog *dlg, const struct sip_msg *msg);
+bool sip_dialog_cmp_half(const struct sip_dialog *dlg,
+ const struct sip_msg *msg);
+
+
+/* msg */
+int sip_msg_decode(struct sip_msg **msgp, struct mbuf *mb);
+const struct sip_hdr *sip_msg_hdr(const struct sip_msg *msg,
+ enum sip_hdrid id);
+const struct sip_hdr *sip_msg_hdr_apply(const struct sip_msg *msg,
+ bool fwd, enum sip_hdrid id,
+ sip_hdr_h *h, void *arg);
+const struct sip_hdr *sip_msg_xhdr(const struct sip_msg *msg,
+ const char *name);
+const struct sip_hdr *sip_msg_xhdr_apply(const struct sip_msg *msg,
+ bool fwd, const char *name,
+ sip_hdr_h *h, void *arg);
+uint32_t sip_msg_hdr_count(const struct sip_msg *msg, enum sip_hdrid id);
+uint32_t sip_msg_xhdr_count(const struct sip_msg *msg, const char *name);
+bool sip_msg_hdr_has_value(const struct sip_msg *msg, enum sip_hdrid id,
+ const char *value);
+bool sip_msg_xhdr_has_value(const struct sip_msg *msg, const char *name,
+ const char *value);
+struct tcp_conn *sip_msg_tcpconn(const struct sip_msg *msg);
+void sip_msg_dump(const struct sip_msg *msg);
+
+int sip_addr_decode(struct sip_addr *addr, const struct pl *pl);
+int sip_via_decode(struct sip_via *via, const struct pl *pl);
+int sip_cseq_decode(struct sip_cseq *cseq, const struct pl *pl);
+
+
+/* keepalive */
+int sip_keepalive_start(struct sip_keepalive **kap, struct sip *sip,
+ const struct sip_msg *msg, uint32_t interval,
+ sip_keepalive_h *kah, void *arg);
diff --git a/include/re_sipevent.h b/include/re_sipevent.h
new file mode 100644
index 0000000..e8677ac
--- /dev/null
+++ b/include/re_sipevent.h
@@ -0,0 +1,123 @@
+/**
+ * @file re_sipevent.h SIP Event Framework
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/* Message Components */
+
+struct sipevent_event {
+ struct pl event;
+ struct pl params;
+ struct pl id;
+};
+
+enum sipevent_subst {
+ SIPEVENT_ACTIVE = 0,
+ SIPEVENT_PENDING,
+ SIPEVENT_TERMINATED,
+};
+
+enum sipevent_reason {
+ SIPEVENT_DEACTIVATED = 0,
+ SIPEVENT_PROBATION,
+ SIPEVENT_REJECTED,
+ SIPEVENT_TIMEOUT,
+ SIPEVENT_GIVEUP,
+ SIPEVENT_NORESOURCE,
+};
+
+struct sipevent_substate {
+ enum sipevent_subst state;
+ enum sipevent_reason reason;
+ struct pl expires;
+ struct pl retry_after;
+ struct pl params;
+};
+
+int sipevent_event_decode(struct sipevent_event *se, const struct pl *pl);
+int sipevent_substate_decode(struct sipevent_substate *ss,
+ const struct pl *pl);
+const char *sipevent_substate_name(enum sipevent_subst state);
+const char *sipevent_reason_name(enum sipevent_reason reason);
+
+
+/* Listener Socket */
+
+struct sipevent_sock;
+
+int sipevent_listen(struct sipevent_sock **sockp, struct sip *sip,
+ uint32_t htsize_not, uint32_t htsize_sub,
+ sip_msg_h *subh, void *arg);
+
+
+/* Notifier */
+
+struct sipnot;
+
+typedef void (sipnot_close_h)(int err, const struct sip_msg *msg, void *arg);
+
+int sipevent_accept(struct sipnot **notp, struct sipevent_sock *sock,
+ const struct sip_msg *msg, struct sip_dialog *dlg,
+ const struct sipevent_event *event,
+ uint16_t scode, const char *reason, uint32_t expires_min,
+ uint32_t expires_dfl, uint32_t expires_max,
+ const char *cuser, const char *ctype,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipnot_close_h *closeh, void *arg, const char *fmt, ...);
+int sipevent_notify(struct sipnot *sipnot, struct mbuf *mb,
+ enum sipevent_subst state, enum sipevent_reason reason,
+ uint32_t retry_after);
+int sipevent_notifyf(struct sipnot *sipnot, struct mbuf **mbp,
+ enum sipevent_subst state, enum sipevent_reason reason,
+ uint32_t retry_after, const char *fmt, ...);
+
+
+/* Subscriber */
+
+struct sipsub;
+
+typedef int (sipsub_fork_h)(struct sipsub **subp, struct sipsub *osub,
+ const struct sip_msg *msg, void *arg);
+typedef void (sipsub_notify_h)(struct sip *sip, const struct sip_msg *msg,
+ void *arg);
+typedef void (sipsub_close_h)(int err, const struct sip_msg *msg,
+ const struct sipevent_substate *substate,
+ void *arg);
+
+int sipevent_subscribe(struct sipsub **subp, struct sipevent_sock *sock,
+ const char *uri, const char *from_name,
+ const char *from_uri, const char *event, const char *id,
+ uint32_t expires, const char *cuser,
+ const char *routev[], uint32_t routec,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsub_fork_h *forkh, sipsub_notify_h *notifyh,
+ sipsub_close_h *closeh, void *arg,
+ const char *fmt, ...);
+int sipevent_dsubscribe(struct sipsub **subp, struct sipevent_sock *sock,
+ struct sip_dialog *dlg, const char *event,
+ const char *id, uint32_t expires, const char *cuser,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsub_notify_h *notifyh, sipsub_close_h *closeh,
+ void *arg, const char *fmt, ...);
+
+int sipevent_refer(struct sipsub **subp, struct sipevent_sock *sock,
+ const char *uri, const char *from_name,
+ const char *from_uri, const char *cuser,
+ const char *routev[], uint32_t routec,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsub_fork_h *forkh, sipsub_notify_h *notifyh,
+ sipsub_close_h *closeh, void *arg,
+ const char *fmt, ...);
+int sipevent_drefer(struct sipsub **subp, struct sipevent_sock *sock,
+ struct sip_dialog *dlg, const char *cuser,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsub_notify_h *notifyh, sipsub_close_h *closeh,
+ void *arg, const char *fmt, ...);
+
+int sipevent_fork(struct sipsub **subp, struct sipsub *osub,
+ const struct sip_msg *msg,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsub_notify_h *notifyh, sipsub_close_h *closeh,
+ void *arg);
diff --git a/include/re_sipreg.h b/include/re_sipreg.h
new file mode 100644
index 0000000..a0c1bae
--- /dev/null
+++ b/include/re_sipreg.h
@@ -0,0 +1,18 @@
+/**
+ * @file re_sipreg.h SIP Registration
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+struct sipreg;
+
+
+int sipreg_register(struct sipreg **regp, struct sip *sip, const char *reg_uri,
+ const char *to_uri, const char *from_name,
+ const char *from_uri, uint32_t expires,
+ const char *cuser, const char *routev[], uint32_t routec,
+ int regid, sip_auth_h *authh, void *aarg, bool aref,
+ sip_resp_h *resph, void *arg,
+ const char *params, const char *fmt, ...);
+
+const struct sa *sipreg_laddr(const struct sipreg *reg);
diff --git a/include/re_sipsess.h b/include/re_sipsess.h
new file mode 100644
index 0000000..e74d046
--- /dev/null
+++ b/include/re_sipsess.h
@@ -0,0 +1,60 @@
+/**
+ * @file re_sipsess.h SIP Session
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+struct sipsess_sock;
+struct sipsess;
+
+
+typedef void (sipsess_conn_h)(const struct sip_msg *msg, void *arg);
+typedef int (sipsess_offer_h)(struct mbuf **descp, const struct sip_msg *msg,
+ void *arg);
+typedef int (sipsess_answer_h)(const struct sip_msg *msg, void *arg);
+typedef void (sipsess_progr_h)(const struct sip_msg *msg, void *arg);
+typedef void (sipsess_estab_h)(const struct sip_msg *msg, void *arg);
+typedef void (sipsess_info_h)(struct sip *sip, const struct sip_msg *msg,
+ void *arg);
+typedef void (sipsess_refer_h)(struct sip *sip, const struct sip_msg *msg,
+ void *arg);
+typedef void (sipsess_close_h)(int err, const struct sip_msg *msg, void *arg);
+
+
+int sipsess_listen(struct sipsess_sock **sockp, struct sip *sip,
+ int htsize, sipsess_conn_h *connh, void *arg);
+
+int sipsess_connect(struct sipsess **sessp, struct sipsess_sock *sock,
+ const char *to_uri, const char *from_name,
+ const char *from_uri, const char *cuser,
+ const char *routev[], uint32_t routec,
+ const char *ctype, struct mbuf *desc,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsess_offer_h *offerh, sipsess_answer_h *answerh,
+ sipsess_progr_h *progrh, sipsess_estab_h *estabh,
+ sipsess_info_h *infoh, sipsess_refer_h *referh,
+ sipsess_close_h *closeh, void *arg, const char *fmt, ...);
+
+int sipsess_accept(struct sipsess **sessp, struct sipsess_sock *sock,
+ const struct sip_msg *msg, uint16_t scode,
+ const char *reason, const char *cuser, const char *ctype,
+ struct mbuf *desc,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsess_offer_h *offerh, sipsess_answer_h *answerh,
+ sipsess_estab_h *estabh, sipsess_info_h *infoh,
+ sipsess_refer_h *referh, sipsess_close_h *closeh,
+ void *arg, const char *fmt, ...);
+
+int sipsess_progress(struct sipsess *sess, uint16_t scode,
+ const char *reason, struct mbuf *desc,
+ const char *fmt, ...);
+int sipsess_answer(struct sipsess *sess, uint16_t scode, const char *reason,
+ struct mbuf *desc, const char *fmt, ...);
+int sipsess_reject(struct sipsess *sess, uint16_t scode, const char *reason,
+ const char *fmt, ...);
+int sipsess_modify(struct sipsess *sess, struct mbuf *desc);
+int sipsess_info(struct sipsess *sess, const char *ctype, struct mbuf *body,
+ sip_resp_h *resph, void *arg);
+int sipsess_set_close_headers(struct sipsess *sess, const char *hdrs, ...);
+void sipsess_close_all(struct sipsess_sock *sock);
+struct sip_dialog *sipsess_dialog(const struct sipsess *sess);
diff --git a/include/re_srtp.h b/include/re_srtp.h
new file mode 100644
index 0000000..cdb495b
--- /dev/null
+++ b/include/re_srtp.h
@@ -0,0 +1,30 @@
+/**
+ * @file re_srtp.h Secure Real-time Transport Protocol (SRTP)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+enum srtp_suite {
+ SRTP_AES_CM_128_HMAC_SHA1_32,
+ SRTP_AES_CM_128_HMAC_SHA1_80,
+ SRTP_AES_256_CM_HMAC_SHA1_32,
+ SRTP_AES_256_CM_HMAC_SHA1_80,
+ SRTP_AES_128_GCM,
+ SRTP_AES_256_GCM,
+};
+
+enum srtp_flags {
+ SRTP_UNENCRYPTED_SRTCP = 1<<1,
+};
+
+struct srtp;
+
+int srtp_alloc(struct srtp **srtpp, enum srtp_suite suite,
+ const uint8_t *key, size_t key_bytes, int flags);
+int srtp_encrypt(struct srtp *srtp, struct mbuf *mb);
+int srtp_decrypt(struct srtp *srtp, struct mbuf *mb);
+int srtcp_encrypt(struct srtp *srtp, struct mbuf *mb);
+int srtcp_decrypt(struct srtp *srtp, struct mbuf *mb);
+
+const char *srtp_suite_name(enum srtp_suite suite);
diff --git a/include/re_stun.h b/include/re_stun.h
new file mode 100644
index 0000000..4b8092a
--- /dev/null
+++ b/include/re_stun.h
@@ -0,0 +1,294 @@
+/**
+ * @file re_stun.h Session Traversal Utilities for (NAT) (STUN)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** STUN Protocol values */
+enum {
+ STUN_PORT = 3478, /**< STUN Port number */
+ STUNS_PORT = 5349, /**< STUNS Port number */
+ STUN_HEADER_SIZE = 20, /**< Number of bytes in header */
+ STUN_ATTR_HEADER_SIZE = 4, /**< Size of attribute header */
+ STUN_TID_SIZE = 12, /**< Number of bytes in transaction ID */
+ STUN_DEFAULT_RTO = 500, /**< Default Retrans Timeout in [ms] */
+ STUN_DEFAULT_RC = 7, /**< Default number of retransmits */
+ STUN_DEFAULT_RM = 16, /**< Wait time after last request is sent */
+ STUN_DEFAULT_TI = 39500 /**< Reliable timeout */
+};
+
+/** STUN Address Family */
+enum stun_af {
+ STUN_AF_IPv4 = 0x01, /**< IPv4 Address Family */
+ STUN_AF_IPv6 = 0x02 /**< IPv6 Address Family */
+};
+
+/** STUN Transport */
+enum stun_transp {
+ STUN_TRANSP_UDP = IPPROTO_UDP, /**< UDP-transport (struct udp_sock) */
+ STUN_TRANSP_TCP = IPPROTO_TCP, /**< TCP-transport (struct tcp_conn) */
+ STUN_TRANSP_DTLS, /**< DTLS-transport (struct tls_conn) */
+};
+
+/** STUN Methods */
+enum stun_method {
+ STUN_METHOD_BINDING = 0x001,
+ STUN_METHOD_ALLOCATE = 0x003,
+ STUN_METHOD_REFRESH = 0x004,
+ STUN_METHOD_SEND = 0x006,
+ STUN_METHOD_DATA = 0x007,
+ STUN_METHOD_CREATEPERM = 0x008,
+ STUN_METHOD_CHANBIND = 0x009,
+};
+
+/** STUN Message class */
+enum stun_msg_class {
+ STUN_CLASS_REQUEST = 0x0, /**< STUN Request */
+ STUN_CLASS_INDICATION = 0x1, /**< STUN Indication */
+ STUN_CLASS_SUCCESS_RESP = 0x2, /**< STUN Success Response */
+ STUN_CLASS_ERROR_RESP = 0x3 /**< STUN Error Response */
+};
+
+/** STUN Attributes */
+enum stun_attrib {
+ /* Comprehension-required range (0x0000-0x7FFF) */
+ STUN_ATTR_MAPPED_ADDR = 0x0001,
+ STUN_ATTR_CHANGE_REQ = 0x0003,
+ STUN_ATTR_USERNAME = 0x0006,
+ STUN_ATTR_MSG_INTEGRITY = 0x0008,
+ STUN_ATTR_ERR_CODE = 0x0009,
+ STUN_ATTR_UNKNOWN_ATTR = 0x000a,
+ STUN_ATTR_CHANNEL_NUMBER = 0x000c,
+ STUN_ATTR_LIFETIME = 0x000d,
+ STUN_ATTR_XOR_PEER_ADDR = 0x0012,
+ STUN_ATTR_DATA = 0x0013,
+ STUN_ATTR_REALM = 0x0014,
+ STUN_ATTR_NONCE = 0x0015,
+ STUN_ATTR_XOR_RELAY_ADDR = 0x0016,
+ STUN_ATTR_REQ_ADDR_FAMILY = 0x0017,
+ STUN_ATTR_EVEN_PORT = 0x0018,
+ STUN_ATTR_REQ_TRANSPORT = 0x0019,
+ STUN_ATTR_DONT_FRAGMENT = 0x001a,
+ STUN_ATTR_XOR_MAPPED_ADDR = 0x0020,
+ STUN_ATTR_RSV_TOKEN = 0x0022,
+ STUN_ATTR_PRIORITY = 0x0024,
+ STUN_ATTR_USE_CAND = 0x0025,
+ STUN_ATTR_PADDING = 0x0026,
+ STUN_ATTR_RESP_PORT = 0x0027,
+
+ /* Comprehension-optional range (0x8000-0xFFFF) */
+ STUN_ATTR_SOFTWARE = 0x8022,
+ STUN_ATTR_ALT_SERVER = 0x8023,
+ STUN_ATTR_FINGERPRINT = 0x8028,
+ STUN_ATTR_CONTROLLED = 0x8029,
+ STUN_ATTR_CONTROLLING = 0x802a,
+ STUN_ATTR_RESP_ORIGIN = 0x802b,
+ STUN_ATTR_OTHER_ADDR = 0x802c,
+};
+
+
+struct stun_change_req {
+ bool ip;
+ bool port;
+};
+
+struct stun_errcode {
+ uint16_t code;
+ char *reason;
+};
+
+struct stun_unknown_attr {
+ uint16_t typev[8];
+ uint32_t typec;
+};
+
+struct stun_even_port {
+ bool r;
+};
+
+/** Defines a STUN attribute */
+struct stun_attr {
+ struct le le;
+ uint16_t type;
+ union {
+ /* generic types */
+ struct sa sa;
+ char *str;
+ uint64_t uint64;
+ uint32_t uint32;
+ uint16_t uint16;
+ uint8_t uint8;
+ struct mbuf mb;
+
+ /* actual attributes */
+ struct sa mapped_addr;
+ struct stun_change_req change_req;
+ char *username;
+ uint8_t msg_integrity[20];
+ struct stun_errcode err_code;
+ struct stun_unknown_attr unknown_attr;
+ uint16_t channel_number;
+ uint32_t lifetime;
+ struct sa xor_peer_addr;
+ struct mbuf data;
+ char *realm;
+ char *nonce;
+ struct sa xor_relay_addr;
+ uint8_t req_addr_family;
+ struct stun_even_port even_port;
+ uint8_t req_transport;
+ struct sa xor_mapped_addr;
+ uint64_t rsv_token;
+ uint32_t priority;
+ struct mbuf padding;
+ uint16_t resp_port;
+ char *software;
+ struct sa alt_server;
+ uint32_t fingerprint;
+ uint64_t controlled;
+ uint64_t controlling;
+ struct sa resp_origin;
+ struct sa other_addr;
+ } v;
+};
+
+
+/** STUN Configuration */
+struct stun_conf {
+ uint32_t rto; /**< RTO Retransmission TimeOut [ms] */
+ uint32_t rc; /**< Rc Retransmission count (default 7) */
+ uint32_t rm; /**< Rm Max retransmissions (default 16) */
+ uint32_t ti; /**< Ti Timeout for reliable transport [ms] */
+ uint8_t tos; /**< Type-of-service field */
+};
+
+
+extern const char *stun_software;
+struct stun;
+struct stun_msg;
+struct stun_ctrans;
+
+typedef void(stun_resp_h)(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg);
+typedef void(stun_ind_h)(struct stun_msg *msg, void *arg);
+typedef bool(stun_attr_h)(const struct stun_attr *attr, void *arg);
+
+int stun_alloc(struct stun **stunp, const struct stun_conf *conf,
+ stun_ind_h *indh, void *arg);
+struct stun_conf *stun_conf(struct stun *stun);
+int stun_send(int proto, void *sock, const struct sa *dst, struct mbuf *mb);
+int stun_recv(struct stun *stun, struct mbuf *mb);
+int stun_ctrans_recv(struct stun *stun, const struct stun_msg *msg,
+ const struct stun_unknown_attr *ua);
+struct re_printf;
+int stun_debug(struct re_printf *pf, const struct stun *stun);
+
+int stun_request(struct stun_ctrans **ctp, struct stun *stun, int proto,
+ void *sock, const struct sa *dst, size_t presz,
+ uint16_t method, const uint8_t *key, size_t keylen, bool fp,
+ stun_resp_h *resph, void *arg, uint32_t attrc, ...);
+int stun_reply(int proto, void *sock, const struct sa *dst, size_t presz,
+ const struct stun_msg *req, const uint8_t *key,
+ size_t keylen, bool fp, uint32_t attrc, ...);
+int stun_ereply(int proto, void *sock, const struct sa *dst, size_t presz,
+ const struct stun_msg *req, uint16_t scode,
+ const char *reason, const uint8_t *key, size_t keylen,
+ bool fp, uint32_t attrc, ...);
+int stun_indication(int proto, void *sock, const struct sa *dst, size_t presz,
+ uint16_t method, const uint8_t *key, size_t keylen,
+ bool fp, uint32_t attrc, ...);
+
+int stun_msg_vencode(struct mbuf *mb, uint16_t method, uint8_t cls,
+ const uint8_t *tid, const struct stun_errcode *ec,
+ const uint8_t *key, size_t keylen, bool fp,
+ uint8_t padding, uint32_t attrc, va_list ap);
+int stun_msg_encode(struct mbuf *mb, uint16_t method, uint8_t cls,
+ const uint8_t *tid, const struct stun_errcode *ec,
+ const uint8_t *key, size_t keylen, bool fp,
+ uint8_t padding, uint32_t attrc, ...);
+int stun_msg_decode(struct stun_msg **msgpp, struct mbuf *mb,
+ struct stun_unknown_attr *ua);
+uint16_t stun_msg_type(const struct stun_msg *msg);
+uint16_t stun_msg_class(const struct stun_msg *msg);
+uint16_t stun_msg_method(const struct stun_msg *msg);
+bool stun_msg_mcookie(const struct stun_msg *msg);
+const uint8_t *stun_msg_tid(const struct stun_msg *msg);
+struct stun_attr *stun_msg_attr(const struct stun_msg *msg, uint16_t type);
+struct stun_attr *stun_msg_attr_apply(const struct stun_msg *msg,
+ stun_attr_h *h, void *arg);
+int stun_msg_chk_mi(const struct stun_msg *msg, const uint8_t *key,
+ size_t keylen);
+int stun_msg_chk_fingerprint(const struct stun_msg *msg);
+void stun_msg_dump(const struct stun_msg *msg);
+
+const char *stun_class_name(uint16_t cls);
+const char *stun_method_name(uint16_t method);
+const char *stun_attr_name(uint16_t type);
+const char *stun_transp_name(enum stun_transp tp);
+
+
+/* DNS Discovery of a STUN Server */
+extern const char *stun_proto_udp;
+extern const char *stun_proto_tcp;
+
+extern const char *stun_usage_binding;
+extern const char *stuns_usage_binding;
+extern const char *stun_usage_relay;
+extern const char *stuns_usage_relay;
+extern const char *stun_usage_behavior;
+extern const char *stuns_usage_behavior;
+
+
+/**
+ * Defines the STUN Server Discovery handler
+ *
+ * @param err Errorcode
+ * @param srv IP Address and port of STUN Server
+ * @param arg Handler argument
+ */
+typedef void (stun_dns_h)(int err, const struct sa *srv, void *arg);
+
+struct stun_dns;
+struct dnsc;
+int stun_server_discover(struct stun_dns **dnsp, struct dnsc *dnsc,
+ const char *service, const char *proto,
+ int af, const char *domain, uint16_t port,
+ stun_dns_h *dnsh, void *arg);
+
+
+/* NAT Keepalives */
+struct stun_keepalive;
+
+/**
+ * Defines the STUN Keepalive Mapped-Address handler
+ *
+ * @param err Errorcode
+ * @param map Mapped Address
+ * @param arg Handler argument
+ */
+typedef void (stun_mapped_addr_h)(int err, const struct sa *map, void *arg);
+
+
+int stun_keepalive_alloc(struct stun_keepalive **skap,
+ int proto, void *sock, int layer,
+ const struct sa *dst, const struct stun_conf *conf,
+ stun_mapped_addr_h *mah, void *arg);
+void stun_keepalive_enable(struct stun_keepalive *ska, uint32_t interval);
+
+
+/* STUN Reason Phrase */
+extern const char *stun_reason_300;
+extern const char *stun_reason_400;
+extern const char *stun_reason_401;
+extern const char *stun_reason_403;
+extern const char *stun_reason_420;
+extern const char *stun_reason_437;
+extern const char *stun_reason_438;
+extern const char *stun_reason_440;
+extern const char *stun_reason_441;
+extern const char *stun_reason_442;
+extern const char *stun_reason_443;
+extern const char *stun_reason_486;
+extern const char *stun_reason_500;
+extern const char *stun_reason_508;
diff --git a/include/re_sys.h b/include/re_sys.h
new file mode 100644
index 0000000..598e35c
--- /dev/null
+++ b/include/re_sys.h
@@ -0,0 +1,73 @@
+/**
+ * @file re_sys.h Interface to system module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifndef VERSION
+#define VERSION "?"
+#endif
+
+/**
+ * @def ARCH
+ *
+ * Architecture
+ */
+#ifndef ARCH
+#define ARCH "?"
+#endif
+
+/**
+ * @def OS
+ *
+ * Operating System
+ */
+#ifndef OS
+#ifdef WIN32
+#define OS "win32"
+#else
+#define OS "?"
+#endif
+#endif
+
+struct re_printf;
+int sys_rel_get(uint32_t *rel, uint32_t *maj, uint32_t *min,
+ uint32_t *patch);
+int sys_kernel_get(struct re_printf *pf, void *unused);
+int sys_build_get(struct re_printf *pf, void *unused);
+const char *sys_arch_get(void);
+const char *sys_os_get(void);
+const char *sys_libre_version_get(void);
+const char *sys_username(void);
+int sys_coredump_set(bool enable);
+int sys_daemon(void);
+void sys_usleep(unsigned int us);
+
+static inline void sys_msleep(unsigned int ms)
+{
+ sys_usleep(ms * 1000);
+}
+
+
+uint16_t sys_htols(uint16_t v);
+uint32_t sys_htoll(uint32_t v);
+uint16_t sys_ltohs(uint16_t v);
+uint32_t sys_ltohl(uint32_t v);
+uint64_t sys_htonll(uint64_t v);
+uint64_t sys_ntohll(uint64_t v);
+
+
+/* Random */
+void rand_init(void);
+uint16_t rand_u16(void);
+uint32_t rand_u32(void);
+uint64_t rand_u64(void);
+char rand_char(void);
+void rand_str(char *str, size_t size);
+void rand_bytes(uint8_t *p, size_t size);
+
+
+/* File-System */
+int fs_mkdir(const char *path, uint16_t mode);
+int fs_gethome(char *path, size_t sz);
diff --git a/include/re_tcp.h b/include/re_tcp.h
new file mode 100644
index 0000000..98dba71
--- /dev/null
+++ b/include/re_tcp.h
@@ -0,0 +1,101 @@
+/**
+ * @file re_tcp.h Interface to Transport Control Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+struct sa;
+struct tcp_sock;
+struct tcp_conn;
+
+
+/**
+ * Defines the incoming TCP connection handler
+ *
+ * @param peer Network address of peer
+ * @param arg Handler argument
+ */
+typedef void (tcp_conn_h)(const struct sa *peer, void *arg);
+
+/**
+ * Defines the TCP connection established handler
+ *
+ * @param arg Handler argument
+ */
+typedef void (tcp_estab_h)(void *arg);
+
+/**
+ * Defines the TCP connection data send handler
+ *
+ * @param arg Handler argument
+ */
+typedef void (tcp_send_h)(void *arg);
+
+/**
+ * Defines the TCP connection data receive handler
+ *
+ * @param mb Buffer with data
+ * @param arg Handler argument
+ */
+typedef void (tcp_recv_h)(struct mbuf *mb, void *arg);
+
+/**
+ * Defines the TCP connection close handler
+ *
+ * @param err Error code
+ * @param arg Handler argument
+ */
+typedef void (tcp_close_h)(int err, void *arg);
+
+
+/* TCP Socket */
+int tcp_sock_alloc(struct tcp_sock **tsp, const struct sa *local,
+ tcp_conn_h *ch, void *arg);
+int tcp_sock_bind(struct tcp_sock *ts, const struct sa *local);
+int tcp_sock_listen(struct tcp_sock *ts, int backlog);
+int tcp_accept(struct tcp_conn **tcp, struct tcp_sock *ts, tcp_estab_h *eh,
+ tcp_recv_h *rh, tcp_close_h *ch, void *arg);
+void tcp_reject(struct tcp_sock *ts);
+int tcp_sock_local_get(const struct tcp_sock *ts, struct sa *local);
+
+
+/* TCP Connection */
+int tcp_conn_alloc(struct tcp_conn **tcp, const struct sa *peer,
+ tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch,
+ void *arg);
+int tcp_conn_bind(struct tcp_conn *tc, const struct sa *local);
+int tcp_conn_connect(struct tcp_conn *tc, const struct sa *peer);
+int tcp_send(struct tcp_conn *tc, struct mbuf *mb);
+int tcp_set_send(struct tcp_conn *tc, tcp_send_h *sendh);
+void tcp_set_handlers(struct tcp_conn *tc, tcp_estab_h *eh, tcp_recv_h *rh,
+ tcp_close_h *ch, void *arg);
+void tcp_conn_rxsz_set(struct tcp_conn *tc, size_t rxsz);
+void tcp_conn_txqsz_set(struct tcp_conn *tc, size_t txqsz);
+int tcp_conn_local_get(const struct tcp_conn *tc, struct sa *local);
+int tcp_conn_peer_get(const struct tcp_conn *tc, struct sa *peer);
+int tcp_conn_fd(const struct tcp_conn *tc);
+size_t tcp_conn_txqsz(const struct tcp_conn *tc);
+
+
+/* High-level API */
+int tcp_listen(struct tcp_sock **tsp, const struct sa *local,
+ tcp_conn_h *ch, void *arg);
+int tcp_connect(struct tcp_conn **tcp, const struct sa *peer,
+ tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch, void *arg);
+int tcp_local_get(const struct tcp_sock *ts, struct sa *local);
+
+
+/* Helper API */
+typedef bool (tcp_helper_estab_h)(int *err, bool active, void *arg);
+typedef bool (tcp_helper_send_h)(int *err, struct mbuf *mb, void *arg);
+typedef bool (tcp_helper_recv_h)(int *err, struct mbuf *mb, bool *estab,
+ void *arg);
+
+struct tcp_helper;
+
+
+int tcp_register_helper(struct tcp_helper **thp, struct tcp_conn *tc,
+ int layer,
+ tcp_helper_estab_h *eh, tcp_helper_send_h *sh,
+ tcp_helper_recv_h *rh, void *arg);
+int tcp_send_helper(struct tcp_conn *tc, struct mbuf *mb,
+ struct tcp_helper *th);
diff --git a/include/re_telev.h b/include/re_telev.h
new file mode 100644
index 0000000..ed22a5d
--- /dev/null
+++ b/include/re_telev.h
@@ -0,0 +1,23 @@
+/**
+ * @file re_telev.h Interface to Telephony Events (RFC 4733)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+enum {
+ TELEV_PTIME = 50,
+ TELEV_SRATE = 8000
+};
+
+struct telev;
+
+extern const char telev_rtpfmt[];
+
+int telev_alloc(struct telev **tp, uint32_t ptime);
+int telev_set_srate(struct telev *tel, uint32_t srate);
+int telev_send(struct telev *tel, int event, bool end);
+int telev_recv(struct telev *tel, struct mbuf *mb, int *event, bool *end);
+int telev_poll(struct telev *tel, bool *marker, struct mbuf *mb);
+
+int telev_digit2code(int digit);
+int telev_code2digit(int code);
diff --git a/include/re_tls.h b/include/re_tls.h
new file mode 100644
index 0000000..05b3c93
--- /dev/null
+++ b/include/re_tls.h
@@ -0,0 +1,101 @@
+/**
+ * @file re_tls.h Interface to Transport Layer Security
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct tls;
+struct tls_conn;
+struct tcp_conn;
+struct udp_sock;
+
+
+/** Defines the TLS method */
+enum tls_method {
+ TLS_METHOD_SSLV23,
+ TLS_METHOD_DTLSV1,
+ TLS_METHOD_DTLS, /* DTLS 1.0 and 1.2 */
+ TLS_METHOD_DTLSV1_2, /* DTLS 1.2 */
+};
+
+enum tls_fingerprint {
+ TLS_FINGERPRINT_SHA1,
+ TLS_FINGERPRINT_SHA256,
+};
+
+enum tls_keytype {
+ TLS_KEYTYPE_RSA,
+ TLS_KEYTYPE_EC,
+};
+
+
+int tls_alloc(struct tls **tlsp, enum tls_method method, const char *keyfile,
+ const char *pwd);
+int tls_add_ca(struct tls *tls, const char *cafile);
+int tls_set_selfsigned(struct tls *tls, const char *cn);
+int tls_set_certificate_pem(struct tls *tls, const char *cert, size_t len_cert,
+ const char *key, size_t len_key);
+int tls_set_certificate_der(struct tls *tls, enum tls_keytype keytype,
+ const uint8_t *cert, size_t len_cert,
+ const uint8_t *key, size_t len_key);
+int tls_set_certificate(struct tls *tls, const char *cert, size_t len);
+void tls_set_verify_client(struct tls *tls);
+int tls_set_srtp(struct tls *tls, const char *suites);
+int tls_fingerprint(const struct tls *tls, enum tls_fingerprint type,
+ uint8_t *md, size_t size);
+
+int tls_peer_fingerprint(const struct tls_conn *tc, enum tls_fingerprint type,
+ uint8_t *md, size_t size);
+int tls_peer_common_name(const struct tls_conn *tc, char *cn, size_t size);
+int tls_peer_verify(const struct tls_conn *tc);
+int tls_srtp_keyinfo(const struct tls_conn *tc, enum srtp_suite *suite,
+ uint8_t *cli_key, size_t cli_key_size,
+ uint8_t *srv_key, size_t srv_key_size);
+const char *tls_cipher_name(const struct tls_conn *tc);
+int tls_set_ciphers(struct tls *tls, const char *cipherv[], size_t count);
+int tls_set_servername(struct tls_conn *tc, const char *servername);
+
+
+/* TCP */
+
+int tls_start_tcp(struct tls_conn **ptc, struct tls *tls,
+ struct tcp_conn *tcp, int layer);
+
+
+/* UDP (DTLS) */
+
+typedef void (dtls_conn_h)(const struct sa *peer, void *arg);
+typedef void (dtls_estab_h)(void *arg);
+typedef void (dtls_recv_h)(struct mbuf *mb, void *arg);
+typedef void (dtls_close_h)(int err, void *arg);
+
+struct dtls_sock;
+
+int dtls_listen(struct dtls_sock **sockp, const struct sa *laddr,
+ struct udp_sock *us, uint32_t htsize, int layer,
+ dtls_conn_h *connh, void *arg);
+struct udp_sock *dtls_udp_sock(struct dtls_sock *sock);
+void dtls_set_mtu(struct dtls_sock *sock, size_t mtu);
+int dtls_connect(struct tls_conn **ptc, struct tls *tls,
+ struct dtls_sock *sock, const struct sa *peer,
+ dtls_estab_h *estabh, dtls_recv_h *recvh,
+ dtls_close_h *closeh, void *arg);
+int dtls_accept(struct tls_conn **ptc, struct tls *tls,
+ struct dtls_sock *sock,
+ dtls_estab_h *estabh, dtls_recv_h *recvh,
+ dtls_close_h *closeh, void *arg);
+int dtls_send(struct tls_conn *tc, struct mbuf *mb);
+void dtls_set_handlers(struct tls_conn *tc, dtls_estab_h *estabh,
+ dtls_recv_h *recvh, dtls_close_h *closeh, void *arg);
+const struct sa *dtls_peer(const struct tls_conn *tc);
+void dtls_set_peer(struct tls_conn *tc, const struct sa *peer);
+void dtls_recv_packet(struct dtls_sock *sock, const struct sa *src,
+ struct mbuf *mb);
+
+
+#ifdef USE_OPENSSL
+struct ssl_ctx_st;
+
+struct ssl_ctx_st *tls_openssl_context(const struct tls *tls);
+#endif
diff --git a/include/re_tmr.h b/include/re_tmr.h
new file mode 100644
index 0000000..b399fb1
--- /dev/null
+++ b/include/re_tmr.h
@@ -0,0 +1,46 @@
+/**
+ * @file re_tmr.h Interface to timer implementation
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/**
+ * Defines the timeout handler
+ *
+ * @param arg Handler argument
+ */
+typedef void (tmr_h)(void *arg);
+
+/** Defines a timer */
+struct tmr {
+ struct le le; /**< Linked list element */
+ tmr_h *th; /**< Timeout handler */
+ void *arg; /**< Handler argument */
+ uint64_t jfs; /**< Jiffies for timeout */
+};
+
+
+void tmr_poll(struct list *tmrl);
+uint64_t tmr_jiffies(void);
+uint64_t tmr_next_timeout(struct list *tmrl);
+void tmr_debug(void);
+int tmr_status(struct re_printf *pf, void *unused);
+
+void tmr_init(struct tmr *tmr);
+void tmr_start(struct tmr *tmr, uint64_t delay, tmr_h *th, void *arg);
+void tmr_cancel(struct tmr *tmr);
+uint64_t tmr_get_expire(const struct tmr *tmr);
+
+
+/**
+ * Check if the timer is running
+ *
+ * @param tmr Timer to check
+ *
+ * @return true if running, false if not running
+ */
+static inline bool tmr_isrunning(const struct tmr *tmr)
+{
+ return tmr ? NULL != tmr->th : false;
+}
diff --git a/include/re_turn.h b/include/re_turn.h
new file mode 100644
index 0000000..6b425d2
--- /dev/null
+++ b/include/re_turn.h
@@ -0,0 +1,33 @@
+/**
+ * @file re_turn.h Interface to TURN implementation
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** TURN Protocol values */
+enum {
+ TURN_DEFAULT_LIFETIME = 600, /**< Default lifetime is 10 minutes */
+ TURN_MAX_LIFETIME = 3600 /**< Maximum lifetime is 1 hour */
+};
+
+typedef void(turnc_h)(int err, uint16_t scode, const char *reason,
+ const struct sa *relay_addr,
+ const struct sa *mapped_addr,
+ const struct stun_msg *msg,
+ void *arg);
+typedef void(turnc_perm_h)(void *arg);
+typedef void(turnc_chan_h)(void *arg);
+
+struct turnc;
+
+int turnc_alloc(struct turnc **turncp, const struct stun_conf *conf, int proto,
+ void *sock, int layer, const struct sa *srv,
+ const char *username, const char *password,
+ uint32_t lifetime, turnc_h *th, void *arg);
+int turnc_send(struct turnc *turnc, const struct sa *dst, struct mbuf *mb);
+int turnc_recv(struct turnc *turnc, struct sa *src, struct mbuf *mb);
+int turnc_add_perm(struct turnc *turnc, const struct sa *peer,
+ turnc_perm_h *ph, void *arg);
+int turnc_add_chan(struct turnc *turnc, const struct sa *peer,
+ turnc_chan_h *ch, void *arg);
diff --git a/include/re_types.h b/include/re_types.h
new file mode 100644
index 0000000..8acb90c
--- /dev/null
+++ b/include/re_types.h
@@ -0,0 +1,255 @@
+/**
+ * @file re_types.h Defines basic types
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <sys/types.h>
+
+#ifdef _MSC_VER
+#include <stdlib.h>
+#endif
+
+/*
+ * Basic integral types from C99
+ */
+
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#else
+
+#ifndef __int8_t_defined
+#define __int8_t_defined
+
+/* Hack for OpenBSD */
+#ifndef __BIT_TYPES_DEFINED__
+
+#if defined(_CHAR_IS_SIGNED)
+typedef char int8_t;
+#elif defined(__STDC__)
+typedef signed char int8_t;
+#else
+typedef char int8_t;
+#endif
+
+typedef signed short int int16_t;
+typedef signed int int32_t;
+typedef signed long long int int64_t;
+
+#ifndef __uint32_t_defined
+#define __uint32_t_defined
+typedef unsigned char uint8_t;
+typedef unsigned short int uint16_t;
+typedef unsigned int uint32_t;
+typedef unsigned long long int uint64_t;
+#endif
+
+#endif /* __BIT_TYPES_DEFINED__ */
+
+#endif /* __int8_t_defined */
+#ifndef __ssize_t_defined
+typedef long ssize_t;
+#define __ssize_t_defined
+#endif
+
+
+#ifndef WIN32
+typedef uint32_t socklen_t;
+#endif
+#endif
+
+
+/*
+ * Hack for Solaris which does not define int64_t/uint64_t for strict ANSI C
+ */
+#ifdef SOLARIS
+#if !(__STDC__ - 0 == 0 && !defined(_NO_LONGLONG))
+typedef signed long long int int64_t;
+typedef unsigned long long int uint64_t;
+#endif
+#endif
+
+
+/*
+ * Boolean type
+ * see http://www.opengroup.org/onlinepubs/000095399/basedefs/stdbool.h.html
+ * www.gnu.org/software/autoconf/manual/html_node/Particular-Headers.html
+ */
+#ifdef HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# ifndef HAVE__BOOL
+# ifdef __cplusplus
+typedef bool _Bool;
+# else
+# define _Bool signed char
+# endif
+# endif
+# define bool _Bool
+# define false 0
+# define true 1
+# define __bool_true_false_are_defined 1
+#endif
+
+/* Needed for MS compiler */
+#ifdef _MSC_VER
+#ifndef __cplusplus
+#define inline _inline
+#endif
+#endif
+
+
+/*
+ * Misc macros
+ */
+
+/** Defines the NULL pointer */
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
+/** Get number of elements in an array */
+#undef ARRAY_SIZE
+#define ARRAY_SIZE(a) ((sizeof(a))/(sizeof((a)[0])))
+
+/** Align a value to the boundary of mask */
+#define ALIGN_MASK(x, mask) (((x)+(mask))&~(mask))
+
+
+/** Get the minimal value */
+#undef MIN
+#define MIN(a,b) (((a)<(b)) ? (a) : (b))
+
+/** Get the maximal value */
+#undef MAX
+#define MAX(a,b) (((a)>(b)) ? (a) : (b))
+
+#ifndef __cplusplus
+
+/** Get the minimal value */
+#undef min
+#define min(x,y) MIN(x, y)
+
+/** Get the maximal value */
+#undef max
+#define max(x,y) MAX(x, y)
+
+#endif
+
+/** Defines a soft breakpoint */
+#if (defined(__i386__) || defined(__x86_64__))
+#define BREAKPOINT __asm__("int $0x03")
+#else
+#define BREAKPOINT
+#endif
+
+
+/* Error codes */
+#include <errno.h>
+
+/* Duplication of error codes. Values are from linux asm-generic/errno.h */
+
+/** No data available */
+#ifndef ENODATA
+#define ENODATA 200
+#endif
+
+/** Protocol error */
+#ifndef EPROTO
+#define EPROTO 201
+#endif
+
+/** Not a data message */
+#ifndef EBADMSG
+#define EBADMSG 202
+#endif
+
+/** Value too large for defined data type */
+#ifndef EOVERFLOW
+#define EOVERFLOW 203
+#endif
+
+/** Accessing a corrupted shared library */
+#ifndef ELIBBAD
+#define ELIBBAD 204
+#endif
+
+/** Destination address required */
+#ifndef EDESTADDRREQ
+#define EDESTADDRREQ 205
+#endif
+
+/** Protocol not supported */
+#ifndef EPROTONOSUPPORT
+#define EPROTONOSUPPORT 206
+#endif
+
+/** Operation not supported */
+#ifndef ENOTSUP
+#define ENOTSUP 207
+#endif
+
+/** Address family not supported by protocol */
+#ifndef EAFNOSUPPORT
+#define EAFNOSUPPORT 208
+#endif
+
+/** Cannot assign requested address */
+#ifndef EADDRNOTAVAIL
+#define EADDRNOTAVAIL 209
+#endif
+
+/** Software caused connection abort */
+#ifndef ECONNABORTED
+#define ECONNABORTED 210
+#endif
+
+/** Connection reset by peer */
+#ifndef ECONNRESET
+#define ECONNRESET 211
+#endif
+
+/** Transport endpoint is not connected */
+#ifndef ENOTCONN
+#define ENOTCONN 212
+#endif
+
+/** Connection timed out */
+#ifndef ETIMEDOUT
+#define ETIMEDOUT 213
+#endif
+
+/** Connection refused */
+#ifndef ECONNREFUSED
+#define ECONNREFUSED 214
+#endif
+
+/** Operation already in progress */
+#ifndef EALREADY
+#define EALREADY 215
+#endif
+
+/** Operation now in progress */
+#ifndef EINPROGRESS
+#define EINPROGRESS 216
+#endif
+
+/** Authentication error */
+#ifndef EAUTH
+#define EAUTH 217
+#endif
+
+/** No STREAM resources */
+#ifndef ENOSR
+#define ENOSR 218
+#endif
+
+
+/*
+ * Any C compiler conforming to C99 or later MUST support __func__
+ */
+#if __STDC_VERSION__ >= 199901L
+#define __REFUNC__ (const char *)__func__
+#else
+#define __REFUNC__ __FUNCTION__
+#endif
diff --git a/include/re_udp.h b/include/re_udp.h
new file mode 100644
index 0000000..f744f2f
--- /dev/null
+++ b/include/re_udp.h
@@ -0,0 +1,59 @@
+/**
+ * @file re_udp.h Interface to User Datagram Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct sa;
+struct udp_sock;
+
+
+/**
+ * Defines the UDP Receive handler
+ *
+ * @param src Source address
+ * @param mb Datagram buffer
+ * @param arg Handler argument
+ */
+typedef void (udp_recv_h)(const struct sa *src, struct mbuf *mb, void *arg);
+typedef void (udp_error_h)(int err, void *arg);
+
+
+int udp_listen(struct udp_sock **usp, const struct sa *local,
+ udp_recv_h *rh, void *arg);
+int udp_connect(struct udp_sock *us, const struct sa *peer);
+int udp_send(struct udp_sock *us, const struct sa *dst, struct mbuf *mb);
+int udp_send_anon(const struct sa *dst, struct mbuf *mb);
+int udp_local_get(const struct udp_sock *us, struct sa *local);
+int udp_setsockopt(struct udp_sock *us, int level, int optname,
+ const void *optval, uint32_t optlen);
+int udp_sockbuf_set(struct udp_sock *us, int size);
+void udp_rxsz_set(struct udp_sock *us, size_t rxsz);
+void udp_rxbuf_presz_set(struct udp_sock *us, size_t rx_presz);
+void udp_handler_set(struct udp_sock *us, udp_recv_h *rh, void *arg);
+void udp_error_handler_set(struct udp_sock *us, udp_error_h *eh);
+int udp_thread_attach(struct udp_sock *us);
+void udp_thread_detach(struct udp_sock *us);
+int udp_sock_fd(const struct udp_sock *us, int af);
+
+int udp_multicast_join(struct udp_sock *us, const struct sa *group);
+int udp_multicast_leave(struct udp_sock *us, const struct sa *group);
+
+
+/* Helper API */
+typedef bool (udp_helper_send_h)(int *err, struct sa *dst,
+ struct mbuf *mb, void *arg);
+typedef bool (udp_helper_recv_h)(struct sa *src,
+ struct mbuf *mb, void *arg);
+
+struct udp_helper;
+
+
+int udp_register_helper(struct udp_helper **uhp, struct udp_sock *us,
+ int layer,
+ udp_helper_send_h *sh, udp_helper_recv_h *rh,
+ void *arg);
+int udp_send_helper(struct udp_sock *us, const struct sa *dst,
+ struct mbuf *mb, struct udp_helper *uh);
+struct udp_helper *udp_helper_find(const struct udp_sock *us, int layer);
diff --git a/include/re_uri.h b/include/re_uri.h
new file mode 100644
index 0000000..d323e5c
--- /dev/null
+++ b/include/re_uri.h
@@ -0,0 +1,45 @@
+/**
+ * @file re_uri.h Interface to URI module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** Defines a URI - Uniform Resource Identifier */
+struct uri {
+ struct pl scheme; /**< URI scheme e.g. "sip:" "sips:" */
+ struct pl user; /**< Username */
+ struct pl password; /**< Optional password */
+ struct pl host; /**< Hostname or IP-address */
+ int af; /**< Address family of host IP-address */
+ uint16_t port; /**< Port number */
+ struct pl params; /**< Optional URI-parameters */
+ struct pl headers; /**< Optional URI-headers */
+};
+
+typedef int (uri_apply_h)(const struct pl *name, const struct pl *val,
+ void *arg);
+
+struct re_printf;
+int uri_encode(struct re_printf *pf, const struct uri *uri);
+int uri_decode(struct uri *uri, const struct pl *pl);
+int uri_decode_hostport(const struct pl *hostport, struct pl *host,
+ struct pl *port);
+int uri_param_get(const struct pl *pl, const struct pl *pname,
+ struct pl *pvalue);
+int uri_params_apply(const struct pl *pl, uri_apply_h *ah, void *arg);
+int uri_header_get(const struct pl *pl, const struct pl *hname,
+ struct pl *hvalue);
+int uri_headers_apply(const struct pl *pl, uri_apply_h *ah, void *arg);
+bool uri_cmp(const struct uri *l, const struct uri *r);
+
+
+/* Special URI escaping/unescaping */
+int uri_user_escape(struct re_printf *pf, const struct pl *pl);
+int uri_user_unescape(struct re_printf *pf, const struct pl *pl);
+int uri_password_escape(struct re_printf *pf, const struct pl *pl);
+int uri_password_unescape(struct re_printf *pf, const struct pl *pl);
+int uri_param_escape(struct re_printf *pf, const struct pl *pl);
+int uri_param_unescape(struct re_printf *pf, const struct pl *pl);
+int uri_header_escape(struct re_printf *pf, const struct pl *pl);
+int uri_header_unescape(struct re_printf *pf, const struct pl *pl);
diff --git a/include/re_websock.h b/include/re_websock.h
new file mode 100644
index 0000000..78551ff
--- /dev/null
+++ b/include/re_websock.h
@@ -0,0 +1,74 @@
+/**
+ * @file re_websock.h The WebSocket Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+enum {
+ WEBSOCK_VERSION = 13,
+};
+
+enum websock_opcode {
+ /* Data frames */
+ WEBSOCK_CONT = 0x0,
+ WEBSOCK_TEXT = 0x1,
+ WEBSOCK_BIN = 0x2,
+ /* Control frames */
+ WEBSOCK_CLOSE = 0x8,
+ WEBSOCK_PING = 0x9,
+ WEBSOCK_PONG = 0xa,
+};
+
+enum websock_scode {
+ WEBSOCK_NORMAL_CLOSURE = 1000,
+ WEBSOCK_GOING_AWAY = 1001,
+ WEBSOCK_PROTOCOL_ERROR = 1002,
+ WEBSOCK_UNSUPPORTED_DATA = 1003,
+ WEBSOCK_INVALID_PAYLOAD = 1007,
+ WEBSOCK_POLICY_VIOLATION = 1008,
+ WEBSOCK_MESSAGE_TOO_BIG = 1009,
+ WEBSOCK_EXTENSION_ERROR = 1010,
+ WEBSOCK_INTERNAL_ERROR = 1011,
+};
+
+struct websock_hdr {
+ unsigned fin:1;
+ unsigned rsv1:1;
+ unsigned rsv2:1;
+ unsigned rsv3:1;
+ unsigned opcode:4;
+ unsigned mask:1;
+ uint64_t len;
+ uint8_t mkey[4];
+};
+
+struct websock;
+struct websock_conn;
+
+typedef void (websock_estab_h)(void *arg);
+typedef void (websock_recv_h)(const struct websock_hdr *hdr, struct mbuf *mb,
+ void *arg);
+typedef void (websock_close_h)(int err, void *arg);
+
+
+int websock_connect(struct websock_conn **connp, struct websock *sock,
+ struct http_cli *cli, const char *uri, unsigned kaint,
+ websock_estab_h *estabh, websock_recv_h *recvh,
+ websock_close_h *closeh, void *arg,
+ const char *fmt, ...);
+int websock_accept(struct websock_conn **connp, struct websock *sock,
+ struct http_conn *htconn, const struct http_msg *msg,
+ unsigned kaint, websock_recv_h *recvh,
+ websock_close_h *closeh, void *arg);
+int websock_send(struct websock_conn *conn, enum websock_opcode opcode,
+ const char *fmt, ...);
+int websock_close(struct websock_conn *conn, enum websock_scode scode,
+ const char *fmt, ...);
+const struct sa *websock_peer(const struct websock_conn *conn);
+
+typedef void (websock_shutdown_h)(void *arg);
+
+int websock_alloc(struct websock **sockp, websock_shutdown_h *shuth,
+ void *arg);
+void websock_shutdown(struct websock *sock);
diff --git a/mk/CMakeLists.txt b/mk/CMakeLists.txt
new file mode 100644
index 0000000..0ae3ccc
--- /dev/null
+++ b/mk/CMakeLists.txt
@@ -0,0 +1,151 @@
+cmake_minimum_required(VERSION 2.8.2)
+
+project(re)
+set(CMAKE_VERBOSE_MAKEFILE ON)
+
+SET(RE_ROOT ${PROJECT_SOURCE_DIR}/..)
+SET(RE_SRC_PREFIX ../src)
+
+# include header files for the IDEs
+file(GLOB_RECURSE HEADER_FILES src/*.h include/*.h)
+
+if(UNIX)
+ # get make db and extract information via regex's
+ execute_process(COMMAND make --no-print-directory --just-print --print-data-base info OUTPUT_VARIABLE RE_MAKEDB OUTPUT_STRIP_TRAILING_WHITESPACE WORKING_DIRECTORY ${RE_ROOT})
+
+ # get list of source files in SRCS = ...
+ STRING(REGEX MATCH "[\n\r]SRCS = ([^\n\r]+)" RE_SRCS "${RE_MAKEDB}")
+ STRING(REGEX REPLACE "[\n\r]SRCS = " "" RE_SRCS "${RE_SRCS}")
+ # convert into list and prefix every element with 'src/'
+ SET(RE_SRCS "${RE_SRC_PREFIX}/${RE_SRCS}")
+ STRING(REPLACE " " ";${RE_SRC_PREFIX}/" RE_SRCS ${RE_SRCS})
+
+ # get CFLAGS
+ STRING(REGEX MATCH "[\n\r]CFLAGS = ([^\n\r]+)" RE_CFLAGS "${RE_MAKEDB}")
+ STRING(REGEX REPLACE "[\n\r]CFLAGS = " "" RE_CFLAGS "${RE_CFLAGS}")
+ # remove anything that is not a macro define
+ STRING(REGEX REPLACE "-[^D][^ ]*" "" RE_CFLAGS "${RE_CFLAGS}" )
+
+ # get EXTRA_CFLAGS
+ STRING(REGEX MATCH "[\n\r]EXTRA_CFLAGS := ([^\n\r]+)" RE_EXTRA_CFLAGS "${RE_MAKEDB}")
+ STRING(REGEX REPLACE "[\n\r]EXTRA_CFLAGS := " "" RE_EXTRA_CFLAGS "${RE_EXTRA_CFLAGS}")
+ if (RE_EXTRA_CFLAGS)
+ # not tested
+ STRING(REGEX REPLACE "\\$\\(EXTRA_CFLAGS\\)" ${RE_EXTRA_CFLAGS} RE_CFLAGS "${RE_CFLAGS}")
+ else()
+ STRING(REGEX REPLACE "\\$\\(EXTRA_CFLAGS\\)" "" RE_CFLAGS "${RE_CFLAGS}")
+ endif()
+
+ # get OS
+ STRING(REGEX MATCH "[\n\r]OS := ([^\n\r]+)" RE_OS "${RE_MAKEDB}")
+ STRING(REGEX REPLACE "[\n\r]OS := " "" RE_OS "${RE_OS}")
+ STRING(REGEX REPLACE "\\$\\(OS\\)" ${RE_OS} RE_CFLAGS "${RE_CFLAGS}")
+
+ # get VERSION
+ STRING(REGEX MATCH "[\n\r]VERSION := ([^\n\r]+)" RE_VERSION "${RE_MAKEDB}")
+ STRING(REGEX REPLACE "[\n\r]VERSION := " "" RE_VERSION "${RE_VERSION}")
+ STRING(REGEX REPLACE "\\$\\(VERSION\\)" ${RE_VERSION} RE_CFLAGS "${RE_CFLAGS}")
+
+ # get ARCH
+ STRING(REGEX MATCH "[\n\r]ARCH := ([^\n\r]+)" RE_ARCH "${RE_MAKEDB}")
+ STRING(REGEX REPLACE "[\n\r]ARCH := " "" RE_ARCH "${RE_ARCH}")
+ STRING(REGEX REPLACE "\\$\\(ARCH\\)" ${RE_ARCH} RE_CFLAGS "${RE_CFLAGS}")
+
+ # escaping '\': makefiles do need it, but it breaks xcode - not sure who's "right"
+ if (CMAKE_GENERATOR MATCHES Xcode)
+ STRING(REGEX REPLACE "\\\\" "" RE_CFLAGS "${RE_CFLAGS}" )
+ endif()
+
+ if (APPLE)
+ LIST(APPEND RE_SRCS "${RE_SRC_PREFIX}/lock/rwlock.c")
+
+ # get MacOSX version
+ execute_process(COMMAND /usr/bin/sw_vers -productVersion
+ OUTPUT_VARIABLE MACOSX_VERSION
+ ERROR_VARIABLE MACOSX_VERSION_errors
+ RESULT_VARIABLE MACOSX_VERSION_result
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+ # build universal binaries
+ set(CMAKE_OSX_ARCHITECTURES "x86_64;i386")
+
+ if (${MACOSX_VERSION} VERSION_LESS 10.9)
+ foreach(FLAGS CMAKE_C_FLAGS CMAKE_CXX_FLAGS CMAKE_EXE_LINKER_FLAGS CMAKE_SHARED_LINKER_FLAGS CMAKE_MODULE_LINKER_FLAGS)
+ set(${FLAGS} "${${FLAGS}} -mmacosx-version-min=10.6 -stdlib=libstdc++")
+ endforeach()
+ else()
+ foreach(FLAGS CMAKE_C_FLAGS CMAKE_CXX_FLAGS CMAKE_EXE_LINKER_FLAGS CMAKE_SHARED_LINKER_FLAGS CMAKE_MODULE_LINKER_FLAGS)
+ set(${FLAGS} "${${FLAGS}} -mmacosx-version-min=10.7 -stdlib=libc++")
+ endforeach()
+ endif()
+ endif()
+
+ message("libre sources: ${RE_SRCS}")
+ message("libre cflags: ${RE_CFLAGS}")
+ message("libre extra_cflags: ${RE_EXTRA_CFLAGS}")
+ message("libre OS: ${RE_OS}")
+ message("libre arch: ${RE_ARCH}")
+ message("libre version: ${RE_VERSION}")
+
+elseif(WIN32)
+ # hard-coded on Win32
+ if(MSVC)
+ SET(RE_CFLAGS
+ "-DWIN32 -D_CONSOLE -D_CRT_SECURE_NO_DEPRECATE -DHAVE_SELECT -DHAVE_IO_H"
+ )
+ elseif(MINGW)
+ SET(RE_CFLAGS
+ "-DHAVE_STDBOOL_H -DHAVE_INET6 -DHAVE_SELECT -DHAVE_IO_H"
+ )
+ add_definitions(-Wall -D_WIN32_WINNT=0x0501)
+ endif()
+ # quotes get eaten in generator
+ add_definitions(-DOS=\"win32\" -DWIN32 -DARCH=\"i386\" -DVERSION=\"0.3.0\")
+
+ # on windows we cannot rely on make and have to do this by hand
+ file(GLOB_RECURSE RE_SRCS RELATIVE ${PROJECT_SOURCE_DIR} ${RE_ROOT}/src/*.c)
+
+ # remove files to compile depending on the compiler flags
+ if (RE_CFLAGS MATCHES USE_ZLIB)
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/crc32/crc32.c")
+ endif()
+
+ if (NOT RE_CFLAGS MATCHES HAVE_PTHREAD_RWLOCK)
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/lock/rwlock.c")
+ endif()
+
+ if (NOT RE_CFLAGS MATCHES HAVE_GETIFADDRS)
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/net/ifaddrs.c")
+ endif()
+
+ if (NOT RE_CFLAGS MATCHES HAVE_EPOLL)
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/main/epoll.c")
+ endif()
+
+ if (NOT RE_CFLAGS MATCHES USE_OPENSSL)
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/tls/openssl/tls.c")
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/tls/openssl/tls_tcp.c")
+ endif()
+
+ if (NOT RE_CFLAGS MATCHES HAVE_PTHREAD)
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/lock/lock.c")
+ endif()
+
+ # remove files not to be comiled for win32 in any case
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/httpauth/basic.c")
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/mod/dl.c")
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/net/posix/pif.c")
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/net/linux/rt.c")
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/dns/res.c")
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/dns/darwin/srv.c")
+ LIST(REMOVE_ITEM RE_SRCS "${RE_SRC_PREFIX}/net/bsd/brt.c")
+
+endif()
+
+include_directories(${RE_ROOT}/include)
+
+add_library(re ${RE_SRCS} ${HEADER_FILES})
+
+SET_TARGET_PROPERTIES(re PROPERTIES
+ COMPILE_FLAGS ${RE_CFLAGS}
+)
diff --git a/mk/Doxyfile b/mk/Doxyfile
new file mode 100644
index 0000000..1361b52
--- /dev/null
+++ b/mk/Doxyfile
@@ -0,0 +1,239 @@
+# Doxyfile 1.4.7
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+PROJECT_NAME = libre
+PROJECT_NUMBER = 0.1.0
+OUTPUT_DIRECTORY = ../re-dox
+CREATE_SUBDIRS = NO
+OUTPUT_LANGUAGE = English
+#USE_WINDOWS_ENCODING = NO
+BRIEF_MEMBER_DESC = YES
+REPEAT_BRIEF = YES
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name widget" \
+ "The $name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+ALWAYS_DETAILED_SEC = NO
+INLINE_INHERITED_MEMB = NO
+FULL_PATH_NAMES = NO
+STRIP_FROM_PATH =
+STRIP_FROM_INC_PATH =
+SHORT_NAMES = NO
+JAVADOC_AUTOBRIEF = YES
+MULTILINE_CPP_IS_BRIEF = NO
+#DETAILS_AT_TOP = NO
+INHERIT_DOCS = YES
+SEPARATE_MEMBER_PAGES = NO
+TAB_SIZE = 8
+ALIASES =
+OPTIMIZE_OUTPUT_FOR_C = YES
+OPTIMIZE_OUTPUT_JAVA = NO
+#BUILTIN_STL_SUPPORT = NO
+DISTRIBUTE_GROUP_DOC = NO
+SUBGROUPING = YES
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+EXTRACT_ALL = NO
+EXTRACT_PRIVATE = NO
+EXTRACT_STATIC = NO
+EXTRACT_LOCAL_CLASSES = YES
+EXTRACT_LOCAL_METHODS = NO
+HIDE_UNDOC_MEMBERS = YES
+HIDE_UNDOC_CLASSES = YES
+HIDE_FRIEND_COMPOUNDS = NO
+HIDE_IN_BODY_DOCS = NO
+INTERNAL_DOCS = NO
+CASE_SENSE_NAMES = YES
+HIDE_SCOPE_NAMES = NO
+SHOW_INCLUDE_FILES = YES
+INLINE_INFO = YES
+SORT_MEMBER_DOCS = YES
+SORT_BRIEF_DOCS = NO
+SORT_BY_SCOPE_NAME = NO
+GENERATE_TODOLIST = YES
+GENERATE_TESTLIST = YES
+GENERATE_BUGLIST = YES
+GENERATE_DEPRECATEDLIST= YES
+ENABLED_SECTIONS =
+MAX_INITIALIZER_LINES = 30
+SHOW_USED_FILES = YES
+#SHOW_DIRECTORIES = NO
+FILE_VERSION_FILTER =
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+QUIET = YES
+WARNINGS = YES
+WARN_IF_UNDOCUMENTED = YES
+WARN_IF_DOC_ERROR = YES
+WARN_NO_PARAMDOC = YES
+WARN_FORMAT = "$file:$line: $text"
+WARN_LOGFILE =
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+INPUT = include src docs
+FILE_PATTERNS = *.c \
+ *.h \
+ *.dox
+RECURSIVE = YES
+EXCLUDE = test.c \
+ include/re_bitv.h \
+ src/md5/md5.h src/md5/md5.c
+
+EXCLUDE_SYMLINKS = NO
+EXCLUDE_PATTERNS = */.svn/*
+EXAMPLE_PATH = .
+EXAMPLE_PATTERNS = *
+EXAMPLE_RECURSIVE = NO
+IMAGE_PATH =
+INPUT_FILTER =
+FILTER_PATTERNS =
+FILTER_SOURCE_FILES = NO
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+SOURCE_BROWSER = YES
+INLINE_SOURCES = NO
+STRIP_CODE_COMMENTS = YES
+REFERENCED_BY_RELATION = YES
+REFERENCES_RELATION = YES
+#REFERENCES_LINK_SOURCE = YES
+#USE_HTAGS = NO
+VERBATIM_HEADERS = YES
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+ALPHABETICAL_INDEX = YES
+COLS_IN_ALPHA_INDEX = 5
+IGNORE_PREFIX =
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+GENERATE_HTML = YES
+HTML_OUTPUT = html
+HTML_FILE_EXTENSION = .html
+HTML_HEADER =
+HTML_FOOTER =
+HTML_STYLESHEET =
+#HTML_ALIGN_MEMBERS = YES
+GENERATE_HTMLHELP = NO
+CHM_FILE =
+HHC_LOCATION =
+GENERATE_CHI = NO
+BINARY_TOC = NO
+TOC_EXPAND = NO
+DISABLE_INDEX = NO
+ENUM_VALUES_PER_LINE = 4
+GENERATE_TREEVIEW = NO
+TREEVIEW_WIDTH = 250
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+GENERATE_LATEX = NO
+LATEX_OUTPUT = latex
+LATEX_CMD_NAME = latex
+MAKEINDEX_CMD_NAME = makeindex
+COMPACT_LATEX = NO
+PAPER_TYPE = a4wide
+EXTRA_PACKAGES =
+LATEX_HEADER =
+PDF_HYPERLINKS = NO
+USE_PDFLATEX = NO
+LATEX_BATCHMODE = NO
+LATEX_HIDE_INDICES = NO
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+GENERATE_RTF = NO
+RTF_OUTPUT = rtf
+COMPACT_RTF = NO
+RTF_HYPERLINKS = NO
+RTF_STYLESHEET_FILE =
+RTF_EXTENSIONS_FILE =
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+GENERATE_MAN = NO
+MAN_OUTPUT = man
+MAN_EXTENSION = .3
+MAN_LINKS = NO
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+GENERATE_XML = NO
+XML_OUTPUT = xml
+XML_PROGRAMLISTING = YES
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+GENERATE_AUTOGEN_DEF = NO
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+GENERATE_PERLMOD = NO
+PERLMOD_LATEX = NO
+PERLMOD_PRETTY = YES
+PERLMOD_MAKEVAR_PREFIX =
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = YES
+EXPAND_ONLY_PREDEF = YES
+SEARCH_INCLUDES = YES
+INCLUDE_PATH = include
+INCLUDE_FILE_PATTERNS =
+PREDEFINED = HAVE_INTTYPES_H HAVE_INET6 HAVE_STDBOOL_H
+EXPAND_AS_DEFINED =
+SKIP_FUNCTION_MACROS = YES
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+TAGFILES =
+GENERATE_TAGFILE =
+ALLEXTERNALS = NO
+EXTERNAL_GROUPS = YES
+PERL_PATH = /usr/bin/perl
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+CLASS_DIAGRAMS = YES
+HIDE_UNDOC_RELATIONS = YES
+HAVE_DOT = YES
+CLASS_GRAPH = YES
+COLLABORATION_GRAPH = YES
+GROUP_GRAPHS = YES
+UML_LOOK = NO
+TEMPLATE_RELATIONS = NO
+INCLUDE_GRAPH = YES
+INCLUDED_BY_GRAPH = YES
+#CALL_GRAPH = YES todo: disabled to run faster
+#CALLER_GRAPH = YES
+GRAPHICAL_HIERARCHY = YES
+DIRECTORY_GRAPH = YES
+DOT_IMAGE_FORMAT = png
+DOT_PATH =
+DOTFILE_DIRS =
+DOT_GRAPH_MAX_NODES = 256
+#MAX_DOT_GRAPH_WIDTH = 1024
+#MAX_DOT_GRAPH_HEIGHT = 1024
+#MAX_DOT_GRAPH_DEPTH = 1000
+DOT_TRANSPARENT = NO
+DOT_MULTI_TARGETS = NO
+GENERATE_LEGEND = YES
+DOT_CLEANUP = YES
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+SEARCHENGINE = NO
diff --git a/mk/exclude b/mk/exclude
new file mode 100644
index 0000000..c1f1f79
--- /dev/null
+++ b/mk/exclude
@@ -0,0 +1 @@
+rpm/
diff --git a/mk/re.mk b/mk/re.mk
new file mode 100644
index 0000000..3347945
--- /dev/null
+++ b/mk/re.mk
@@ -0,0 +1,829 @@
+#
+# re.mk - common make rules
+#
+# Copyright (C) 2010 Creytiv.com
+#
+# Imported variables:
+#
+# ARCH Target architecture
+# CC Compiler
+# CROSS_COMPILE Cross-compiler prefix (optional)
+# EXTRA_CFLAGS Extra compiler flags appended to CFLAGS
+# EXTRA_LFLAGS Extra linker flags appended to LFLAGS
+# GCOV If non-empty, enable GNU Coverage testing
+# GPROF If non-empty, enable GNU Profiling
+# OPT_SIZE If non-empty, optimize for size
+# OPT_SPEED If non-empty, optimize for speed
+# PROJECT Project name
+# RELEASE Release build
+# SYSROOT System root of library and include files
+# SYSROOT_ALT Alternative system root of library and include files
+# USE_OPENSSL If non-empty, link to libssl library
+# USE_ZLIB If non-empty, link to libz library
+# VERSION Version number
+#
+# Exported variables:
+#
+# APP_LFLAGS Linker flags for applications using modules
+# BIN_SUFFIX Suffix for binary executables
+# CC Compiler
+# CCACHE Compiler ccache tool
+# CFLAGS Compiler flags
+# DFLAGS Dependency generator flags
+# LFLAGS Common linker flags
+# LIBS Libraries to link against
+# LIB_SUFFIX Suffix for shared libraries
+# MOD_LFLAGS Linker flags for dynamic modules
+# MOD_SUFFIX Suffix for dynamic modules
+# SH_LFLAGS Linker flags for shared libraries
+# USE_TLS Defined if TLS is available
+# USE_DTLS Defined if DTLS is available
+#
+
+
+ifneq ($(RELEASE),)
+CFLAGS += -DRELEASE
+OPT_SPEED=1
+endif
+
+
+# Default system root
+ifeq ($(SYSROOT),)
+SYSROOT := /usr
+endif
+
+# Alternative Systemroot
+ifeq ($(SYSROOT_ALT),)
+SYSROOT_ALT := $(shell [ -d /sw/include ] && echo "/sw")
+endif
+ifeq ($(SYSROOT_ALT),)
+SYSROOT_ALT := $(shell [ -d /opt/local/include ] && echo "/opt/local")
+endif
+
+ifneq ($(SYSROOT_ALT),)
+CFLAGS += -I$(SYSROOT_ALT)/include
+LFLAGS += -L$(SYSROOT_ALT)/lib
+endif
+
+
+##############################################################################
+#
+# Compiler section
+#
+# find compiler name & version
+
+ifeq ($(CC),)
+ CC := gcc
+endif
+ifeq ($(CC),cc)
+ CC := gcc
+endif
+LD := $(CC)
+CC_LONGVER := $(shell if $(CC) -v 2>/dev/null; then \
+ $(CC) -v 2>&1 ;\
+ else \
+ $(CC) -V 2>&1 ; \
+ fi )
+
+# find-out the compiler's name
+
+ifneq (,$(findstring gcc, $(CC_LONGVER)))
+ CC_NAME := gcc
+ CC_VER := $(word 1,$(CC)) $(shell $(CC) - --version|head -n 1|\
+ cut -d" " -f 3|\
+ sed -e 's/^.*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/'\
+ -e 's/^[^0-9].*\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/')
+ # sun sed is a little brain damaged => this complicated expression
+ MKDEP := $(CC) -MM
+ #transform gcc version into 2.9x, 3.x or 4.x
+ CC_SHORTVER := $(shell echo "$(CC_VER)" | cut -d" " -f 2| \
+ sed -e 's/[^0-9]*-\(.*\)/\1/'| \
+ sed -e 's/2\.9.*/2.9x/' -e 's/3\.[0-3]\..*/3.0/' -e \
+ 's/3\.[0-3]/3.0/' -e 's/3\.[4-9]\..*/3.4/' -e\
+ 's/3\.[4-9]/3.4/' -e 's/4\.[0-9]\..*/4.x/' -e\
+ 's/4\.[0-9]/4.x/' )
+endif
+
+ifeq ($(CC_NAME),)
+ifneq (,$(findstring clang, $(CC_LONGVER)))
+ CC_NAME := clang
+ CC_SHORTVER := $(shell echo "$(CC_LONGVER)"|head -n 1| \
+ sed -e 's/.*version \([0-9]\.[0-9]\).*/\1/g' )
+ CC_VER := $(CC) $(CC_SHORTVER)
+ MKDEP := $(CC) -MM
+endif
+endif
+
+ifeq ($(CC_NAME),)
+ifneq (, $(findstring Sun, $(CC_LONGVER)))
+ CC_NAME := suncc
+ CC_SHORTVER := $(shell echo "$(CC_LONGVER)"|head -n 1| \
+ sed -e 's/.*\([0-9]\.[0-9]\).*/\1/g' )
+ CC_VER := $(CC) $(CC_SHORTVER)
+ MKDEP := $(CC) -xM1
+endif
+endif
+
+ifeq ($(CC_NAME),)
+ifneq (, $(findstring Intel(R) C++ Compiler, $(CC_LONGVER)))
+ # very nice: gcc compatible
+ CC_NAME := icc
+ CC_FULLVER := $(shell echo "$(CC_LONGVER)"|head -n 1| \
+ sed -e 's/.*Version \([0-9]\.[0-9]\.[0-9]*\).*/\1/g')
+ CC_SHORTVER := $(shell echo "$(CC_FULLVER)" | cut -d. -f1,2 )
+ CC_VER := $(CC) $(CC_FULLVER)
+ MKDEP := $(CC) -MM
+endif
+endif
+
+
+ifeq (,$(CC_NAME))
+#not found
+ CC_NAME := $(CC)
+ CC_SHORTVER := unknown
+ CC_VER := unknown
+ MKDEP := gcc -MM
+$(warning Unknown compiler $(CC)\; supported compilers: \
+ gcc, clang, sun cc, intel icc )
+endif
+
+
+# Compiler warning flags
+CFLAGS += -Wall
+CFLAGS += -Wmissing-declarations
+CFLAGS += -Wmissing-prototypes
+CFLAGS += -Wstrict-prototypes
+CFLAGS += -Wbad-function-cast
+CFLAGS += -Wsign-compare
+CFLAGS += -Wnested-externs
+CFLAGS += -Wshadow
+CFLAGS += -Waggregate-return
+CFLAGS += -Wcast-align
+
+
+ifeq ($(CC_SHORTVER),4.x)
+CFLAGS += -Wextra
+CFLAGS += -Wold-style-definition
+CFLAGS += -Wdeclaration-after-statement
+endif
+
+CFLAGS += -g
+ifneq ($(OPT_SPEED),)
+CFLAGS += -O3 # Optimize for speed - takes longer to compile!
+OPTIMIZE := 1
+endif
+ifneq ($(OPT_SIZE),)
+CFLAGS += -Os # Optimize for size - takes longer to compile!
+OPTIMIZE := 1
+endif
+
+ifneq ($(OPTIMIZE),)
+CFLAGS += -Wuninitialized
+ifneq ($(CC_SHORTVER), 2.9x)
+CFLAGS += -Wno-strict-aliasing
+endif
+endif
+
+# Compiler dependency flags
+ifeq ($(CC_SHORTVER), 2.9x)
+ DFLAGS = -MD
+else
+ DFLAGS = -MD -MF $(@:.o=.d) -MT $@
+endif
+
+
+##############################################################################
+#
+# OS section
+#
+
+MACHINE := $(shell $(CC) -dumpmachine)
+
+ifeq ($(CROSS_COMPILE),)
+OS := $(shell uname -s | sed -e s/SunOS/solaris/ | tr "[A-Z]" "[a-z]")
+endif
+
+
+ifneq ($(strip $(filter i386-mingw32 i486-mingw32 i586-mingw32msvc \
+ i686-w64-mingw32 x86_64-w64-mingw32 mingw32, \
+ $(MACHINE))),)
+ OS := win32
+ifeq ($(MACHINE), mingw32)
+ CROSS_COMPILE :=
+endif
+endif
+
+
+# default
+LIB_SUFFIX := .so
+MOD_SUFFIX := .so
+BIN_SUFFIX :=
+
+ifeq ($(OS),solaris)
+ CFLAGS += -fPIC -DSOLARIS
+ LIBS += -ldl -lresolv -lsocket -lnsl
+ LFLAGS += -fPIC
+ SH_LFLAGS += -G
+ MOD_LFLAGS +=
+ APP_LFLAGS +=
+ AR := ar
+ AFLAGS := cru
+endif
+ifeq ($(OS),linux)
+ CFLAGS += -fPIC -DLINUX
+ LIBS += -ldl
+ LFLAGS += -fPIC
+ SH_LFLAGS += -shared
+ MOD_LFLAGS +=
+ APP_LFLAGS += -rdynamic
+ AR := ar
+ AFLAGS := crD
+endif
+ifeq ($(OS),gnu)
+ CFLAGS += -fPIC -DGNU
+ LIBS += -ldl
+ LFLAGS += -fPIC
+ SH_LFLAGS += -shared
+ MOD_LFLAGS +=
+ APP_LFLAGS += -rdynamic
+ AR := ar
+ AFLAGS := cru
+endif
+ifeq ($(OS),darwin)
+ CFLAGS += -fPIC -dynamic -DDARWIN
+ifneq (,$(findstring Apple, $(CC_LONGVER)))
+ CFLAGS += -Wshorten-64-to-32
+endif
+ DFLAGS := -MD
+ LIBS += -lresolv
+ LFLAGS += -fPIC
+ SH_LFLAGS += -dynamiclib
+ifeq ($(CC_NAME),gcc)
+ SH_LFLAGS += -dylib
+endif
+ifneq ($(VERSION),)
+ SH_LFLAGS += -current_version $(VERSION)
+ SH_LFLAGS += -compatibility_version $(VERSION)
+endif
+ MOD_LFLAGS += -undefined dynamic_lookup
+ APP_LFLAGS +=
+ AR := ar
+ AFLAGS := cru
+ LIB_SUFFIX := .dylib
+ HAVE_KQUEUE := 1
+endif
+ifeq ($(OS),netbsd)
+ CFLAGS += -fPIC -DNETBSD
+ LFLAGS += -fPIC
+ SH_LFLAGS += -shared
+ MOD_LFLAGS +=
+ APP_LFLAGS += -rdynamic
+ AR := ar
+ AFLAGS := cru
+ HAVE_KQUEUE := 1
+endif
+ifeq ($(OS),freebsd)
+ CFLAGS += -fPIC -DFREEBSD
+ LFLAGS += -fPIC
+ SH_LFLAGS += -shared
+ MOD_LFLAGS +=
+ APP_LFLAGS += -rdynamic
+ AR := ar
+ AFLAGS := cru
+ HAVE_KQUEUE := 1
+endif
+ifeq ($(OS),gnu/kfreebsd)
+ CFLAGS += -fPIC -DKFREEBSD -D_GNU_SOURCE
+ LFLAGS += -fPIC
+ SH_LFLAGS += -shared
+ MOD_LFLAGS +=
+ APP_LFLAGS += -rdynamic
+ AR := ar
+ AFLAGS := cru
+ HAVE_KQUEUE := 1
+endif
+ifeq ($(OS),dragonfly)
+ CFLAGS += -fPIC -DDRAGONFLY
+ LFLAGS += -fPIC
+ SH_LFLAGS += -shared
+ MOD_LFLAGS +=
+ APP_LFLAGS += -rdynamic
+ AR := ar
+ AFLAGS := cru
+ HAVE_KQUEUE := 1
+endif
+ifeq ($(OS),openbsd)
+ CFLAGS += -fPIC -DOPENBSD
+ LFLAGS += -fPIC
+ SH_LFLAGS += -shared
+ MOD_LFLAGS +=
+ APP_LFLAGS += -rdynamic
+ AR := ar
+ AFLAGS := cru
+ HAVE_KQUEUE := 1
+ HAVE_ARC4RANDOM := 1
+endif
+ifeq ($(OS),win32)
+ CFLAGS += -DWIN32 -D_WIN32_WINNT=0x0501 -D__ssize_t_defined
+ LIBS += -lwsock32 -lws2_32 -liphlpapi
+ LFLAGS +=
+ SH_LFLAGS += -shared
+ MOD_LFLAGS +=
+ APP_LFLAGS += -Wl,--export-all-symbols
+ AR := ar
+ AFLAGS := cru
+ CROSS_COMPILE ?= $(MACHINE)-
+ RANLIB := $(CROSS_COMPILE)ranlib
+ LIB_SUFFIX := .dll
+ MOD_SUFFIX := .dll
+ BIN_SUFFIX := .exe
+ SYSROOT := /usr/$(MACHINE)/
+endif
+
+CFLAGS += -DOS=\"$(OS)\"
+
+ifeq ($(CC_SHORTVER),2.9x)
+CFLAGS += -Wno-long-long
+else
+CFLAGS += -std=c99
+PEDANTIC := 1
+endif # CC_SHORTVER
+
+ifneq ($(PEDANTIC),)
+CFLAGS += -pedantic
+endif
+
+
+ifeq ($(OS),)
+$(warning Could not detect OS)
+endif
+
+
+##############################################################################
+#
+# Architecture section
+#
+
+
+ifeq ($(ARCH),)
+ifeq ($(CC_NAME),$(filter $(CC_NAME),gcc clang))
+PREDEF := $(shell $(CC) -dM -E -x c $(EXTRA_CFLAGS) $(CFLAGS) /dev/null)
+
+ifneq ($(strip $(filter i386 __i386__ __i386 _M_IX86 __X86__ _X86_, \
+ $(PREDEF))),)
+ARCH := i386
+endif
+
+ifneq ($(strip $(filter __i486__,$(PREDEF))),)
+ARCH := i486
+endif
+
+ifneq ($(strip $(filter __i586__,$(PREDEF))),)
+ARCH := i586
+endif
+
+ifneq ($(strip $(filter __i686__ ,$(PREDEF))),)
+ARCH := i686
+endif
+
+ifneq ($(strip $(filter __amd64__ __amd64 __x86_64__ __x86_64, \
+ $(PREDEF))),)
+ARCH := x86_64
+endif
+
+ifneq ($(strip $(filter __arm__ __thumb__,$(PREDEF))),)
+
+ifneq ($(strip $(filter __ARM_ARCH_6__,$(PREDEF))),)
+ARCH := arm6
+else
+ARCH := arm
+endif
+
+endif
+
+ifneq ($(strip $(filter __arm64__ ,$(PREDEF))),)
+ARCH := arm64
+endif
+
+ifneq ($(strip $(filter __mips__ __mips, $(PREDEF))),)
+ARCH := mips
+endif
+
+ifneq ($(strip $(filter __powerpc __powerpc__ __POWERPC__ __ppc__ \
+ _ARCH_PPC, $(PREDEF))),)
+ARCH := ppc
+endif
+
+ifneq ($(strip $(filter __ppc64__ _ARCH_PPC64 , $(PREDEF))),)
+ARCH := ppc64
+endif
+
+ifneq ($(strip $(filter __sparc__ __sparc __sparcv8 , $(PREDEF))),)
+
+ifneq ($(strip $(filter __sparcv9 __sparc_v9__ , $(PREDEF))),)
+ARCH := sparc64
+else
+ARCH := sparc
+endif
+
+endif
+
+endif
+endif
+
+
+ifeq ($(ARCH),)
+$(warning Could not detect ARCH)
+endif
+
+
+CFLAGS += -DARCH=\"$(ARCH)\"
+
+ifeq ($(ARCH),mipsel)
+CFLAGS += -march=mips32
+endif
+
+
+##############################################################################
+#
+# External libraries section
+#
+
+USE_OPENSSL := $(shell [ -f $(SYSROOT)/include/openssl/ssl.h ] || \
+ [ -f $(SYSROOT)/local/include/openssl/ssl.h ] || \
+ [ -f $(SYSROOT_ALT)/include/openssl/ssl.h ] && echo "yes")
+
+ifneq ($(USE_OPENSSL),)
+CFLAGS += -DUSE_OPENSSL -DUSE_TLS
+LIBS += -lssl -lcrypto
+USE_TLS := yes
+
+USE_OPENSSL_DTLS := $(shell [ -f $(SYSROOT)/include/openssl/dtls1.h ] || \
+ [ -f $(SYSROOT)/local/include/openssl/dtls1.h ] || \
+ [ -f $(SYSROOT_ALT)/include/openssl/dtls1.h ] && echo "yes")
+
+USE_OPENSSL_SRTP := $(shell [ -f $(SYSROOT)/include/openssl/srtp.h ] || \
+ [ -f $(SYSROOT)/local/include/openssl/srtp.h ] || \
+ [ -f $(SYSROOT_ALT)/include/openssl/srtp.h ] && echo "yes")
+
+ifneq ($(USE_OPENSSL_DTLS),)
+CFLAGS += -DUSE_OPENSSL_DTLS -DUSE_DTLS
+USE_DTLS := yes
+endif
+
+ifneq ($(USE_OPENSSL_SRTP),)
+CFLAGS += -DUSE_OPENSSL_SRTP -DUSE_DTLS_SRTP
+USE_DTLS_SRTP := yes
+endif
+
+USE_OPENSSL_AES := yes
+USE_OPENSSL_HMAC := yes
+
+endif
+
+
+USE_ZLIB := $(shell [ -f $(SYSROOT)/include/zlib.h ] || \
+ [ -f $(SYSROOT)/local/include/zlib.h ] || \
+ [ -f $(SYSROOT_ALT)/include/zlib.h ] && echo "yes")
+
+ifneq ($(USE_ZLIB),)
+CFLAGS += -DUSE_ZLIB
+LIBS += -lz
+endif
+
+
+ifneq ($(OS),win32)
+
+HAVE_PTHREAD := $(shell [ -f $(SYSROOT)/include/pthread.h ] && echo "1")
+ifneq ($(HAVE_PTHREAD),)
+HAVE_PTHREAD_RWLOCK := 1
+CFLAGS += -DHAVE_PTHREAD
+HAVE_LIBPTHREAD := 1
+ifneq ($(HAVE_LIBPTHREAD),)
+LIBS += -lpthread
+endif
+endif
+
+ifneq ($(ARCH),mipsel)
+HAVE_GETIFADDRS := $(shell [ -f $(SYSROOT)/include/ifaddrs.h ] && echo "1")
+ifneq ($(HAVE_GETIFADDRS),)
+CFLAGS += -DHAVE_GETIFADDRS
+endif
+endif
+
+HAVE_STRERROR_R := 1
+ifneq ($(HAVE_STRERROR_R),)
+CFLAGS += -DHAVE_STRERROR_R
+endif
+
+endif
+
+HAVE_GETOPT := $(shell [ -f $(SYSROOT)/include/getopt.h ] && echo "1")
+ifneq ($(HAVE_GETOPT),)
+CFLAGS += -DHAVE_GETOPT
+endif
+HAVE_INTTYPES_H := $(shell [ -f $(SYSROOT)/include/inttypes.h ] && echo "1")
+ifneq ($(HAVE_INTTYPES_H),)
+CFLAGS += -DHAVE_INTTYPES_H
+endif
+HAVE_NET_ROUTE_H := $(shell [ -f $(SYSROOT)/include/net/route.h ] && echo "1")
+ifneq ($(HAVE_NET_ROUTE_H),)
+CFLAGS += -DHAVE_NET_ROUTE_H
+endif
+HAVE_SYS_SYSCTL_H := \
+ $(shell [ -f $(SYSROOT)/include/sys/sysctl.h ] || \
+ [ -f $(SYSROOT)/include/$(MACHINE)/sys/sysctl.h ] \
+ && echo "1")
+ifneq ($(HAVE_SYS_SYSCTL_H),)
+CFLAGS += -DHAVE_SYS_SYSCTL_H
+endif
+
+CFLAGS += -DHAVE_STDBOOL_H
+
+HAVE_INET6 := 1
+ifneq ($(HAVE_INET6),)
+CFLAGS += -DHAVE_INET6
+endif
+
+ifeq ($(OS),win32)
+CFLAGS += -DHAVE_SELECT
+CFLAGS += -DHAVE_IO_H
+else
+HAVE_SYSLOG := $(shell [ -f $(SYSROOT)/include/syslog.h ] && echo "1")
+HAVE_DLFCN_H := $(shell [ -f $(SYSROOT)/include/dlfcn.h ] && echo "1")
+ifneq ($(OS),darwin)
+HAVE_EPOLL := $(shell [ -f $(SYSROOT)/include/sys/epoll.h ] || \
+ [ -f $(SYSROOT)/include/$(MACHINE)/sys/epoll.h ] \
+ && echo "1")
+endif
+
+HAVE_RESOLV := $(shell [ -f $(SYSROOT)/include/resolv.h ] && echo "1")
+
+ifneq ($(HAVE_RESOLV),)
+CFLAGS += -DHAVE_RESOLV
+endif
+ifneq ($(HAVE_SYSLOG),)
+CFLAGS += -DHAVE_SYSLOG
+endif
+
+HAVE_INET_NTOP := 1
+
+CFLAGS += -DHAVE_FORK
+
+ifneq ($(HAVE_INET_NTOP),)
+CFLAGS += -DHAVE_INET_NTOP
+endif
+CFLAGS += -DHAVE_PWD_H
+ifneq ($(OS),darwin)
+CFLAGS += -DHAVE_POLL # Darwin: poll() does not support devices
+HAVE_INET_PTON := 1
+endif
+ifneq ($(HAVE_INET_PTON),)
+CFLAGS += -DHAVE_INET_PTON
+endif
+CFLAGS += -DHAVE_SELECT -DHAVE_SELECT_H
+CFLAGS += -DHAVE_SETRLIMIT
+CFLAGS += -DHAVE_SIGNAL
+CFLAGS += -DHAVE_SYS_TIME_H
+ifneq ($(HAVE_EPOLL),)
+CFLAGS += -DHAVE_EPOLL
+endif
+ifneq ($(HAVE_KQUEUE),)
+CFLAGS += -DHAVE_KQUEUE
+endif
+CFLAGS += -DHAVE_UNAME
+CFLAGS += -DHAVE_UNISTD_H
+CFLAGS += -DHAVE_STRINGS_H
+endif
+
+ifneq ($(HAVE_ARC4RANDOM),)
+CFLAGS += -DHAVE_ARC4RANDOM
+endif
+
+
+##############################################################################
+#
+# Misc tools section
+#
+CCACHE := $(shell [ -e /usr/bin/ccache ] 2>/dev/null \
+ || [ -e /opt/local/bin/ccache ] \
+ && echo "ccache")
+CFLAGS += -DVERSION=\"$(VERSION)\"
+CFLAGS += \
+ -DVER_MAJOR=$(VER_MAJOR) \
+ -DVER_MINOR=$(VER_MINOR) \
+ -DVER_PATCH=$(VER_PATCH)
+
+
+# Enable gcov Coverage testing
+#
+# - generated during build: .gcno files
+# - generated during exec: .gcda files
+#
+ifneq ($(GCOV),)
+CFLAGS += -fprofile-arcs -ftest-coverage
+LFLAGS += -fprofile-arcs -ftest-coverage
+# Disable ccache
+CCACHE :=
+endif
+
+# gprof - GNU Profiling
+#
+# - generated during exec: gmon.out
+#
+ifneq ($(GPROF),)
+CFLAGS += -pg
+LFLAGS += -pg
+# Disable ccache
+CCACHE :=
+endif
+
+CC := $(CCACHE) $(CC)
+CFLAGS += $(EXTRA_CFLAGS)
+LFLAGS += $(EXTRA_LFLAGS)
+
+BUILD := build-$(ARCH)
+
+
+default: all
+
+.PHONY: distclean
+distclean:
+ @rm -rf build* *core*
+ @rm -f *stamp $(BIN)
+ @rm -f `find . -name "*.[oda]"` `find . -name "*.so"`
+ @rm -f `find . -name "*~"` `find . -name "\.\#*"`
+ @rm -f `find . -name "*.orig"` `find . -name "*.rej"`
+ @rm -f `find . -name "*.previous"` `find . -name "*.gcov"`
+ @rm -f `find . -name "*.exe"` `find . -name "*.dll"`
+ @rm -f `find . -name "*.dylib"`
+
+.PHONY: info
+info:
+ @echo "info - $(PROJECT) version $(VERSION)"
+ @echo " MODULES: $(MODULES)"
+# @echo " SRCS: $(SRCS)"
+ @echo " MACHINE: $(MACHINE)"
+ @echo " ARCH: $(ARCH)"
+ @echo " OS: $(OS)"
+ @echo " BUILD: $(BUILD)"
+ @echo " CCACHE: $(CCACHE)"
+ @echo " CC: $(CC_NAME) $(CC_SHORTVER)"
+ @echo " CFLAGS: $(CFLAGS)"
+ @echo " DFLAGS: $(DFLAGS)"
+ @echo " LFLAGS: $(LFLAGS)"
+ @echo " SH_LFLAGS: $(SH_LFLAGS)"
+ @echo " MOD_LFLAGS: $(MOD_LFLAGS)"
+ @echo " APP_LFLAGS: $(APP_LFLAGS)"
+ @echo " LIBS: $(LIBS)"
+ @echo " LIBRE_MK: $(LIBRE_MK)"
+ @echo " LIBRE_INC: $(LIBRE_INC)"
+ @echo " LIBRE_SO: $(LIBRE_SO)"
+ @echo " USE_OPENSSL: $(USE_OPENSSL)"
+ @echo " USE_OPENSSL_AES: $(USE_OPENSSL_AES)"
+ @echo " USE_OPENSSL_HMAC: $(USE_OPENSSL_HMAC)"
+ @echo " USE_TLS: $(USE_TLS)"
+ @echo " USE_DTLS: $(USE_DTLS)"
+ @echo " USE_DTLS_SRTP: $(USE_DTLS_SRTP)"
+ @echo " USE_ZLIB: $(USE_ZLIB)"
+ @echo " GCOV: $(GCOV)"
+ @echo " GPROF: $(GPROF)"
+ @echo " CROSS_COMPILE: $(CROSS_COMPILE)"
+ @echo " SYSROOT: $(SYSROOT)"
+ @echo " SYSROOT_ALT: $(SYSROOT_ALT)"
+ @echo " LIB_SUFFIX: $(LIB_SUFFIX)"
+ @echo " MOD_SUFFIX: $(MOD_SUFFIX)"
+ @echo " BIN_SUFFIX: $(BIN_SUFFIX)"
+
+
+##############################################################################
+#
+# Packaging section
+#
+TAR_SRC := $(PROJECT)-$(VERSION)
+
+release:
+ @rm -rf ../$(TAR_SRC)
+ @svn export . ../$(TAR_SRC)
+ @if [ -f ../$(TAR_SRC)/mk/exclude ]; then \
+ cat ../$(TAR_SRC)/mk/exclude \
+ | sed 's|^|../$(TAR_SRC)/|' | xargs -t rm -rf ; \
+ rm -f ../$(TAR_SRC)/mk/exclude ; \
+ fi
+ @cd .. && rm -f $(TAR_SRC).tar.gz \
+ && tar -zcf $(TAR_SRC).tar.gz $(TAR_SRC) \
+ && echo "created release tarball `pwd`/$(TAR_SRC).tar.gz"
+
+tar:
+ @rm -rf ../$(TAR_SRC)
+ @svn export . ../$(TAR_SRC)
+ @cd .. && rm -f $(TAR_SRC).tar.gz \
+ && tar -zcf $(TAR_SRC).tar.gz $(TAR_SRC) \
+ && echo "created source tarball `pwd`/$(TAR_SRC).tar.gz"
+
+
+git_release:
+ git archive --format=tar --prefix=$(PROJECT)-$(VERSION)/ v$(VERSION) \
+ | gzip > ../$(PROJECT)-$(VERSION).tar.gz
+
+
+git_snapshot:
+ git archive --format=tar --prefix=$(PROJECT)-$(VERSION)/ HEAD \
+ | gzip > ../$(PROJECT)-$(VERSION).tar.gz
+
+
+# Debian
+.PHONY: deb
+deb:
+ dpkg-buildpackage -rfakeroot
+
+.PHONY: debclean
+debclean:
+ @rm -rf build-stamp configure-stamp debian/files debian/$(PROJECT) \
+ debian/lib$(PROJECT) debian/lib$(PROJECT)-dev debian/tmp \
+ debian/.debhelper debian/*.debhelper debian/*.debhelper.log \
+ debian/*.substvars
+
+# RPM
+RPM := $(shell [ -d /usr/src/rpm ] 2>/dev/null && echo "rpm")
+ifeq ($(RPM),)
+RPM := $(shell [ -d /usr/src/redhat ] 2>/dev/null && echo "redhat")
+endif
+.PHONY: rpm
+rpm: tar
+ sudo cp ../$(PROJECT)-$(VERSION).tar.gz /usr/src/$(RPM)/SOURCES
+ sudo rpmbuild -ba rpm/$(PROJECT).spec
+
+
+##############################################################################
+#
+# Library and header files location section - in prioritised order
+#
+# - relative path
+# - local installation
+# - system installation
+#
+
+LIBRE_PATH := ../re
+
+# Include path
+LIBRE_INC := $(shell [ -f $(LIBRE_PATH)/include/re.h ] && \
+ echo "$(LIBRE_PATH)/include")
+ifeq ($(LIBRE_INC),)
+LIBRE_INC := $(shell [ -f /usr/local/include/re/re.h ] && \
+ echo "/usr/local/include/re")
+endif
+ifeq ($(LIBRE_INC),)
+LIBRE_INC := $(shell [ -f /usr/include/re/re.h ] && echo "/usr/include/re")
+endif
+
+# Library path
+LIBRE_SO := $(shell [ -f $(LIBRE_PATH)/libre$(LIB_SUFFIX) ] \
+ && echo "$(LIBRE_PATH)")
+ifeq ($(LIBRE_SO),)
+LIBRE_SO := $(shell [ -f /usr/local/lib/libre$(LIB_SUFFIX) ] \
+ && echo "/usr/local/lib")
+endif
+ifeq ($(LIBRE_SO),)
+LIBRE_SO := $(shell [ -f /usr/lib/libre$(LIB_SUFFIX) ] && echo "/usr/lib")
+endif
+ifeq ($(LIBRE_SO),)
+LIBRE_SO := $(shell [ -f /usr/lib64/libre$(LIB_SUFFIX) ] && echo "/usr/lib64")
+endif
+
+
+###############################################################################
+#
+# Clang section
+#
+
+CLANG_OPTIONS := -Iinclude -I$(LIBRE_INC) $(CFLAGS)
+CLANG_IGNORE :=
+CLANG_SRCS += $(filter-out $(CLANG_IGNORE), $(patsubst %,src/%,$(SRCS)))
+
+clang:
+ @clang --analyze $(CLANG_OPTIONS) $(CLANG_SRCS)
+ @rm -f *.plist
+
+
+###############################################################################
+#
+# Documentation section
+#
+DOX_DIR=../$(PROJECT)-dox
+DOX_TAR=$(PROJECT)-dox-$(VERSION)
+
+$(DOX_DIR):
+ @mkdir $@
+
+$(DOX_DIR)/Doxyfile: mk/Doxyfile Makefile
+ @cp $< $@
+ @perl -pi -e 's/PROJECT_NUMBER\s*=.*/PROJECT_NUMBER = $(VERSION)/' \
+ $(DOX_DIR)/Doxyfile
+
+.PHONY:
+dox: $(DOX_DIR) $(DOX_DIR)/Doxyfile
+ @doxygen $(DOX_DIR)/Doxyfile 2>&1 | grep -v DEBUG_ ; true
+ @cd .. && rm -f $(DOX_TAR).tar.gz && \
+ tar -zcf $(DOX_TAR).tar.gz $(PROJECT)-dox > /dev/null && \
+ echo "Doxygen docs in `pwd`/$(DOX_TAR).tar.gz"
diff --git a/mk/win32/re.vcxproj b/mk/win32/re.vcxproj
new file mode 100644
index 0000000..e70f904
--- /dev/null
+++ b/mk/win32/re.vcxproj
@@ -0,0 +1,332 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\include\re.h" />
+ <ClInclude Include="..\..\include\re_aes.h" />
+ <ClInclude Include="..\..\include\re_base64.h" />
+ <ClInclude Include="..\..\include\re_bfcp.h" />
+ <ClInclude Include="..\..\include\re_bitv.h" />
+ <ClInclude Include="..\..\include\re_conf.h" />
+ <ClInclude Include="..\..\include\re_crc32.h" />
+ <ClInclude Include="..\..\include\re_dbg.h" />
+ <ClInclude Include="..\..\include\re_dns.h" />
+ <ClInclude Include="..\..\include\re_fmt.h" />
+ <ClInclude Include="..\..\include\re_hash.h" />
+ <ClInclude Include="..\..\include\re_hmac.h" />
+ <ClInclude Include="..\..\include\re_http.h" />
+ <ClInclude Include="..\..\include\re_httpauth.h" />
+ <ClInclude Include="..\..\include\re_ice.h" />
+ <ClInclude Include="..\..\include\re_jbuf.h" />
+ <ClInclude Include="..\..\include\re_json.h" />
+ <ClInclude Include="..\..\include\re_list.h" />
+ <ClInclude Include="..\..\include\re_lock.h" />
+ <ClInclude Include="..\..\include\re_main.h" />
+ <ClInclude Include="..\..\include\re_mbuf.h" />
+ <ClInclude Include="..\..\include\re_md5.h" />
+ <ClInclude Include="..\..\include\re_mem.h" />
+ <ClInclude Include="..\..\include\re_mod.h" />
+ <ClInclude Include="..\..\include\re_mqueue.h" />
+ <ClInclude Include="..\..\include\re_msg.h" />
+ <ClInclude Include="..\..\include\re_natbd.h" />
+ <ClInclude Include="..\..\include\re_net.h" />
+ <ClInclude Include="..\..\include\re_odict.h" />
+ <ClInclude Include="..\..\include\re_rtp.h" />
+ <ClInclude Include="..\..\include\re_sa.h" />
+ <ClInclude Include="..\..\include\re_sdp.h" />
+ <ClInclude Include="..\..\include\re_sha.h" />
+ <ClInclude Include="..\..\include\re_sip.h" />
+ <ClInclude Include="..\..\include\re_sipevent.h" />
+ <ClInclude Include="..\..\include\re_sipreg.h" />
+ <ClInclude Include="..\..\include\re_sipsess.h" />
+ <ClInclude Include="..\..\include\re_srtp.h" />
+ <ClInclude Include="..\..\include\re_stun.h" />
+ <ClInclude Include="..\..\include\re_sys.h" />
+ <ClInclude Include="..\..\include\re_tcp.h" />
+ <ClInclude Include="..\..\include\re_telev.h" />
+ <ClInclude Include="..\..\include\re_tls.h" />
+ <ClInclude Include="..\..\include\re_tmr.h" />
+ <ClInclude Include="..\..\include\re_turn.h" />
+ <ClInclude Include="..\..\include\re_types.h" />
+ <ClInclude Include="..\..\include\re_udp.h" />
+ <ClInclude Include="..\..\include\re_uri.h" />
+ <ClInclude Include="..\..\include\re_websock.h" />
+ <ClInclude Include="..\..\src\bfcp\bfcp.h" />
+ <ClInclude Include="..\..\src\dns\dns.h" />
+ <ClInclude Include="..\..\src\ice\ice.h" />
+ <ClInclude Include="..\..\src\main\main.h" />
+ <ClInclude Include="..\..\src\md5\md5.h" />
+ <ClInclude Include="..\..\src\mod\mod_internal.h" />
+ <ClInclude Include="..\..\src\mqueue\mqueue.h" />
+ <ClInclude Include="..\..\src\rtp\rtcp.h" />
+ <ClInclude Include="..\..\src\sa\sa.h" />
+ <ClInclude Include="..\..\src\sdp\sdp.h" />
+ <ClInclude Include="..\..\src\sipevent\sipevent.h" />
+ <ClInclude Include="..\..\src\sipsess\sipsess.h" />
+ <ClInclude Include="..\..\src\sip\sip.h" />
+ <ClInclude Include="..\..\src\srtp\srtp.h" />
+ <ClInclude Include="..\..\src\stun\stun.h" />
+ <ClInclude Include="..\..\src\turn\turnc.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\src\aes\stub.c" />
+ <ClCompile Include="..\..\src\base64\b64.c" />
+ <ClCompile Include="..\..\src\bfcp\attr.c" />
+ <ClCompile Include="..\..\src\bfcp\conn.c" />
+ <ClCompile Include="..\..\src\bfcp\msg.c" />
+ <ClCompile Include="..\..\src\bfcp\reply.c" />
+ <ClCompile Include="..\..\src\bfcp\request.c" />
+ <ClCompile Include="..\..\src\conf\conf.c" />
+ <ClCompile Include="..\..\src\crc32\crc32.c" />
+ <ClCompile Include="..\..\src\dbg\dbg.c" />
+ <ClCompile Include="..\..\src\dns\client.c" />
+ <ClCompile Include="..\..\src\dns\cstr.c" />
+ <ClCompile Include="..\..\src\dns\dname.c" />
+ <ClCompile Include="..\..\src\dns\hdr.c" />
+ <ClCompile Include="..\..\src\dns\ns.c" />
+ <ClCompile Include="..\..\src\dns\rr.c" />
+ <ClCompile Include="..\..\src\dns\rrlist.c" />
+ <ClCompile Include="..\..\src\dns\win32\srv.c" />
+ <ClCompile Include="..\..\src\fmt\ch.c" />
+ <ClCompile Include="..\..\src\fmt\hexdump.c" />
+ <ClCompile Include="..\..\src\fmt\pl.c" />
+ <ClCompile Include="..\..\src\fmt\print.c" />
+ <ClCompile Include="..\..\src\fmt\prm.c" />
+ <ClCompile Include="..\..\src\fmt\regex.c" />
+ <ClCompile Include="..\..\src\fmt\str.c" />
+ <ClCompile Include="..\..\src\fmt\str_error.c" />
+ <ClCompile Include="..\..\src\fmt\time.c" />
+ <ClCompile Include="..\..\src\fmt\unicode.c" />
+ <ClCompile Include="..\..\src\hash\func.c" />
+ <ClCompile Include="..\..\src\hash\hash.c" />
+ <ClCompile Include="..\..\src\hmac\hmac.c" />
+ <ClCompile Include="..\..\src\hmac\hmac_sha1.c" />
+ <ClCompile Include="..\..\src\httpauth\basic.c" />
+ <ClCompile Include="..\..\src\httpauth\digest.c" />
+ <ClCompile Include="..\..\src\http\auth.c" />
+ <ClCompile Include="..\..\src\http\client.c" />
+ <ClCompile Include="..\..\src\http\msg.c" />
+ <ClCompile Include="..\..\src\http\server.c" />
+ <ClCompile Include="..\..\src\ice\cand.c" />
+ <ClCompile Include="..\..\src\ice\candpair.c" />
+ <ClCompile Include="..\..\src\ice\chklist.c" />
+ <ClCompile Include="..\..\src\ice\comp.c" />
+ <ClCompile Include="..\..\src\ice\connchk.c" />
+ <ClCompile Include="..\..\src\ice\ice.c" />
+ <ClCompile Include="..\..\src\ice\icem.c" />
+ <ClCompile Include="..\..\src\ice\icesdp.c" />
+ <ClCompile Include="..\..\src\ice\icestr.c" />
+ <ClCompile Include="..\..\src\ice\stunsrv.c" />
+ <ClCompile Include="..\..\src\ice\util.c" />
+ <ClCompile Include="..\..\src\jbuf\jbuf.c" />
+ <ClCompile Include="..\..\src\json\decode.c" />
+ <ClCompile Include="..\..\src\json\decode_odict.c" />
+ <ClCompile Include="..\..\src\json\encode.c" />
+ <ClCompile Include="..\..\src\list\list.c" />
+ <ClCompile Include="..\..\src\lock\win32\lock.c" />
+ <ClCompile Include="..\..\src\main\init.c" />
+ <ClCompile Include="..\..\src\main\main.c" />
+ <ClCompile Include="..\..\src\main\method.c" />
+ <ClCompile Include="..\..\src\mbuf\mbuf.c" />
+ <ClCompile Include="..\..\src\md5\md5.c" />
+ <ClCompile Include="..\..\src\md5\wrap.c" />
+ <ClCompile Include="..\..\src\mem\mem.c" />
+ <ClCompile Include="..\..\src\mod\mod.c" />
+ <ClCompile Include="..\..\src\mod\win32\dll.c" />
+ <ClCompile Include="..\..\src\mqueue\mqueue.c" />
+ <ClCompile Include="..\..\src\mqueue\win32\pipe.c" />
+ <ClCompile Include="..\..\src\msg\ctype.c" />
+ <ClCompile Include="..\..\src\msg\param.c" />
+ <ClCompile Include="..\..\src\natbd\filtering.c" />
+ <ClCompile Include="..\..\src\natbd\genalg.c" />
+ <ClCompile Include="..\..\src\natbd\hairpinning.c" />
+ <ClCompile Include="..\..\src\natbd\lifetime.c" />
+ <ClCompile Include="..\..\src\natbd\mapping.c" />
+ <ClCompile Include="..\..\src\natbd\natstr.c" />
+ <ClCompile Include="..\..\src\net\if.c" />
+ <ClCompile Include="..\..\src\net\net.c" />
+ <ClCompile Include="..\..\src\net\netstr.c" />
+ <ClCompile Include="..\..\src\net\rt.c" />
+ <ClCompile Include="..\..\src\net\sock.c" />
+ <ClCompile Include="..\..\src\net\sockopt.c" />
+ <ClCompile Include="..\..\src\net\win32\wif.c" />
+ <ClCompile Include="..\..\src\odict\entry.c" />
+ <ClCompile Include="..\..\src\odict\odict.c" />
+ <ClCompile Include="..\..\src\odict\type.c" />
+ <ClCompile Include="..\..\src\rtp\fb.c" />
+ <ClCompile Include="..\..\src\rtp\member.c" />
+ <ClCompile Include="..\..\src\rtp\ntp.c" />
+ <ClCompile Include="..\..\src\rtp\pkt.c" />
+ <ClCompile Include="..\..\src\rtp\rr.c" />
+ <ClCompile Include="..\..\src\rtp\rtcp.c" />
+ <ClCompile Include="..\..\src\rtp\rtp.c" />
+ <ClCompile Include="..\..\src\rtp\sdes.c" />
+ <ClCompile Include="..\..\src\rtp\sess.c" />
+ <ClCompile Include="..\..\src\rtp\source.c" />
+ <ClCompile Include="..\..\src\sa\ntop.c" />
+ <ClCompile Include="..\..\src\sa\printaddr.c" />
+ <ClCompile Include="..\..\src\sa\pton.c" />
+ <ClCompile Include="..\..\src\sa\sa.c" />
+ <ClCompile Include="..\..\src\sdp\attr.c" />
+ <ClCompile Include="..\..\src\sdp\format.c" />
+ <ClCompile Include="..\..\src\sdp\media.c" />
+ <ClCompile Include="..\..\src\sdp\msg.c" />
+ <ClCompile Include="..\..\src\sdp\session.c" />
+ <ClCompile Include="..\..\src\sdp\str.c" />
+ <ClCompile Include="..\..\src\sdp\util.c" />
+ <ClCompile Include="..\..\src\sha\sha1.c" />
+ <ClCompile Include="..\..\src\sipevent\listen.c" />
+ <ClCompile Include="..\..\src\sipevent\msg.c" />
+ <ClCompile Include="..\..\src\sipevent\notify.c" />
+ <ClCompile Include="..\..\src\sipevent\subscribe.c" />
+ <ClCompile Include="..\..\src\sipreg\reg.c" />
+ <ClCompile Include="..\..\src\sipsess\accept.c" />
+ <ClCompile Include="..\..\src\sipsess\ack.c" />
+ <ClCompile Include="..\..\src\sipsess\close.c" />
+ <ClCompile Include="..\..\src\sipsess\connect.c" />
+ <ClCompile Include="..\..\src\sipsess\info.c" />
+ <ClCompile Include="..\..\src\sipsess\listen.c" />
+ <ClCompile Include="..\..\src\sipsess\modify.c" />
+ <ClCompile Include="..\..\src\sipsess\reply.c" />
+ <ClCompile Include="..\..\src\sipsess\request.c" />
+ <ClCompile Include="..\..\src\sipsess\sess.c" />
+ <ClCompile Include="..\..\src\sip\addr.c" />
+ <ClCompile Include="..\..\src\sip\auth.c" />
+ <ClCompile Include="..\..\src\sip\contact.c" />
+ <ClCompile Include="..\..\src\sip\cseq.c" />
+ <ClCompile Include="..\..\src\sip\ctrans.c" />
+ <ClCompile Include="..\..\src\sip\dialog.c" />
+ <ClCompile Include="..\..\src\sip\keepalive.c" />
+ <ClCompile Include="..\..\src\sip\keepalive_udp.c" />
+ <ClCompile Include="..\..\src\sip\msg.c" />
+ <ClCompile Include="..\..\src\sip\reply.c" />
+ <ClCompile Include="..\..\src\sip\request.c" />
+ <ClCompile Include="..\..\src\sip\sip.c" />
+ <ClCompile Include="..\..\src\sip\strans.c" />
+ <ClCompile Include="..\..\src\sip\transp.c" />
+ <ClCompile Include="..\..\src\sip\via.c" />
+ <ClCompile Include="..\..\src\srtp\misc.c" />
+ <ClCompile Include="..\..\src\srtp\replay.c" />
+ <ClCompile Include="..\..\src\srtp\srtcp.c" />
+ <ClCompile Include="..\..\src\srtp\srtp.c" />
+ <ClCompile Include="..\..\src\srtp\stream.c" />
+ <ClCompile Include="..\..\src\stun\addr.c" />
+ <ClCompile Include="..\..\src\stun\attr.c" />
+ <ClCompile Include="..\..\src\stun\ctrans.c" />
+ <ClCompile Include="..\..\src\stun\dnsdisc.c" />
+ <ClCompile Include="..\..\src\stun\hdr.c" />
+ <ClCompile Include="..\..\src\stun\ind.c" />
+ <ClCompile Include="..\..\src\stun\keepalive.c" />
+ <ClCompile Include="..\..\src\stun\msg.c" />
+ <ClCompile Include="..\..\src\stun\rep.c" />
+ <ClCompile Include="..\..\src\stun\req.c" />
+ <ClCompile Include="..\..\src\stun\stun.c" />
+ <ClCompile Include="..\..\src\stun\stunstr.c" />
+ <ClCompile Include="..\..\src\sys\daemon.c" />
+ <ClCompile Include="..\..\src\sys\endian.c" />
+ <ClCompile Include="..\..\src\sys\fs.c" />
+ <ClCompile Include="..\..\src\sys\rand.c" />
+ <ClCompile Include="..\..\src\sys\sleep.c" />
+ <ClCompile Include="..\..\src\sys\sys.c" />
+ <ClCompile Include="..\..\src\tcp\tcp.c" />
+ <ClCompile Include="..\..\src\tcp\tcp_high.c" />
+ <ClCompile Include="..\..\src\telev\telev.c" />
+ <ClCompile Include="..\..\src\tmr\tmr.c" />
+ <ClCompile Include="..\..\src\turn\chan.c" />
+ <ClCompile Include="..\..\src\turn\perm.c" />
+ <ClCompile Include="..\..\src\turn\turnc.c" />
+ <ClCompile Include="..\..\src\udp\mcast.c" />
+ <ClCompile Include="..\..\src\udp\udp.c" />
+ <ClCompile Include="..\..\src\uri\ucmp.c" />
+ <ClCompile Include="..\..\src\uri\uri.c" />
+ <ClCompile Include="..\..\src\uri\uric.c" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="..\..\src\srtp\README" />
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectName>re-win32</ProjectName>
+ <ProjectGuid>{40B28DF6-4B4A-411A-9EB7-8D80C2A29B9D}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup>
+ <_ProjectFileVersion>14.0.25123.0</_ProjectFileVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <OutDir>..\..\$(Platform)\$(Configuration)\bin\</OutDir>
+ <IntDir>..\..\$(Platform)\$(Configuration)\tmp\mk\win32\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <OutDir>..\..\$(Platform)\$(Configuration)\bin\</OutDir>
+ <IntDir>..\..\$(Platform)\$(Configuration)\tmp\mk\win32\</IntDir>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <InlineFunctionExpansion>Default</InlineFunctionExpansion>
+ <AdditionalIncludeDirectories>..\..\include;..\..\..\misc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_CONSOLE;HAVE_INET_PTON;HAVE_INET_NTOP;FD_SETSIZE=1024;HAVE_SELECT;HAVE_IO_H;_CRT_SECURE_NO_DEPRECATE;WIN32_LEAN_AND_MEAN;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>true</MinimalRebuild>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <PrecompiledHeader />
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4142;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ObjectFileName>$(IntDir)%(RelativeDir)%(Filename).obj</ObjectFileName>
+ </ClCompile>
+ <Lib>
+ <AdditionalOptions>Iphlpapi.lib %(AdditionalOptions)</AdditionalOptions>
+ <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <AdditionalIncludeDirectories>..\..\include;..\..\..\misc;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_CONSOLE;HAVE_INET_PTON;HAVE_INET_NTOP;FD_SETSIZE=1024;HAVE_SELECT;HAVE_IO_H;_CRT_SECURE_NO_DEPRECATE;WIN32_LEAN_AND_MEAN;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <PrecompiledHeader />
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4142;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ObjectFileName>$(IntDir)%(RelativeDir)%(Filename).obj</ObjectFileName>
+ </ClCompile>
+ <Lib>
+ <AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Lib>
+ </ItemDefinitionGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/mk/win32/re.vcxproj.filters b/mk/win32/re.vcxproj.filters
new file mode 100644
index 0000000..27a35cf
--- /dev/null
+++ b/mk/win32/re.vcxproj.filters
@@ -0,0 +1,875 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="include">
+ <UniqueIdentifier>{24ec5e26-1680-4fb0-8766-02a6834e676d}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src">
+ <UniqueIdentifier>{f97cb0a3-607e-4302-a41d-cbac8aad6bd7}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\uri">
+ <UniqueIdentifier>{57a0561c-ea6e-45d0-a034-72d1fabd97ca}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\udp">
+ <UniqueIdentifier>{974b5b66-04cf-4237-9e6c-f706acb078bd}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\turn">
+ <UniqueIdentifier>{0115caca-c0eb-469d-9f51-1a8a53a4cf36}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\tmr">
+ <UniqueIdentifier>{e46ff0a0-f978-4356-8103-804f228ce65d}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\telev">
+ <UniqueIdentifier>{7f1f0174-24be-4049-bccd-c03c00e5bacc}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\tcp">
+ <UniqueIdentifier>{b277177e-4cd2-4028-8e24-b12237bd4122}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\sys">
+ <UniqueIdentifier>{d1a8c49e-d20f-4bca-ab49-7dd50593c09a}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\stun">
+ <UniqueIdentifier>{469bd25d-3ebd-474b-b13c-6914bfa44f2e}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\srtp">
+ <UniqueIdentifier>{793d3b1b-a4a5-49a7-aeb4-fc3be5944d3b}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\sipsess">
+ <UniqueIdentifier>{da8a92af-e8a6-45c6-9708-aa18e3adb084}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\sipreg">
+ <UniqueIdentifier>{5d4ea0d3-afee-4f12-8a79-ac4c8447777a}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\sipevent">
+ <UniqueIdentifier>{ed8fd6a9-58aa-47c4-b1f7-34e1ac182b87}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\sip">
+ <UniqueIdentifier>{e29353b1-55db-453e-91c6-f7f37748147c}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\sdp">
+ <UniqueIdentifier>{f3ce68eb-55bb-46b2-8d3f-1b1048fad972}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\sa">
+ <UniqueIdentifier>{c6620f98-9fd7-45ea-9b58-cc2c4a3b6f2d}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\rtp">
+ <UniqueIdentifier>{60bd6b3a-4f71-48fb-a161-10ce29910d93}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\odict">
+ <UniqueIdentifier>{8643b7af-6ec5-43b4-b0fe-399a79d445cc}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\net">
+ <UniqueIdentifier>{f7c75b83-f284-430c-b18c-b2633914a750}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\net\win32">
+ <UniqueIdentifier>{779e44ed-0935-450c-a3dd-7bdef53a1179}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\natbd">
+ <UniqueIdentifier>{1bff8dd5-d220-4f10-8c26-64c82522d824}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\msg">
+ <UniqueIdentifier>{9ea33625-0411-4065-a16b-ce3f49d3ca15}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\mqueue">
+ <UniqueIdentifier>{955109b7-ec66-49d3-b4cf-0ba816e095e4}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\mqueue\win32">
+ <UniqueIdentifier>{31d6e0d5-7c9d-4ca7-a911-4be6b45291b0}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\mod">
+ <UniqueIdentifier>{249dc72a-9cb9-45fa-bdd1-742fafb1d831}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\mod\win32">
+ <UniqueIdentifier>{e6d208f2-2ef2-49bc-a13a-d66885a1dd61}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\mem">
+ <UniqueIdentifier>{fc6552c5-e5cd-4f77-9daa-77361089d71f}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\md5">
+ <UniqueIdentifier>{eb84b7a5-764a-43c4-98b1-90c989cbe8e7}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\mbuf">
+ <UniqueIdentifier>{09180e07-9646-454c-ae1a-7bfdc9ec96cc}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\main">
+ <UniqueIdentifier>{4d08218c-9027-430b-bec4-5b435620ca77}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\lock">
+ <UniqueIdentifier>{3489e5fe-f25d-4410-a351-8c485609a49a}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\lock\win32">
+ <UniqueIdentifier>{c33fec0f-9764-431d-9fe3-46704e850f6b}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\list">
+ <UniqueIdentifier>{fb912922-9084-4a6a-b36f-cf7039fbdf59}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\json">
+ <UniqueIdentifier>{bf38bff6-d13b-40e9-908f-73aa6bd29dc2}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\jbuf">
+ <UniqueIdentifier>{8723551b-d33a-464f-b15e-d477b65d8d34}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\ice">
+ <UniqueIdentifier>{c9915c2b-1f49-45bc-9552-8679bd697f23}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\httpauth">
+ <UniqueIdentifier>{c8b66d98-2787-4b05-be8c-d4acd28dbf9e}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\http">
+ <UniqueIdentifier>{ac3c7db7-cb2d-4191-9c45-f499de912f51}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\hmac">
+ <UniqueIdentifier>{9a795fdf-efaf-4ea1-b7c7-671616e84f0a}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\hash">
+ <UniqueIdentifier>{2af593ee-1fa6-44c1-b6ca-01d437c39397}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\fmt">
+ <UniqueIdentifier>{37c6bc4f-8fd1-40cf-b04c-77ae5d7f80c6}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\dns">
+ <UniqueIdentifier>{6aae1e31-0191-4c51-a470-c43be58ae837}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\dns\win32">
+ <UniqueIdentifier>{f029350a-d256-4dda-889b-430f65615047}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\dbg">
+ <UniqueIdentifier>{c1c4b382-5196-45dc-9185-1b321a34caa2}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\crc32">
+ <UniqueIdentifier>{7590d26c-f92a-4da7-89be-857b74649099}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\conf">
+ <UniqueIdentifier>{394be8ad-8a7d-4ed8-93a5-cf4a1c9f5c37}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\bfcp">
+ <UniqueIdentifier>{26df0238-b37e-4c97-97d4-549f23a4fb1b}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\aes">
+ <UniqueIdentifier>{3f9bcd72-2513-4e1e-826e-6df1e9c3fa3e}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\base64">
+ <UniqueIdentifier>{7a203acf-4223-4b71-8705-995eac29db75}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="src\sha">
+ <UniqueIdentifier>{5bb58e89-4183-4380-993a-d01d1bc3d9d9}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\include\re.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_aes.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_base64.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_bfcp.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_bitv.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_conf.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_crc32.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_dbg.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_dns.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_fmt.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_hash.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_hmac.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_http.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_httpauth.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_ice.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_jbuf.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_json.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_list.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_lock.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_main.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_mbuf.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_md5.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_mem.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_mod.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_mqueue.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_msg.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_natbd.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_net.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_odict.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_rtp.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_sa.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_sdp.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_sha.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_sip.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_sipevent.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_sipreg.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_sipsess.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_srtp.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_stun.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_sys.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_tcp.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_telev.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_tls.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_tmr.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_turn.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_types.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_udp.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_uri.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\include\re_websock.h">
+ <Filter>include</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\turn\turnc.h">
+ <Filter>src\turn</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\stun\stun.h">
+ <Filter>src\stun</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\srtp\srtp.h">
+ <Filter>src\srtp</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\sipsess\sipsess.h">
+ <Filter>src\sipsess</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\sipevent\sipevent.h">
+ <Filter>src\sipevent</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\sip\sip.h">
+ <Filter>src\sip</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\sdp\sdp.h">
+ <Filter>src\sdp</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\sa\sa.h">
+ <Filter>src\sa</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\rtp\rtcp.h">
+ <Filter>src\rtp</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\mqueue\mqueue.h">
+ <Filter>src\mqueue</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\mod\mod_internal.h">
+ <Filter>src\mod</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\md5\md5.h">
+ <Filter>src\md5</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\main\main.h">
+ <Filter>src\main</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\ice\ice.h">
+ <Filter>src\ice</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\dns\dns.h">
+ <Filter>src\dns</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\src\bfcp\bfcp.h">
+ <Filter>src\bfcp</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\src\uri\ucmp.c">
+ <Filter>src\uri</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\uri\uri.c">
+ <Filter>src\uri</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\uri\uric.c">
+ <Filter>src\uri</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\udp\mcast.c">
+ <Filter>src\udp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\udp\udp.c">
+ <Filter>src\udp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\turn\chan.c">
+ <Filter>src\turn</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\turn\perm.c">
+ <Filter>src\turn</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\turn\turnc.c">
+ <Filter>src\turn</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\tmr\tmr.c">
+ <Filter>src\tmr</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\telev\telev.c">
+ <Filter>src\telev</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\tcp\tcp.c">
+ <Filter>src\tcp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\tcp\tcp_high.c">
+ <Filter>src\tcp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sys\daemon.c">
+ <Filter>src\sys</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sys\endian.c">
+ <Filter>src\sys</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sys\fs.c">
+ <Filter>src\sys</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sys\rand.c">
+ <Filter>src\sys</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sys\sleep.c">
+ <Filter>src\sys</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sys\sys.c">
+ <Filter>src\sys</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stun\addr.c">
+ <Filter>src\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stun\attr.c">
+ <Filter>src\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stun\ctrans.c">
+ <Filter>src\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stun\dnsdisc.c">
+ <Filter>src\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stun\hdr.c">
+ <Filter>src\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stun\ind.c">
+ <Filter>src\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stun\keepalive.c">
+ <Filter>src\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stun\msg.c">
+ <Filter>src\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stun\rep.c">
+ <Filter>src\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stun\req.c">
+ <Filter>src\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stun\stun.c">
+ <Filter>src\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\stun\stunstr.c">
+ <Filter>src\stun</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\srtp\misc.c">
+ <Filter>src\srtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\srtp\replay.c">
+ <Filter>src\srtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\srtp\srtcp.c">
+ <Filter>src\srtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\srtp\srtp.c">
+ <Filter>src\srtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\srtp\stream.c">
+ <Filter>src\srtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipsess\accept.c">
+ <Filter>src\sipsess</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipsess\ack.c">
+ <Filter>src\sipsess</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipsess\close.c">
+ <Filter>src\sipsess</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipsess\connect.c">
+ <Filter>src\sipsess</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipsess\info.c">
+ <Filter>src\sipsess</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipsess\listen.c">
+ <Filter>src\sipsess</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipsess\modify.c">
+ <Filter>src\sipsess</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipsess\reply.c">
+ <Filter>src\sipsess</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipsess\request.c">
+ <Filter>src\sipsess</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipsess\sess.c">
+ <Filter>src\sipsess</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipreg\reg.c">
+ <Filter>src\sipreg</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipevent\listen.c">
+ <Filter>src\sipevent</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipevent\msg.c">
+ <Filter>src\sipevent</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipevent\notify.c">
+ <Filter>src\sipevent</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sipevent\subscribe.c">
+ <Filter>src\sipevent</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\addr.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\auth.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\contact.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\cseq.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\ctrans.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\dialog.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\keepalive.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\keepalive_udp.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\msg.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\reply.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\request.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\sip.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\strans.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\transp.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sip\via.c">
+ <Filter>src\sip</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sdp\attr.c">
+ <Filter>src\sdp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sdp\format.c">
+ <Filter>src\sdp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sdp\media.c">
+ <Filter>src\sdp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sdp\msg.c">
+ <Filter>src\sdp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sdp\session.c">
+ <Filter>src\sdp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sdp\str.c">
+ <Filter>src\sdp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sdp\util.c">
+ <Filter>src\sdp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sa\ntop.c">
+ <Filter>src\sa</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sa\printaddr.c">
+ <Filter>src\sa</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sa\pton.c">
+ <Filter>src\sa</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sa\sa.c">
+ <Filter>src\sa</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\rtp\fb.c">
+ <Filter>src\rtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\rtp\member.c">
+ <Filter>src\rtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\rtp\ntp.c">
+ <Filter>src\rtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\rtp\pkt.c">
+ <Filter>src\rtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\rtp\rr.c">
+ <Filter>src\rtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\rtp\rtcp.c">
+ <Filter>src\rtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\rtp\rtp.c">
+ <Filter>src\rtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\rtp\sdes.c">
+ <Filter>src\rtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\rtp\sess.c">
+ <Filter>src\rtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\rtp\source.c">
+ <Filter>src\rtp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\odict\entry.c">
+ <Filter>src\odict</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\odict\odict.c">
+ <Filter>src\odict</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\odict\type.c">
+ <Filter>src\odict</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\net\if.c">
+ <Filter>src\net</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\net\net.c">
+ <Filter>src\net</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\net\netstr.c">
+ <Filter>src\net</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\net\rt.c">
+ <Filter>src\net</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\net\sock.c">
+ <Filter>src\net</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\net\sockopt.c">
+ <Filter>src\net</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\net\win32\wif.c">
+ <Filter>src\net\win32</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\natbd\filtering.c">
+ <Filter>src\natbd</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\natbd\genalg.c">
+ <Filter>src\natbd</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\natbd\hairpinning.c">
+ <Filter>src\natbd</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\natbd\lifetime.c">
+ <Filter>src\natbd</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\natbd\mapping.c">
+ <Filter>src\natbd</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\natbd\natstr.c">
+ <Filter>src\natbd</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\msg\ctype.c">
+ <Filter>src\msg</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\msg\param.c">
+ <Filter>src\msg</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\mqueue\mqueue.c">
+ <Filter>src\mqueue</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\mqueue\win32\pipe.c">
+ <Filter>src\mqueue\win32</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\mod\mod.c">
+ <Filter>src\mod</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\mod\win32\dll.c">
+ <Filter>src\mod\win32</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\mem\mem.c">
+ <Filter>src\mem</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\md5\md5.c">
+ <Filter>src\md5</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\md5\wrap.c">
+ <Filter>src\md5</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\mbuf\mbuf.c">
+ <Filter>src\mbuf</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\main\init.c">
+ <Filter>src\main</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\main\main.c">
+ <Filter>src\main</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\main\method.c">
+ <Filter>src\main</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\lock\win32\lock.c">
+ <Filter>src\lock\win32</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\list\list.c">
+ <Filter>src\list</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\json\decode.c">
+ <Filter>src\json</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\json\decode_odict.c">
+ <Filter>src\json</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\json\encode.c">
+ <Filter>src\json</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\jbuf\jbuf.c">
+ <Filter>src\jbuf</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ice\cand.c">
+ <Filter>src\ice</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ice\candpair.c">
+ <Filter>src\ice</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ice\chklist.c">
+ <Filter>src\ice</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ice\comp.c">
+ <Filter>src\ice</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ice\connchk.c">
+ <Filter>src\ice</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ice\ice.c">
+ <Filter>src\ice</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ice\icem.c">
+ <Filter>src\ice</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ice\icesdp.c">
+ <Filter>src\ice</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ice\icestr.c">
+ <Filter>src\ice</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ice\stunsrv.c">
+ <Filter>src\ice</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\ice\util.c">
+ <Filter>src\ice</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\httpauth\basic.c">
+ <Filter>src\httpauth</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\httpauth\digest.c">
+ <Filter>src\httpauth</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\http\auth.c">
+ <Filter>src\http</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\http\client.c">
+ <Filter>src\http</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\http\msg.c">
+ <Filter>src\http</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\http\server.c">
+ <Filter>src\http</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\hmac\hmac.c">
+ <Filter>src\hmac</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\hmac\hmac_sha1.c">
+ <Filter>src\hmac</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\hash\func.c">
+ <Filter>src\hash</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\hash\hash.c">
+ <Filter>src\hash</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\fmt\ch.c">
+ <Filter>src\fmt</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\fmt\hexdump.c">
+ <Filter>src\fmt</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\fmt\pl.c">
+ <Filter>src\fmt</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\fmt\print.c">
+ <Filter>src\fmt</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\fmt\prm.c">
+ <Filter>src\fmt</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\fmt\regex.c">
+ <Filter>src\fmt</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\fmt\str.c">
+ <Filter>src\fmt</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\fmt\str_error.c">
+ <Filter>src\fmt</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\fmt\time.c">
+ <Filter>src\fmt</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\fmt\unicode.c">
+ <Filter>src\fmt</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\dns\client.c">
+ <Filter>src\dns</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\dns\cstr.c">
+ <Filter>src\dns</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\dns\dname.c">
+ <Filter>src\dns</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\dns\hdr.c">
+ <Filter>src\dns</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\dns\ns.c">
+ <Filter>src\dns</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\dns\rr.c">
+ <Filter>src\dns</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\dns\rrlist.c">
+ <Filter>src\dns</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\dns\win32\srv.c">
+ <Filter>src\dns\win32</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\dbg\dbg.c">
+ <Filter>src\dbg</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\crc32\crc32.c">
+ <Filter>src\crc32</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\conf\conf.c">
+ <Filter>src\conf</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\bfcp\attr.c">
+ <Filter>src\bfcp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\bfcp\conn.c">
+ <Filter>src\bfcp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\bfcp\msg.c">
+ <Filter>src\bfcp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\bfcp\reply.c">
+ <Filter>src\bfcp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\bfcp\request.c">
+ <Filter>src\bfcp</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\aes\stub.c">
+ <Filter>src\aes</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\base64\b64.c">
+ <Filter>src\base64</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\src\sha\sha1.c">
+ <Filter>src\sha</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="..\..\src\srtp\README">
+ <Filter>src\srtp</Filter>
+ </None>
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/rpm/re.spec b/rpm/re.spec
new file mode 100644
index 0000000..cc91773
--- /dev/null
+++ b/rpm/re.spec
@@ -0,0 +1,60 @@
+%define name re
+%define ver 0.6.0
+%define rel 1
+
+Summary: Generic library for real-time communications with async IO support
+Name: %name
+Version: %ver
+Release: %rel
+License: BSD
+Group: Applications/Devel
+Source0: file://%{name}-%{version}.tar.gz
+URL: http://www.creytiv.com/
+Vendor: Creytiv
+Packager: Alfred E. Heggestad <aeh@db.org>
+BuildRoot: /var/tmp/%{name}-build-root
+
+%description
+Generic library for real-time communications with async IO support
+
+%package devel
+Summary: libre development files
+Group: Development/Libraries
+Requires: %{name} = %{version}
+
+%description devel
+libre development files.
+
+%prep
+%setup
+
+%build
+make RELEASE=1
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make DESTDIR=$RPM_BUILD_ROOT install \
+%ifarch x86_64
+ LIBDIR=/usr/lib64
+%endif
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%post -p /sbin/ldconfig
+%postun -p /sbin/ldconfig
+
+%files
+%defattr(644,root,root,755)
+%attr(755,root,root) %{_libdir}/libre*.so*
+
+%files devel
+%defattr(644,root,root,755)
+%{_includedir}/re/*.h
+/usr/share/re/re.mk
+%{_libdir}/libre*.a
+%{_libdir}/pkgconfig/libre.pc
+
+%changelog
+* Fri Nov 5 2010 Alfred E. Heggestad <aeh@db.org> -
+- Initial build.
diff --git a/src/aes/apple/aes.c b/src/aes/apple/aes.c
new file mode 100644
index 0000000..8f5809e
--- /dev/null
+++ b/src/aes/apple/aes.c
@@ -0,0 +1,145 @@
+/**
+ * @file apple/aes.c AES using Apple CommonCrypto API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_fmt.h>
+#include <re_aes.h>
+#include <CommonCrypto/CommonCryptor.h>
+
+
+struct aes {
+ CCCryptorRef cryptor;
+ uint8_t key[64];
+ size_t key_bytes;
+};
+
+
+static void destructor(void *arg)
+{
+ struct aes *st = arg;
+
+ if (st->cryptor)
+ CCCryptorRelease(st->cryptor);
+}
+
+
+int aes_alloc(struct aes **stp, enum aes_mode mode,
+ const uint8_t *key, size_t key_bits,
+ const uint8_t *iv)
+{
+ struct aes *st;
+ size_t key_bytes = key_bits / 8;
+ CCCryptorStatus status;
+ int err = 0;
+
+ if (!stp || !key)
+ return EINVAL;
+
+ if (mode != AES_MODE_CTR)
+ return ENOTSUP;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ if (key_bytes > sizeof(st->key)) {
+ err = EINVAL;
+ goto out;
+ }
+
+ st->key_bytes = key_bytes;
+ memcpy(st->key, key, st->key_bytes);
+
+ /* used for both encryption and decryption because CTR is symmetric */
+ status = CCCryptorCreateWithMode(kCCEncrypt, kCCModeCTR,
+ kCCAlgorithmAES, ccNoPadding,
+ iv, key, key_bytes, NULL, 0, 0,
+ kCCModeOptionCTR_BE, &st->cryptor);
+ if (status != kCCSuccess) {
+ err = EPROTO;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *stp = st;
+
+ return err;
+}
+
+
+void aes_set_iv(struct aes *st, const uint8_t *iv)
+{
+ CCCryptorStatus status;
+
+ if (!st)
+ return;
+
+ /* we must reset the state when updating IV */
+ if (st->cryptor) {
+ CCCryptorRelease(st->cryptor);
+ st->cryptor = NULL;
+ }
+
+ status = CCCryptorCreateWithMode(kCCEncrypt, kCCModeCTR,
+ kCCAlgorithmAES, ccNoPadding,
+ iv, st->key, st->key_bytes,
+ NULL, 0, 0, kCCModeOptionCTR_BE,
+ &st->cryptor);
+ if (status != kCCSuccess) {
+ re_fprintf(stderr, "aes: CCCryptorCreateWithMode error (%d)\n",
+ status);
+ }
+}
+
+
+int aes_encr(struct aes *st, uint8_t *out, const uint8_t *in, size_t len)
+{
+ CCCryptorStatus status;
+ size_t moved;
+
+ if (!st || !out || !in)
+ return EINVAL;
+
+ status = CCCryptorUpdate(st->cryptor, in, len, out, len, &moved);
+ if (status != kCCSuccess) {
+ re_fprintf(stderr, "aes: CCCryptorUpdate error (%d)\n",
+ status);
+ return EPROTO;
+ }
+
+ return 0;
+}
+
+
+int aes_decr(struct aes *st, uint8_t *out, const uint8_t *in, size_t len)
+{
+ return aes_encr(st, out, in, len);
+}
+
+
+int aes_get_authtag(struct aes *aes, uint8_t *tag, size_t taglen)
+{
+ (void)aes;
+ (void)tag;
+ (void)taglen;
+
+ return ENOSYS;
+}
+
+
+int aes_authenticate(struct aes *aes, const uint8_t *tag, size_t taglen)
+{
+ (void)aes;
+ (void)tag;
+ (void)taglen;
+
+ return ENOSYS;
+}
diff --git a/src/aes/mod.mk b/src/aes/mod.mk
new file mode 100644
index 0000000..c522149
--- /dev/null
+++ b/src/aes/mod.mk
@@ -0,0 +1,13 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+ifneq ($(USE_OPENSSL_AES),)
+SRCS += aes/openssl/aes.c
+else ifneq ($(USE_APPLE_COMMONCRYPTO),)
+SRCS += aes/apple/aes.c
+else
+SRCS += aes/stub.c
+endif
diff --git a/src/aes/openssl/aes.c b/src/aes/openssl/aes.c
new file mode 100644
index 0000000..4d710f2
--- /dev/null
+++ b/src/aes/openssl/aes.c
@@ -0,0 +1,267 @@
+/**
+ * @file openssl/aes.c AES (Advanced Encryption Standard) using OpenSSL
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <openssl/aes.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_aes.h>
+
+
+struct aes {
+ EVP_CIPHER_CTX *ctx;
+ enum aes_mode mode;
+ bool encr;
+};
+
+
+static const EVP_CIPHER *aes_cipher(enum aes_mode mode, size_t key_bits)
+{
+ if (mode == AES_MODE_CTR) {
+
+ switch (key_bits) {
+
+ case 128: return EVP_aes_128_ctr();
+ case 192: return EVP_aes_192_ctr();
+ case 256: return EVP_aes_256_ctr();
+ default:
+ return NULL;
+ }
+ }
+ else if (mode == AES_MODE_GCM) {
+
+ switch (key_bits) {
+
+ case 128: return EVP_aes_128_gcm();
+ case 256: return EVP_aes_256_gcm();
+ default:
+ return NULL;
+ }
+ }
+ else {
+ return NULL;
+ }
+}
+
+
+static inline bool set_crypt_dir(struct aes *aes, bool encr)
+{
+ if (aes->encr != encr) {
+
+ /* update the encrypt/decrypt direction */
+ if (!EVP_CipherInit_ex(aes->ctx, NULL, NULL,
+ NULL, NULL, encr)) {
+ ERR_clear_error();
+ return false;
+ }
+
+ aes->encr = encr;
+ }
+
+ return true;
+}
+
+
+static void destructor(void *arg)
+{
+ struct aes *st = arg;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ if (st->ctx)
+ EVP_CIPHER_CTX_free(st->ctx);
+#else
+ if (st->ctx)
+ EVP_CIPHER_CTX_cleanup(st->ctx);
+ mem_deref(st->ctx);
+#endif
+}
+
+
+int aes_alloc(struct aes **aesp, enum aes_mode mode,
+ const uint8_t *key, size_t key_bits,
+ const uint8_t *iv)
+{
+ const EVP_CIPHER *cipher;
+ struct aes *st;
+ int err = 0, r;
+
+ if (!aesp || !key)
+ return EINVAL;
+
+ cipher = aes_cipher(mode, key_bits);
+ if (!cipher)
+ return ENOTSUP;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ st->mode = mode;
+ st->encr = true;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ st->ctx = EVP_CIPHER_CTX_new();
+ if (!st->ctx) {
+ ERR_clear_error();
+ err = ENOMEM;
+ goto out;
+ }
+
+#else
+ st->ctx = mem_zalloc(sizeof(*st->ctx), NULL);
+ if (!st->ctx) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ EVP_CIPHER_CTX_init(st->ctx);
+#endif
+
+ r = EVP_EncryptInit_ex(st->ctx, cipher, NULL, key, iv);
+ if (!r) {
+ ERR_clear_error();
+ err = EPROTO;
+ }
+
+ out:
+ if (err)
+ mem_deref(st);
+ else
+ *aesp = st;
+
+ return err;
+}
+
+
+void aes_set_iv(struct aes *aes, const uint8_t *iv)
+{
+ int r;
+
+ if (!aes || !iv)
+ return;
+
+ r = EVP_CipherInit_ex(aes->ctx, NULL, NULL, NULL, iv, -1);
+ if (!r)
+ ERR_clear_error();
+}
+
+
+int aes_encr(struct aes *aes, uint8_t *out, const uint8_t *in, size_t len)
+{
+ int c_len = (int)len;
+
+ if (!aes || !in)
+ return EINVAL;
+
+ if (!set_crypt_dir(aes, true))
+ return EPROTO;
+
+ if (!EVP_EncryptUpdate(aes->ctx, out, &c_len, in, (int)len)) {
+ ERR_clear_error();
+ return EPROTO;
+ }
+
+ return 0;
+}
+
+
+int aes_decr(struct aes *aes, uint8_t *out, const uint8_t *in, size_t len)
+{
+ int c_len = (int)len;
+
+ if (!aes || !in)
+ return EINVAL;
+
+ if (!set_crypt_dir(aes, false))
+ return EPROTO;
+
+ if (!EVP_DecryptUpdate(aes->ctx, out, &c_len, in, (int)len)) {
+ ERR_clear_error();
+ return EPROTO;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Get the authentication tag for an AEAD cipher (e.g. GCM)
+ *
+ * @param aes AES Context
+ * @param tag Authentication tag
+ * @param taglen Length of Authentication tag
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int aes_get_authtag(struct aes *aes, uint8_t *tag, size_t taglen)
+{
+ int tmplen;
+
+ if (!aes || !tag || !taglen)
+ return EINVAL;
+
+ switch (aes->mode) {
+
+ case AES_MODE_GCM:
+ if (!EVP_EncryptFinal_ex(aes->ctx, NULL, &tmplen)) {
+ ERR_clear_error();
+ return EPROTO;
+ }
+
+ if (!EVP_CIPHER_CTX_ctrl(aes->ctx, EVP_CTRL_GCM_GET_TAG,
+ (int)taglen, tag)) {
+ ERR_clear_error();
+ return EPROTO;
+ }
+
+ return 0;
+
+ default:
+ return ENOTSUP;
+ }
+}
+
+
+/**
+ * Authenticate a decryption tag for an AEAD cipher (e.g. GCM)
+ *
+ * @param aes AES Context
+ * @param tag Authentication tag
+ * @param taglen Length of Authentication tag
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * @retval EAUTH if authentication failed
+ */
+int aes_authenticate(struct aes *aes, const uint8_t *tag, size_t taglen)
+{
+ int tmplen;
+
+ if (!aes || !tag || !taglen)
+ return EINVAL;
+
+ switch (aes->mode) {
+
+ case AES_MODE_GCM:
+ if (!EVP_CIPHER_CTX_ctrl(aes->ctx, EVP_CTRL_GCM_SET_TAG,
+ (int)taglen, (void *)tag)) {
+ ERR_clear_error();
+ return EPROTO;
+ }
+
+ if (EVP_DecryptFinal_ex(aes->ctx, NULL, &tmplen) <= 0) {
+ ERR_clear_error();
+ return EAUTH;
+ }
+
+ return 0;
+
+ default:
+ return ENOTSUP;
+ }
+}
diff --git a/src/aes/stub.c b/src/aes/stub.c
new file mode 100644
index 0000000..32506d9
--- /dev/null
+++ b/src/aes/stub.c
@@ -0,0 +1,67 @@
+/**
+ * @file aes/stub.c AES stub
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_aes.h>
+
+
+int aes_alloc(struct aes **stp, enum aes_mode mode,
+ const uint8_t *key, size_t key_bits,
+ const uint8_t iv[AES_BLOCK_SIZE])
+{
+ (void)stp;
+ (void)mode;
+ (void)key;
+ (void)key_bits;
+ (void)iv;
+ return ENOSYS;
+}
+
+
+void aes_set_iv(struct aes *st, const uint8_t iv[AES_BLOCK_SIZE])
+{
+ (void)st;
+ (void)iv;
+}
+
+
+int aes_encr(struct aes *st, uint8_t *out, const uint8_t *in, size_t len)
+{
+ (void)st;
+ (void)out;
+ (void)in;
+ (void)len;
+ return ENOSYS;
+}
+
+
+int aes_decr(struct aes *st, uint8_t *out, const uint8_t *in, size_t len)
+{
+ (void)st;
+ (void)out;
+ (void)in;
+ (void)len;
+ return ENOSYS;
+}
+
+
+int aes_get_authtag(struct aes *aes, uint8_t *tag, size_t taglen)
+{
+ (void)aes;
+ (void)tag;
+ (void)taglen;
+
+ return ENOSYS;
+}
+
+
+int aes_authenticate(struct aes *aes, const uint8_t *tag, size_t taglen)
+{
+ (void)aes;
+ (void)tag;
+ (void)taglen;
+
+ return ENOSYS;
+}
diff --git a/src/base64/b64.c b/src/base64/b64.c
new file mode 100644
index 0000000..a235875
--- /dev/null
+++ b/src/base64/b64.c
@@ -0,0 +1,156 @@
+/**
+ * @file b64.c Base64 encoding/decoding functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_base64.h>
+
+
+static const char b64_table[65] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+
+
+/**
+ * Base-64 encode a buffer
+ *
+ * @param in Input buffer
+ * @param ilen Length of input buffer
+ * @param out Output buffer
+ * @param olen Size of output buffer, actual written on return
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int base64_encode(const uint8_t *in, size_t ilen, char *out, size_t *olen)
+{
+ const uint8_t *in_end = in + ilen;
+ const char *o = out;
+
+ if (!in || !out || !olen)
+ return EINVAL;
+
+ if (*olen < 4 * ((ilen+2)/3))
+ return EOVERFLOW;
+
+ for (; in < in_end; ) {
+ uint32_t v;
+ int pad = 0;
+
+ v = *in++ << 16;
+ if (in < in_end) {
+ v |= *in++ << 8;
+ }
+ else {
+ ++pad;
+ }
+ if (in < in_end) {
+ v |= *in++ << 0;
+ }
+ else {
+ ++pad;
+ }
+
+ *out++ = b64_table[v>>18 & 0x3f];
+ *out++ = b64_table[v>>12 & 0x3f];
+ *out++ = (pad >= 2) ? '=' : b64_table[v>>6 & 0x3f];
+ *out++ = (pad >= 1) ? '=' : b64_table[v>>0 & 0x3f];
+ }
+
+ *olen = out - o;
+
+ return 0;
+}
+
+
+int base64_print(struct re_printf *pf, const uint8_t *ptr, size_t len)
+{
+ char buf[256];
+
+ if (!pf || !ptr)
+ return EINVAL;
+
+ while (len > 0) {
+ size_t l, sz = sizeof(buf);
+ int err;
+
+ l = min(len, 3 * (sizeof(buf)/4));
+
+ err = base64_encode(ptr, l, buf, &sz);
+ if (err)
+ return err;
+
+ err = pf->vph(buf, sz, pf->arg);
+ if (err)
+ return err;
+
+ ptr += l;
+ len -= l;
+ }
+
+ return 0;
+}
+
+
+/* convert char -> 6-bit value */
+static inline uint32_t b64val(char c)
+{
+ if ('A' <= c && c <= 'Z')
+ return c - 'A' + 0;
+ else if ('a' <= c && c <= 'z')
+ return c - 'a' + 26;
+ else if ('0' <= c && c <= '9')
+ return c - '0' + 52;
+ else if ('+' == c)
+ return 62;
+ else if ('/' == c)
+ return 63;
+ else if ('=' == c)
+ return 1<<24; /* special trick */
+ else
+ return 0;
+}
+
+
+/**
+ * Decode a Base-64 encoded string
+ *
+ * @param in Input buffer
+ * @param ilen Length of input buffer
+ * @param out Output buffer
+ * @param olen Size of output buffer, actual written on return
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int base64_decode(const char *in, size_t ilen, uint8_t *out, size_t *olen)
+{
+ const char *in_end = in + ilen;
+ const uint8_t *o = out;
+
+ if (!in || !out || !olen)
+ return EINVAL;
+
+ if (*olen < 3 * (ilen/4))
+ return EOVERFLOW;
+
+ for (;in+3 < in_end; ) {
+ uint32_t v;
+
+ v = b64val(*in++) << 18;
+ v |= b64val(*in++) << 12;
+ v |= b64val(*in++) << 6;
+ v |= b64val(*in++) << 0;
+
+ *out++ = v>>16;
+ if (!(v & (1<<30)))
+ *out++ = (v>>8) & 0xff;
+ if (!(v & (1<<24)))
+ *out++ = (v>>0) & 0xff;
+ }
+
+ *olen = out - o;
+
+ return 0;
+}
diff --git a/src/base64/mod.mk b/src/base64/mod.mk
new file mode 100644
index 0000000..fddc2ee
--- /dev/null
+++ b/src/base64/mod.mk
@@ -0,0 +1,7 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += base64/b64.c
diff --git a/src/bfcp/attr.c b/src/bfcp/attr.c
new file mode 100644
index 0000000..7607fe0
--- /dev/null
+++ b/src/bfcp/attr.c
@@ -0,0 +1,746 @@
+/**
+ * @file bfcp/attr.c BFCP Attributes
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_bfcp.h>
+#include "bfcp.h"
+
+
+enum {
+ BFCP_ATTR_HDR_SIZE = 2,
+};
+
+
+static void destructor(void *arg)
+{
+ struct bfcp_attr *attr = arg;
+
+ switch (attr->type) {
+
+ case BFCP_ERROR_CODE:
+ mem_deref(attr->v.errcode.details);
+ break;
+
+ case BFCP_ERROR_INFO:
+ case BFCP_PART_PROV_INFO:
+ case BFCP_STATUS_INFO:
+ case BFCP_USER_DISP_NAME:
+ case BFCP_USER_URI:
+ mem_deref(attr->v.str);
+ break;
+
+ case BFCP_SUPPORTED_ATTRS:
+ mem_deref(attr->v.supattr.attrv);
+ break;
+
+ case BFCP_SUPPORTED_PRIMS:
+ mem_deref(attr->v.supprim.primv);
+ break;
+
+ default:
+ break;
+ }
+
+ list_flush(&attr->attrl);
+ list_unlink(&attr->le);
+}
+
+
+static int attr_encode(struct mbuf *mb, bool mand, enum bfcp_attrib type,
+ const void *v)
+{
+ const struct bfcp_reqstatus *reqstatus = v;
+ const struct bfcp_errcode *errcode = v;
+ const struct bfcp_supattr *supattr = v;
+ const struct bfcp_supprim *supprim = v;
+ const enum bfcp_priority *priority = v;
+ const uint16_t *u16 = v;
+ size_t start, len, i;
+ int err;
+
+ start = mb->pos;
+ mb->pos += BFCP_ATTR_HDR_SIZE;
+
+ switch (type) {
+
+ case BFCP_BENEFICIARY_ID:
+ case BFCP_FLOOR_ID:
+ case BFCP_FLOOR_REQUEST_ID:
+ case BFCP_BENEFICIARY_INFO:
+ case BFCP_FLOOR_REQ_INFO:
+ case BFCP_REQUESTED_BY_INFO:
+ case BFCP_FLOOR_REQ_STATUS:
+ case BFCP_OVERALL_REQ_STATUS:
+ err = mbuf_write_u16(mb, htons(*u16));
+ break;
+
+ case BFCP_PRIORITY:
+ err = mbuf_write_u8(mb, *priority << 5);
+ err |= mbuf_write_u8(mb, 0x00);
+ break;
+
+ case BFCP_REQUEST_STATUS:
+ err = mbuf_write_u8(mb, reqstatus->status);
+ err |= mbuf_write_u8(mb, reqstatus->qpos);
+ break;
+
+ case BFCP_ERROR_CODE:
+ err = mbuf_write_u8(mb, errcode->code);
+ if (errcode->details && errcode->len)
+ err |= mbuf_write_mem(mb, errcode->details,
+ errcode->len);
+ break;
+
+ case BFCP_ERROR_INFO:
+ case BFCP_PART_PROV_INFO:
+ case BFCP_STATUS_INFO:
+ case BFCP_USER_DISP_NAME:
+ case BFCP_USER_URI:
+ err = mbuf_write_str(mb, v);
+ break;
+
+ case BFCP_SUPPORTED_ATTRS:
+ for (i=0, err=0; i<supattr->attrc; i++)
+ err |= mbuf_write_u8(mb, supattr->attrv[i] << 1);
+ break;
+
+ case BFCP_SUPPORTED_PRIMS:
+ for (i=0, err=0; i<supprim->primc; i++)
+ err |= mbuf_write_u8(mb, supprim->primv[i]);
+ break;
+
+ default:
+ err = EINVAL;
+ break;
+ }
+
+ /* header */
+ len = mb->pos - start;
+
+ mb->pos = start;
+ err |= mbuf_write_u8(mb, (type<<1) | (mand ? 1 : 0));
+ err |= mbuf_write_u8(mb, len);
+ mb->pos += (len - BFCP_ATTR_HDR_SIZE);
+
+ /* padding */
+ while ((mb->pos - start) & 0x03)
+ err |= mbuf_write_u8(mb, 0x00);
+
+ return err;
+}
+
+
+/**
+ * Encode BFCP Attributes with variable arguments
+ *
+ * @param mb Mbuf to encode into
+ * @param attrc Number of attributes
+ * @param ap Variable argument of attributes
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int bfcp_attrs_vencode(struct mbuf *mb, unsigned attrc, va_list *ap)
+{
+ unsigned i;
+
+ if (!mb)
+ return EINVAL;
+
+ for (i=0; i<attrc; i++) {
+
+ int type = va_arg(*ap, int);
+ unsigned subc = va_arg(*ap, unsigned);
+ const void *v = va_arg(*ap, const void *);
+ size_t start, len;
+ int err;
+
+ if (!v)
+ continue;
+
+ start = mb->pos;
+
+ if (type == BFCP_ENCODE_HANDLER) {
+
+ const struct bfcp_encode *enc = v;
+
+ if (enc->ench) {
+ err = enc->ench(mb, enc->arg);
+ if (err)
+ return err;
+ }
+
+ continue;
+ }
+
+ err = attr_encode(mb, type>>7, type & 0x7f, v);
+ if (err)
+ return err;
+
+ if (subc == 0)
+ continue;
+
+ err = bfcp_attrs_vencode(mb, subc, ap);
+ if (err)
+ return err;
+
+ /* update total length for grouped attributes */
+ len = mb->pos - start;
+
+ mb->pos = start + 1;
+ err = mbuf_write_u8(mb, (uint8_t)len);
+ if (err)
+ return err;
+
+ mb->pos += (len - BFCP_ATTR_HDR_SIZE);
+ }
+
+ return 0;
+}
+
+
+/**
+ * Encode BFCP Attributes
+ *
+ * @param mb Mbuf to encode into
+ * @param attrc Number of attributes
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int bfcp_attrs_encode(struct mbuf *mb, unsigned attrc, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, attrc);
+ err = bfcp_attrs_vencode(mb, attrc, &ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+static int attr_decode(struct bfcp_attr **attrp, struct mbuf *mb,
+ struct bfcp_unknown_attr *uma)
+{
+ struct bfcp_attr *attr;
+ union bfcp_union *v;
+ size_t i, start, len;
+ int err = 0;
+ uint8_t b;
+
+ if (mbuf_get_left(mb) < BFCP_ATTR_HDR_SIZE)
+ return EBADMSG;
+
+ attr = mem_zalloc(sizeof(*attr), destructor);
+ if (!attr)
+ return ENOMEM;
+
+ start = mb->pos;
+
+ b = mbuf_read_u8(mb);
+ attr->type = b >> 1;
+ attr->mand = b & 1;
+ len = mbuf_read_u8(mb);
+
+ if (len < BFCP_ATTR_HDR_SIZE)
+ goto badmsg;
+
+ len -= BFCP_ATTR_HDR_SIZE;
+
+ if (mbuf_get_left(mb) < len)
+ goto badmsg;
+
+ v = &attr->v;
+
+ switch (attr->type) {
+
+ case BFCP_BENEFICIARY_ID:
+ case BFCP_FLOOR_ID:
+ case BFCP_FLOOR_REQUEST_ID:
+ if (len < 2)
+ goto badmsg;
+
+ v->u16 = ntohs(mbuf_read_u16(mb));
+ break;
+
+ case BFCP_PRIORITY:
+ if (len < 2)
+ goto badmsg;
+
+ v->priority = mbuf_read_u8(mb) >> 5;
+ (void)mbuf_read_u8(mb);
+ break;
+
+ case BFCP_REQUEST_STATUS:
+ if (len < 2)
+ goto badmsg;
+
+ v->reqstatus.status = mbuf_read_u8(mb);
+ v->reqstatus.qpos = mbuf_read_u8(mb);
+ break;
+
+ case BFCP_ERROR_CODE:
+ if (len < 1)
+ goto badmsg;
+
+ v->errcode.code = mbuf_read_u8(mb);
+ v->errcode.len = len - 1;
+
+ if (v->errcode.len == 0)
+ break;
+
+ v->errcode.details = mem_alloc(v->errcode.len, NULL);
+ if (!v->errcode.details) {
+ err = ENOMEM;
+ goto error;
+ }
+
+ (void)mbuf_read_mem(mb, v->errcode.details,
+ v->errcode.len);
+ break;
+
+ case BFCP_ERROR_INFO:
+ case BFCP_PART_PROV_INFO:
+ case BFCP_STATUS_INFO:
+ case BFCP_USER_DISP_NAME:
+ case BFCP_USER_URI:
+ err = mbuf_strdup(mb, &v->str, len);
+ break;
+
+ case BFCP_SUPPORTED_ATTRS:
+ v->supattr.attrc = len;
+ v->supattr.attrv = mem_alloc(len*sizeof(*v->supattr.attrv),
+ NULL);
+ if (!v->supattr.attrv) {
+ err = ENOMEM;
+ goto error;
+ }
+
+ for (i=0; i<len; i++)
+ v->supattr.attrv[i] = mbuf_read_u8(mb) >> 1;
+ break;
+
+ case BFCP_SUPPORTED_PRIMS:
+ v->supprim.primc = len;
+ v->supprim.primv = mem_alloc(len * sizeof(*v->supprim.primv),
+ NULL);
+ if (!v->supprim.primv) {
+ err = ENOMEM;
+ goto error;
+ }
+
+ for (i=0; i<len; i++)
+ v->supprim.primv[i] = mbuf_read_u8(mb);
+ break;
+
+ /* grouped attributes */
+
+ case BFCP_BENEFICIARY_INFO:
+ case BFCP_FLOOR_REQ_INFO:
+ case BFCP_REQUESTED_BY_INFO:
+ case BFCP_FLOOR_REQ_STATUS:
+ case BFCP_OVERALL_REQ_STATUS:
+ if (len < 2)
+ goto badmsg;
+
+ v->u16 = ntohs(mbuf_read_u16(mb));
+ err = bfcp_attrs_decode(&attr->attrl, mb, len - 2, uma);
+ break;
+
+ default:
+ mb->pos += len;
+
+ if (!attr->mand)
+ break;
+
+ if (uma && uma->typec < ARRAY_SIZE(uma->typev))
+ uma->typev[uma->typec++] = attr->type<<1;
+ break;
+ }
+
+ if (err)
+ goto error;
+
+ /* padding */
+ while (((mb->pos - start) & 0x03) && mbuf_get_left(mb))
+ ++mb->pos;
+
+ *attrp = attr;
+
+ return 0;
+
+ badmsg:
+ err = EBADMSG;
+ error:
+ mem_deref(attr);
+
+ return err;
+}
+
+
+int bfcp_attrs_decode(struct list *attrl, struct mbuf *mb, size_t len,
+ struct bfcp_unknown_attr *uma)
+{
+ int err = 0;
+ size_t end;
+
+ if (!attrl || !mb || mbuf_get_left(mb) < len)
+ return EINVAL;
+
+ end = mb->end;
+ mb->end = mb->pos + len;
+
+ while (mbuf_get_left(mb) >= BFCP_ATTR_HDR_SIZE) {
+
+ struct bfcp_attr *attr;
+
+ err = attr_decode(&attr, mb, uma);
+ if (err)
+ break;
+
+ list_append(attrl, &attr->le, attr);
+ }
+
+ mb->end = end;
+
+ return err;
+}
+
+
+struct bfcp_attr *bfcp_attrs_find(const struct list *attrl,
+ enum bfcp_attrib type)
+{
+ struct le *le = list_head(attrl);
+
+ while (le) {
+ struct bfcp_attr *attr = le->data;
+
+ le = le->next;
+
+ if (attr->type == type)
+ return attr;
+ }
+
+ return NULL;
+}
+
+
+struct bfcp_attr *bfcp_attrs_apply(const struct list *attrl,
+ bfcp_attr_h *h, void *arg)
+{
+ struct le *le = list_head(attrl);
+
+ while (le) {
+ struct bfcp_attr *attr = le->data;
+
+ le = le->next;
+
+ if (h && h(attr, arg))
+ return attr;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get a BFCP sub-attribute from a BFCP attribute
+ *
+ * @param attr BFCP attribute
+ * @param type Attribute type
+ *
+ * @return Matching BFCP attribute if found, otherwise NULL
+ */
+struct bfcp_attr *bfcp_attr_subattr(const struct bfcp_attr *attr,
+ enum bfcp_attrib type)
+{
+ if (!attr)
+ return NULL;
+
+ return bfcp_attrs_find(&attr->attrl, type);
+}
+
+
+/**
+ * Apply a function handler to all sub-attributes in a BFCP attribute
+ *
+ * @param attr BFCP attribute
+ * @param h Handler
+ * @param arg Handler argument
+ *
+ * @return BFCP attribute returned by handler, or NULL
+ */
+struct bfcp_attr *bfcp_attr_subattr_apply(const struct bfcp_attr *attr,
+ bfcp_attr_h *h, void *arg)
+{
+ if (!attr)
+ return NULL;
+
+ return bfcp_attrs_apply(&attr->attrl, h, arg);
+}
+
+
+/**
+ * Print a BFCP attribute
+ *
+ * @param pf Print function
+ * @param attr BFCP attribute
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int bfcp_attr_print(struct re_printf *pf, const struct bfcp_attr *attr)
+{
+ const union bfcp_union *v;
+ size_t i;
+ int err;
+
+ if (!attr)
+ return 0;
+
+ err = re_hprintf(pf, "%c%-28s", attr->mand ? '*' : ' ',
+ bfcp_attr_name(attr->type));
+
+ v = &attr->v;
+
+ switch (attr->type) {
+
+ case BFCP_BENEFICIARY_ID:
+ case BFCP_FLOOR_ID:
+ case BFCP_FLOOR_REQUEST_ID:
+ err |= re_hprintf(pf, "%u", v->u16);
+ break;
+
+ case BFCP_PRIORITY:
+ err |= re_hprintf(pf, "%d", v->priority);
+ break;
+
+ case BFCP_REQUEST_STATUS:
+ err |= re_hprintf(pf, "%s (%d), qpos=%u",
+ bfcp_reqstatus_name(v->reqstatus.status),
+ v->reqstatus.status,
+ v->reqstatus.qpos);
+ break;
+
+ case BFCP_ERROR_CODE:
+ err |= re_hprintf(pf, "%d (%s)", v->errcode.code,
+ bfcp_errcode_name(v->errcode.code));
+
+ if (v->errcode.code == BFCP_UNKNOWN_MAND_ATTR) {
+
+ for (i=0; i<v->errcode.len; i++) {
+
+ uint8_t type = v->errcode.details[i] >> 1;
+
+ err |= re_hprintf(pf, " %s",
+ bfcp_attr_name(type));
+ }
+ }
+ break;
+
+ case BFCP_ERROR_INFO:
+ case BFCP_PART_PROV_INFO:
+ case BFCP_STATUS_INFO:
+ case BFCP_USER_DISP_NAME:
+ case BFCP_USER_URI:
+ err |= re_hprintf(pf, "\"%s\"", v->str);
+ break;
+
+ case BFCP_SUPPORTED_ATTRS:
+ err |= re_hprintf(pf, "%zu:", v->supattr.attrc);
+
+ for (i=0; i<v->supattr.attrc; i++) {
+
+ const enum bfcp_attrib type = v->supattr.attrv[i];
+
+ err |= re_hprintf(pf, " %s", bfcp_attr_name(type));
+ }
+ break;
+
+ case BFCP_SUPPORTED_PRIMS:
+ err |= re_hprintf(pf, "%zu:", v->supprim.primc);
+
+ for (i=0; i<v->supprim.primc; i++) {
+
+ const enum bfcp_prim prim = v->supprim.primv[i];
+
+ err |= re_hprintf(pf, " %s", bfcp_prim_name(prim));
+ }
+ break;
+
+ /* Grouped Attributes */
+
+ case BFCP_BENEFICIARY_INFO:
+ err |= re_hprintf(pf, "beneficiary-id=%u", v->beneficiaryid);
+ break;
+
+ case BFCP_FLOOR_REQ_INFO:
+ err |= re_hprintf(pf, "floor-request-id=%u", v->floorreqid);
+ break;
+
+ case BFCP_REQUESTED_BY_INFO:
+ err |= re_hprintf(pf, "requested-by-id=%u", v->reqbyid);
+ break;
+
+ case BFCP_FLOOR_REQ_STATUS:
+ err |= re_hprintf(pf, "floor-id=%u", v->floorid);
+ break;
+
+ case BFCP_OVERALL_REQ_STATUS:
+ err |= re_hprintf(pf, "floor-request-id=%u", v->floorreqid);
+ break;
+
+ default:
+ err |= re_hprintf(pf, "???");
+ break;
+ }
+
+ return err;
+}
+
+
+int bfcp_attrs_print(struct re_printf *pf, const struct list *attrl,
+ unsigned level)
+{
+ struct le *le;
+ int err = 0;
+
+ for (le=list_head(attrl); le; le=le->next) {
+
+ const struct bfcp_attr *attr = le->data;
+ unsigned i;
+
+ for (i=0; i<level; i++)
+ err |= re_hprintf(pf, " ");
+
+ err |= re_hprintf(pf, "%H\n", bfcp_attr_print, attr);
+ err |= bfcp_attrs_print(pf, &attr->attrl, level + 1);
+ }
+
+ return err;
+}
+
+
+/**
+ * Get the BFCP attribute name
+ *
+ * @param type BFCP attribute type
+ *
+ * @return String with BFCP attribute name
+ */
+const char *bfcp_attr_name(enum bfcp_attrib type)
+{
+ switch (type) {
+
+ case BFCP_BENEFICIARY_ID: return "BENEFICIARY-ID";
+ case BFCP_FLOOR_ID: return "FLOOR-ID";
+ case BFCP_FLOOR_REQUEST_ID: return "FLOOR-REQUEST-ID";
+ case BFCP_PRIORITY: return "PRIORITY";
+ case BFCP_REQUEST_STATUS: return "REQUEST-STATUS";
+ case BFCP_ERROR_CODE: return "ERROR-CODE";
+ case BFCP_ERROR_INFO: return "ERROR-INFO";
+ case BFCP_PART_PROV_INFO: return "PARTICIPANT-PROVIDED-INFO";
+ case BFCP_STATUS_INFO: return "STATUS-INFO";
+ case BFCP_SUPPORTED_ATTRS: return "SUPPORTED-ATTRIBUTES";
+ case BFCP_SUPPORTED_PRIMS: return "SUPPORTED-PRIMITIVES";
+ case BFCP_USER_DISP_NAME: return "USER-DISPLAY-NAME";
+ case BFCP_USER_URI: return "USER-URI";
+ case BFCP_BENEFICIARY_INFO: return "BENEFICIARY-INFORMATION";
+ case BFCP_FLOOR_REQ_INFO: return "FLOOR-REQUEST-INFORMATION";
+ case BFCP_REQUESTED_BY_INFO: return "REQUESTED-BY-INFORMATION";
+ case BFCP_FLOOR_REQ_STATUS: return "FLOOR-REQUEST-STATUS";
+ case BFCP_OVERALL_REQ_STATUS: return "OVERALL-REQUEST-STATUS";
+ default: return "???";
+ }
+}
+
+
+/**
+ * Get the BFCP Request status name
+ *
+ * @param status Request status
+ *
+ * @return String with BFCP Request status name
+ */
+const char *bfcp_reqstatus_name(enum bfcp_reqstat status)
+{
+ switch (status) {
+
+ case BFCP_PENDING: return "Pending";
+ case BFCP_ACCEPTED: return "Accepted";
+ case BFCP_GRANTED: return "Granted";
+ case BFCP_DENIED: return "Denied";
+ case BFCP_CANCELLED: return "Cancelled";
+ case BFCP_RELEASED: return "Released";
+ case BFCP_REVOKED: return "Revoked";
+ default: return "???";
+ }
+}
+
+
+/**
+ * Get the BFCP Error code name
+ *
+ * @param code BFCP Error code
+ *
+ * @return String with error code
+ */
+const char *bfcp_errcode_name(enum bfcp_err code)
+{
+ switch (code) {
+
+ case BFCP_CONF_NOT_EXIST:
+ return "Conference does not Exist";
+
+ case BFCP_USER_NOT_EXIST:
+ return "User does not Exist";
+
+ case BFCP_UNKNOWN_PRIM:
+ return "Unknown Primitive";
+
+ case BFCP_UNKNOWN_MAND_ATTR:
+ return "Unknown Mandatory Attribute";
+
+ case BFCP_UNAUTH_OPERATION:
+ return "Unauthorized Operation";
+
+ case BFCP_INVALID_FLOOR_ID:
+ return "Invalid Floor ID";
+
+ case BFCP_FLOOR_REQ_ID_NOT_EXIST:
+ return "Floor Request ID Does Not Exist";
+
+ case BFCP_MAX_FLOOR_REQ_REACHED:
+ return "You have Already Reached the Maximum Number "
+ "of Ongoing Floor Requests for this Floor";
+
+ case BFCP_USE_TLS:
+ return "Use TLS";
+
+ case BFCP_PARSE_ERROR:
+ return "Unable to Parse Message";
+
+ case BFCP_USE_DTLS:
+ return "Use DTLS";
+
+ case BFCP_UNSUPPORTED_VERSION:
+ return "Unsupported Version";
+
+ case BFCP_BAD_LENGTH:
+ return "Incorrect Message Length";
+
+ case BFCP_GENERIC_ERROR:
+ return "Generic Error";
+
+ default:
+ return "???";
+ }
+}
diff --git a/src/bfcp/bfcp.h b/src/bfcp/bfcp.h
new file mode 100644
index 0000000..7e76e0d
--- /dev/null
+++ b/src/bfcp/bfcp.h
@@ -0,0 +1,48 @@
+/**
+ * @file bfcp.h Internal interface to Binary Floor Control Protocol (BFCP)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+struct bfcp_strans {
+ enum bfcp_prim prim;
+ uint32_t confid;
+ uint16_t tid;
+ uint16_t userid;
+};
+
+struct bfcp_conn {
+ struct bfcp_strans st;
+ struct list ctransl;
+ struct tmr tmr1;
+ struct tmr tmr2;
+ struct udp_sock *us;
+ struct mbuf *mb;
+ bfcp_recv_h *recvh;
+ void *arg;
+ enum bfcp_transp tp;
+ unsigned txc;
+ uint16_t tid;
+};
+
+
+/* attributes */
+int bfcp_attrs_decode(struct list *attrl, struct mbuf *mb, size_t len,
+ struct bfcp_unknown_attr *uma);
+struct bfcp_attr *bfcp_attrs_find(const struct list *attrl,
+ enum bfcp_attrib type);
+struct bfcp_attr *bfcp_attrs_apply(const struct list *attrl,
+ bfcp_attr_h *h, void *arg);
+int bfcp_attrs_print(struct re_printf *pf, const struct list *attrl,
+ unsigned level);
+
+
+/* connection */
+int bfcp_send(struct bfcp_conn *bc, const struct sa *dst, struct mbuf *mb);
+
+
+/* request */
+bool bfcp_handle_response(struct bfcp_conn *bc, const struct bfcp_msg *msg);
+int bfcp_vrequest(struct bfcp_conn *bc, const struct sa *dst, uint8_t ver,
+ enum bfcp_prim prim, uint32_t confid, uint16_t userid,
+ bfcp_resp_h *resph, void *arg, unsigned attrc, va_list *ap);
diff --git a/src/bfcp/conn.c b/src/bfcp/conn.c
new file mode 100644
index 0000000..6e4051d
--- /dev/null
+++ b/src/bfcp/conn.c
@@ -0,0 +1,156 @@
+/**
+ * @file bfcp/conn.c BFCP Connection
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tmr.h>
+#include <re_bfcp.h>
+#include "bfcp.h"
+
+
+static void destructor(void *arg)
+{
+ struct bfcp_conn *bc = arg;
+
+ list_flush(&bc->ctransl);
+ tmr_cancel(&bc->tmr1);
+ tmr_cancel(&bc->tmr2);
+ mem_deref(bc->us);
+ mem_deref(bc->mb);
+}
+
+
+static bool strans_cmp(const struct bfcp_strans *st,
+ const struct bfcp_msg *msg)
+{
+ if (st->tid != msg->tid)
+ return false;
+
+ if (st->prim != msg->prim)
+ return false;
+
+ if (st->confid != msg->confid)
+ return false;
+
+ if (st->userid != msg->userid)
+ return false;
+
+ return true;
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct bfcp_conn *bc = arg;
+ struct bfcp_msg *msg;
+ int err;
+
+ err = bfcp_msg_decode(&msg, mb);
+ if (err)
+ return;
+
+ msg->src = *src;
+
+ if (bfcp_handle_response(bc, msg))
+ goto out;
+
+ if (bc->mb && strans_cmp(&bc->st, msg)) {
+ (void)bfcp_send(bc, &msg->src, bc->mb);
+ goto out;
+ }
+
+ if (bc->recvh)
+ bc->recvh(msg, bc->arg);
+
+out:
+ mem_deref(msg);
+}
+
+
+/**
+ * Create BFCP connection
+ *
+ * @param bcp Pointer to BFCP connection
+ * @param tp BFCP Transport type
+ * @param laddr Optional listening address/port
+ * @param tls TLS Context (optional)
+ * @param recvh Receive handler
+ * @param arg Receive handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int bfcp_listen(struct bfcp_conn **bcp, enum bfcp_transp tp, struct sa *laddr,
+ struct tls *tls, bfcp_recv_h *recvh, void *arg)
+{
+ struct bfcp_conn *bc;
+ int err;
+ (void)tls;
+
+ if (!bcp)
+ return EINVAL;
+
+ bc = mem_zalloc(sizeof(*bc), destructor);
+ if (!bc)
+ return ENOMEM;
+
+ bc->tp = tp;
+ bc->recvh = recvh;
+ bc->arg = arg;
+
+ switch (bc->tp) {
+
+ case BFCP_UDP:
+ err = udp_listen(&bc->us, laddr, udp_recv_handler, bc);
+ if (err)
+ goto out;
+
+ if (laddr) {
+ err = udp_local_get(bc->us, laddr);
+ if (err)
+ goto out;
+ }
+ break;
+
+ default:
+ err = ENOSYS;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(bc);
+ else
+ *bcp = bc;
+
+ return err;
+}
+
+
+int bfcp_send(struct bfcp_conn *bc, const struct sa *dst, struct mbuf *mb)
+{
+ if (!bc || !dst || !mb)
+ return EINVAL;
+
+ switch (bc->tp) {
+
+ case BFCP_UDP:
+ return udp_send(bc->us, dst, mb);
+
+ default:
+ return ENOSYS;
+ }
+}
+
+
+void *bfcp_sock(const struct bfcp_conn *bc)
+{
+ return bc ? bc->us : NULL;
+}
diff --git a/src/bfcp/mod.mk b/src/bfcp/mod.mk
new file mode 100644
index 0000000..b533415
--- /dev/null
+++ b/src/bfcp/mod.mk
@@ -0,0 +1,11 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += bfcp/attr.c
+SRCS += bfcp/conn.c
+SRCS += bfcp/msg.c
+SRCS += bfcp/reply.c
+SRCS += bfcp/request.c
diff --git a/src/bfcp/msg.c b/src/bfcp/msg.c
new file mode 100644
index 0000000..03f0b72
--- /dev/null
+++ b/src/bfcp/msg.c
@@ -0,0 +1,288 @@
+/**
+ * @file bfcp/msg.c BFCP Message
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_tmr.h>
+#include <re_bfcp.h>
+#include "bfcp.h"
+
+
+enum {
+ BFCP_HDR_SIZE = 12,
+};
+
+
+static void destructor(void *arg)
+{
+ struct bfcp_msg *msg = arg;
+
+ list_flush(&msg->attrl);
+}
+
+
+static int hdr_encode(struct mbuf *mb, uint8_t ver, bool r,
+ enum bfcp_prim prim, uint16_t len, uint32_t confid,
+ uint16_t tid, uint16_t userid)
+{
+ int err;
+
+ err = mbuf_write_u8(mb, (ver << 5) | ((r ? 1 : 0) << 4));
+ err |= mbuf_write_u8(mb, prim);
+ err |= mbuf_write_u16(mb, htons(len));
+ err |= mbuf_write_u32(mb, htonl(confid));
+ err |= mbuf_write_u16(mb, htons(tid));
+ err |= mbuf_write_u16(mb, htons(userid));
+
+ return err;
+}
+
+
+static int hdr_decode(struct bfcp_msg *msg, struct mbuf *mb)
+{
+ uint8_t b;
+
+ if (mbuf_get_left(mb) < BFCP_HDR_SIZE)
+ return ENODATA;
+
+ b = mbuf_read_u8(mb);
+
+ msg->ver = b >> 5;
+ msg->r = (b >> 4) & 1;
+ msg->f = (b >> 3) & 1;
+ msg->prim = mbuf_read_u8(mb);
+ msg->len = ntohs(mbuf_read_u16(mb));
+ msg->confid = ntohl(mbuf_read_u32(mb));
+ msg->tid = ntohs(mbuf_read_u16(mb));
+ msg->userid = ntohs(mbuf_read_u16(mb));
+
+ if (msg->ver != BFCP_VER1 && msg->ver != BFCP_VER2)
+ return EBADMSG;
+
+ /* fragmentation not supported */
+ if (msg->f)
+ return ENOSYS;
+
+ if (mbuf_get_left(mb) < (size_t)(4*msg->len))
+ return ENODATA;
+
+ return 0;
+}
+
+
+/**
+ * Encode a BFCP message with variable arguments
+ *
+ * @param mb Mbuf to encode into
+ * @param ver Protocol version
+ * @param r Transaction responder flag
+ * @param prim BFCP Primitive
+ * @param confid Conference ID
+ * @param tid Transaction ID
+ * @param userid User ID
+ * @param attrc Number of attributes
+ * @param ap Variable argument of attributes
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int bfcp_msg_vencode(struct mbuf *mb, uint8_t ver, bool r, enum bfcp_prim prim,
+ uint32_t confid, uint16_t tid, uint16_t userid,
+ unsigned attrc, va_list *ap)
+{
+ size_t start, len;
+ int err;
+
+ if (!mb)
+ return EINVAL;
+
+ start = mb->pos;
+ mb->pos += BFCP_HDR_SIZE;
+
+ err = bfcp_attrs_vencode(mb, attrc, ap);
+ if (err)
+ return err;
+
+ /* header */
+ len = mb->pos - start - BFCP_HDR_SIZE;
+ mb->pos = start;
+ err = hdr_encode(mb, ver, r, prim, (uint16_t)(len/4), confid, tid,
+ userid);
+ mb->pos += len;
+
+ return err;
+}
+
+
+/**
+ * Encode a BFCP message
+ *
+ * @param mb Mbuf to encode into
+ * @param ver Protocol version
+ * @param r Transaction responder flag
+ * @param prim BFCP Primitive
+ * @param confid Conference ID
+ * @param tid Transaction ID
+ * @param userid User ID
+ * @param attrc Number of attributes
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int bfcp_msg_encode(struct mbuf *mb, uint8_t ver, bool r, enum bfcp_prim prim,
+ uint32_t confid, uint16_t tid, uint16_t userid,
+ unsigned attrc, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, attrc);
+ err = bfcp_msg_vencode(mb, ver, r, prim, confid, tid, userid,
+ attrc, &ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Decode a BFCP message from a buffer
+ *
+ * @param msgp Pointer to allocated and decoded BFCP message
+ * @param mb Mbuf to decode from
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int bfcp_msg_decode(struct bfcp_msg **msgp, struct mbuf *mb)
+{
+ struct bfcp_msg *msg;
+ size_t start;
+ int err;
+
+ if (!msgp || !mb)
+ return EINVAL;
+
+ msg = mem_zalloc(sizeof(*msg), destructor);
+ if (!msg)
+ return ENOMEM;
+
+ start = mb->pos;
+
+ err = hdr_decode(msg, mb);
+ if (err) {
+ mb->pos = start;
+ goto out;
+ }
+
+ err = bfcp_attrs_decode(&msg->attrl, mb, 4*msg->len, &msg->uma);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(msg);
+ else
+ *msgp = msg;
+
+ return err;
+}
+
+
+/**
+ * Get a BFCP attribute from a BFCP message
+ *
+ * @param msg BFCP message
+ * @param type Attribute type
+ *
+ * @return Matching BFCP attribute if found, otherwise NULL
+ */
+struct bfcp_attr *bfcp_msg_attr(const struct bfcp_msg *msg,
+ enum bfcp_attrib type)
+{
+ if (!msg)
+ return NULL;
+
+ return bfcp_attrs_find(&msg->attrl, type);
+}
+
+
+/**
+ * Apply a function handler to all attributes in a BFCP message
+ *
+ * @param msg BFCP message
+ * @param h Handler
+ * @param arg Handler argument
+ *
+ * @return BFCP attribute returned by handler, or NULL
+ */
+struct bfcp_attr *bfcp_msg_attr_apply(const struct bfcp_msg *msg,
+ bfcp_attr_h *h, void *arg)
+{
+ if (!msg)
+ return NULL;
+
+ return bfcp_attrs_apply(&msg->attrl, h, arg);
+}
+
+
+/**
+ * Print a BFCP message
+ *
+ * @param pf Print function
+ * @param msg BFCP message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int bfcp_msg_print(struct re_printf *pf, const struct bfcp_msg *msg)
+{
+ int err;
+
+ if (!msg)
+ return 0;
+
+ err = re_hprintf(pf, "%s (confid=%u tid=%u userid=%u)\n",
+ bfcp_prim_name(msg->prim), msg->confid,
+ msg->tid, msg->userid);
+
+ err |= bfcp_attrs_print(pf, &msg->attrl, 0);
+
+ return err;
+}
+
+
+/**
+ * Get the BFCP primitive name
+ *
+ * @param prim BFCP primitive
+ *
+ * @return String with BFCP primitive name
+ */
+const char *bfcp_prim_name(enum bfcp_prim prim)
+{
+ switch (prim) {
+
+ case BFCP_FLOOR_REQUEST: return "FloorRequest";
+ case BFCP_FLOOR_RELEASE: return "FloorRelease";
+ case BFCP_FLOOR_REQUEST_QUERY: return "FloorRequestQuery";
+ case BFCP_FLOOR_REQUEST_STATUS: return "FloorRequestStatus";
+ case BFCP_USER_QUERY: return "UserQuery";
+ case BFCP_USER_STATUS: return "UserStatus";
+ case BFCP_FLOOR_QUERY: return "FloorQuery";
+ case BFCP_FLOOR_STATUS: return "FloorStatus";
+ case BFCP_CHAIR_ACTION: return "ChairAction";
+ case BFCP_CHAIR_ACTION_ACK: return "ChairActionAck";
+ case BFCP_HELLO: return "Hello";
+ case BFCP_HELLO_ACK: return "HelloAck";
+ case BFCP_ERROR: return "Error";
+ case BFCP_FLOOR_REQ_STATUS_ACK: return "FloorRequestStatusAck";
+ case BFCP_FLOOR_STATUS_ACK: return "FloorStatusAck";
+ case BFCP_GOODBYE: return "Goodbye";
+ case BFCP_GOODBYE_ACK: return "GoodbyeAck";
+ default: return "???";
+ }
+}
diff --git a/src/bfcp/reply.c b/src/bfcp/reply.c
new file mode 100644
index 0000000..321429c
--- /dev/null
+++ b/src/bfcp/reply.c
@@ -0,0 +1,124 @@
+/**
+ * @file bfcp/reply.c BFCP Reply
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_tmr.h>
+#include <re_bfcp.h>
+#include "bfcp.h"
+
+
+enum {
+ BFCP_T2 = 10000,
+};
+
+
+static void tmr_handler(void *arg)
+{
+ struct bfcp_conn *bc = arg;
+
+ bc->mb = mem_deref(bc->mb);
+}
+
+
+/**
+ * Send a BFCP response
+ *
+ * @param bc BFCP connection
+ * @param req BFCP request message
+ * @param prim BFCP Primitive
+ * @param attrc Number of attributes
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int bfcp_reply(struct bfcp_conn *bc, const struct bfcp_msg *req,
+ enum bfcp_prim prim, unsigned attrc, ...)
+{
+ va_list ap;
+ int err;
+
+ if (!bc || !req)
+ return EINVAL;
+
+ bc->mb = mem_deref(bc->mb);
+ tmr_cancel(&bc->tmr2);
+
+ bc->mb = mbuf_alloc(64);
+ if (!bc->mb)
+ return ENOMEM;
+
+ va_start(ap, attrc);
+ err = bfcp_msg_vencode(bc->mb, req->ver, true, prim, req->confid,
+ req->tid, req->userid, attrc, &ap);
+ va_end(ap);
+
+ if (err)
+ goto out;
+
+ bc->mb->pos = 0;
+
+ err = bfcp_send(bc, &req->src, bc->mb);
+ if (err)
+ goto out;
+
+ bc->st.prim = req->prim;
+ bc->st.confid = req->confid;
+ bc->st.tid = req->tid;
+ bc->st.userid = req->userid;
+
+ tmr_start(&bc->tmr2, BFCP_T2, tmr_handler, bc);
+
+ out:
+ if (err)
+ bc->mb = mem_deref(bc->mb);
+
+ return err;
+}
+
+
+/**
+ * Send a BFCP error response with details
+ *
+ * @param bc BFCP connection
+ * @param req BFCP request message
+ * @param code Error code
+ * @param details Error details
+ * @param len Details length
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int bfcp_edreply(struct bfcp_conn *bc, const struct bfcp_msg *req,
+ enum bfcp_err code, const uint8_t *details, size_t len)
+{
+ struct bfcp_errcode errcode;
+
+ errcode.code = code;
+ errcode.details = (uint8_t *)details;
+ errcode.len = len;
+
+ return bfcp_reply(bc, req, BFCP_ERROR, 1,
+ BFCP_ERROR_CODE, 0, &errcode);
+}
+
+
+/**
+ * Send a BFCP error response
+ *
+ * @param bc BFCP connection
+ * @param req BFCP request message
+ * @param code Error code
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int bfcp_ereply(struct bfcp_conn *bc, const struct bfcp_msg *req,
+ enum bfcp_err code)
+{
+ return bfcp_edreply(bc, req, code, NULL, 0);
+}
diff --git a/src/bfcp/request.c b/src/bfcp/request.c
new file mode 100644
index 0000000..cfe066e
--- /dev/null
+++ b/src/bfcp/request.c
@@ -0,0 +1,257 @@
+/**
+ * @file bfcp/request.c BFCP Request
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_tmr.h>
+#include <re_bfcp.h>
+#include "bfcp.h"
+
+
+enum {
+ BFCP_T1 = 500,
+ BFCP_TXC = 4,
+};
+
+
+struct bfcp_ctrans {
+ struct le le;
+ struct sa dst;
+ struct mbuf *mb;
+ bfcp_resp_h *resph;
+ void *arg;
+ uint32_t confid;
+ uint16_t userid;
+ uint16_t tid;
+};
+
+
+static void tmr_handler(void *arg);
+
+
+static void dummy_resp_handler(int err, const struct bfcp_msg *msg, void *arg)
+{
+ (void)err;
+ (void)msg;
+ (void)arg;
+}
+
+
+static void destructor(void *arg)
+{
+ struct bfcp_ctrans *ct = arg;
+
+ list_unlink(&ct->le);
+ mem_deref(ct->mb);
+}
+
+
+static void dispatch(struct bfcp_conn *bc)
+{
+ struct le *le = bc->ctransl.head;
+
+ while (le) {
+ struct bfcp_ctrans *ct = le->data;
+ int err;
+
+ le = le->next;
+
+ err = bfcp_send(bc, &ct->dst, ct->mb);
+ if (err) {
+ ct->resph(err, NULL, ct->arg);
+ mem_deref(ct);
+ continue;
+ }
+
+ tmr_start(&bc->tmr1, BFCP_T1, tmr_handler, bc);
+ bc->txc = 1;
+ break;
+ }
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct bfcp_conn *bc = arg;
+ struct bfcp_ctrans *ct;
+ uint32_t timeout;
+ int err;
+
+ ct = list_ledata(bc->ctransl.head);
+ if (!ct)
+ return;
+
+ timeout = BFCP_T1<<bc->txc;
+
+ if (++bc->txc > BFCP_TXC) {
+ err = ETIMEDOUT;
+ goto out;
+ }
+
+ err = bfcp_send(bc, &ct->dst, ct->mb);
+ if (err)
+ goto out;
+
+ tmr_start(&bc->tmr1, timeout, tmr_handler, bc);
+ return;
+
+ out:
+ ct->resph(err, NULL, ct->arg);
+ mem_deref(ct);
+ dispatch(bc);
+}
+
+
+bool bfcp_handle_response(struct bfcp_conn *bc, const struct bfcp_msg *msg)
+{
+ struct bfcp_ctrans *ct;
+
+ if (!bc || !msg)
+ return false;
+
+ ct = list_ledata(bc->ctransl.head);
+ if (!ct)
+ return false;
+
+ if (msg->tid != ct->tid)
+ return false;
+
+ if (msg->confid != ct->confid)
+ return false;
+
+ if (msg->userid != ct->userid)
+ return false;
+
+ tmr_cancel(&bc->tmr1);
+
+ ct->resph(0, msg, ct->arg);
+ mem_deref(ct);
+
+ dispatch(bc);
+
+ return true;
+}
+
+
+int bfcp_vrequest(struct bfcp_conn *bc, const struct sa *dst, uint8_t ver,
+ enum bfcp_prim prim, uint32_t confid, uint16_t userid,
+ bfcp_resp_h *resph, void *arg, unsigned attrc, va_list *ap)
+{
+ struct bfcp_ctrans *ct;
+ int err;
+
+ if (!bc || !dst)
+ return EINVAL;
+
+ ct = mem_zalloc(sizeof(*ct), destructor);
+ if (!ct)
+ return ENOMEM;
+
+ if (bc->tid == 0)
+ bc->tid = 1;
+
+ ct->dst = *dst;
+ ct->confid = confid;
+ ct->userid = userid;
+ ct->tid = bc->tid++;
+ ct->resph = resph ? resph : dummy_resp_handler;
+ ct->arg = arg;
+
+ ct->mb = mbuf_alloc(128);
+ if (!ct->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = bfcp_msg_vencode(ct->mb, ver, false, prim, confid, ct->tid,
+ userid, attrc, ap);
+ if (err)
+ goto out;
+
+ ct->mb->pos = 0;
+
+ if (!bc->ctransl.head) {
+
+ err = bfcp_send(bc, &ct->dst, ct->mb);
+ if (err)
+ goto out;
+
+ tmr_start(&bc->tmr1, BFCP_T1, tmr_handler, bc);
+ bc->txc = 1;
+ }
+
+ list_append(&bc->ctransl, &ct->le, ct);
+
+ out:
+ if (err)
+ mem_deref(ct);
+
+ return err;
+}
+
+
+/**
+ * Send a BFCP request
+ *
+ * @param bc BFCP connection
+ * @param dst Destination address
+ * @param ver BFCP Version
+ * @param prim BFCP Primitive
+ * @param confid Conference ID
+ * @param userid User ID
+ * @param resph Response handler
+ * @param arg Response handler argument
+ * @param attrc Number of attributes
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int bfcp_request(struct bfcp_conn *bc, const struct sa *dst, uint8_t ver,
+ enum bfcp_prim prim, uint32_t confid, uint16_t userid,
+ bfcp_resp_h *resph, void *arg, unsigned attrc, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, attrc);
+ err = bfcp_vrequest(bc, dst, ver, prim, confid, userid, resph, arg,
+ attrc, &ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Send a BFCP notification/subsequent response
+ *
+ * @param bc BFCP connection
+ * @param dst Destination address
+ * @param ver BFCP Version
+ * @param prim BFCP Primitive
+ * @param confid Conference ID
+ * @param userid User ID
+ * @param attrc Number of attributes
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int bfcp_notify(struct bfcp_conn *bc, const struct sa *dst, uint8_t ver,
+ enum bfcp_prim prim, uint32_t confid, uint16_t userid,
+ unsigned attrc, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, attrc);
+ err = bfcp_vrequest(bc, dst, ver, prim, confid, userid, NULL, NULL,
+ attrc, &ap);
+ va_end(ap);
+
+ return err;
+}
diff --git a/src/conf/conf.c b/src/conf/conf.c
new file mode 100644
index 0000000..6c800a8
--- /dev/null
+++ b/src/conf/conf.c
@@ -0,0 +1,294 @@
+/**
+ * @file conf.c Configuration file parser
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_IO_H
+#include <io.h>
+#endif
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_conf.h>
+
+
+#ifdef WIN32
+#define open _open
+#define read _read
+#define close _close
+#endif
+
+
+/**
+ * Defines a Configuration state. The configuration data is stored in a
+ * linear buffer which can be used for reading key-value pairs of
+ * configuration data. The config data can be strings or numeric values.
+ */
+struct conf {
+ struct mbuf *mb;
+};
+
+
+static int load_file(struct mbuf *mb, const char *filename)
+{
+ int err = 0, fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return errno;
+
+ for (;;) {
+ uint8_t buf[1024];
+
+ const ssize_t n = read(fd, (void *)buf, sizeof(buf));
+ if (n < 0) {
+ err = errno;
+ break;
+ }
+ else if (n == 0)
+ break;
+
+ err |= mbuf_write_mem(mb, buf, n);
+ }
+
+ (void)close(fd);
+
+ return err;
+}
+
+
+static void conf_destructor(void *data)
+{
+ struct conf *conf = data;
+
+ mem_deref(conf->mb);
+}
+
+
+/**
+ * Load configuration from file
+ *
+ * @param confp Configuration object to be allocated
+ * @param filename Name of configuration file
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_alloc(struct conf **confp, const char *filename)
+{
+ struct conf *conf;
+ int err = 0;
+
+ if (!confp)
+ return EINVAL;
+
+ conf = mem_zalloc(sizeof(*conf), conf_destructor);
+ if (!conf)
+ return ENOMEM;
+
+ conf->mb = mbuf_alloc(1024);
+ if (!conf->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err |= mbuf_write_u8(conf->mb, '\n');
+ if (filename)
+ err |= load_file(conf->mb, filename);
+
+ out:
+ if (err)
+ mem_deref(conf);
+ else
+ *confp = conf;
+
+ return err;
+}
+
+
+/**
+ * Allocate configuration from a buffer
+ *
+ * @param confp Configuration object to be allocated
+ * @param buf Buffer containing configuration
+ * @param sz Size of configuration buffer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_alloc_buf(struct conf **confp, const uint8_t *buf, size_t sz)
+{
+ struct conf *conf;
+ int err;
+
+ err = conf_alloc(&conf, NULL);
+ if (err)
+ return err;
+
+ err = mbuf_write_mem(conf->mb, buf, sz);
+
+ if (err)
+ mem_deref(conf);
+ else
+ *confp = conf;
+
+ return err;
+}
+
+
+/**
+ * Get the value of a configuration item PL string
+ *
+ * @param conf Configuration object
+ * @param name Name of config item key
+ * @param pl Value of config item, if present
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_get(const struct conf *conf, const char *name, struct pl *pl)
+{
+ char expr[512];
+ struct pl spl;
+
+ if (!conf || !name || !pl)
+ return EINVAL;
+
+ spl.p = (const char *)conf->mb->buf;
+ spl.l = conf->mb->end;
+
+ (void)re_snprintf(expr, sizeof(expr),
+ "[\r\n]+[ \t]*%s[ \t]+[~ \t\r\n]+", name);
+
+ return re_regex(spl.p, spl.l, expr, NULL, NULL, NULL, pl);
+}
+
+
+/**
+ * Get the value of a configuration item string
+ *
+ * @param conf Configuration object
+ * @param name Name of config item key
+ * @param str Value of config item, if present
+ * @param size Size of string to store value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_get_str(const struct conf *conf, const char *name, char *str,
+ size_t size)
+{
+ struct pl pl;
+ int err;
+
+ if (!conf || !name || !str || !size)
+ return EINVAL;
+
+ err = conf_get(conf, name, &pl);
+ if (err)
+ return err;
+
+ return pl_strcpy(&pl, str, size);
+}
+
+
+/**
+ * Get the numeric value of a configuration item
+ *
+ * @param conf Configuration object
+ * @param name Name of config item key
+ * @param num Returned numeric value of config item, if present
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_get_u32(const struct conf *conf, const char *name, uint32_t *num)
+{
+ struct pl pl;
+ int err;
+
+ if (!conf || !name || !num)
+ return EINVAL;
+
+ err = conf_get(conf, name, &pl);
+ if (err)
+ return err;
+
+ *num = pl_u32(&pl);
+
+ return 0;
+}
+
+
+/**
+ * Get the boolean value of a configuration item
+ *
+ * @param conf Configuration object
+ * @param name Name of config item key
+ * @param val Returned boolean value of config item, if present
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_get_bool(const struct conf *conf, const char *name, bool *val)
+{
+ struct pl pl;
+ int err;
+
+ if (!conf || !name || !val)
+ return EINVAL;
+
+ err = conf_get(conf, name, &pl);
+ if (err)
+ return err;
+
+ if (!pl_strcasecmp(&pl, "true"))
+ *val = true;
+ else if (!pl_strcasecmp(&pl, "yes"))
+ *val = true;
+ else if (!pl_strcasecmp(&pl, "1"))
+ *val = true;
+ else
+ *val = false;
+
+ return 0;
+}
+
+
+/**
+ * Apply a function handler to all config items of a certain key
+ *
+ * @param conf Configuration object
+ * @param name Name of config item key
+ * @param ch Config item handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int conf_apply(const struct conf *conf, const char *name,
+ conf_h *ch, void *arg)
+{
+ char expr[512];
+ struct pl pl, val;
+ int err = 0;
+
+ if (!conf || !name || !ch)
+ return EINVAL;
+
+ pl.p = (const char *)conf->mb->buf;
+ pl.l = conf->mb->end;
+
+ (void)re_snprintf(expr, sizeof(expr),
+ "[\r\n]+[ \t]*%s[ \t]+[~ \t\r\n]+", name);
+
+ while (!re_regex(pl.p, pl.l, expr, NULL, NULL, NULL, &val)) {
+
+ err = ch(&val, arg);
+ if (err)
+ break;
+
+ pl.l -= val.p + val.l - pl.p;
+ pl.p = val.p + val.l;
+ }
+
+ return err;
+}
diff --git a/src/conf/mod.mk b/src/conf/mod.mk
new file mode 100644
index 0000000..01b0999
--- /dev/null
+++ b/src/conf/mod.mk
@@ -0,0 +1,7 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += conf/conf.c
diff --git a/src/crc32/crc32.c b/src/crc32/crc32.c
new file mode 100644
index 0000000..db8a65e
--- /dev/null
+++ b/src/crc32/crc32.c
@@ -0,0 +1,117 @@
+/**
+ * @file crc32.c CRC32 Implementation
+ *
+ * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or
+ * code or tables extracted from it, as desired without restriction.
+ */
+
+/*
+ * First, the polynomial itself and its table of feedback terms. The
+ * polynomial is
+ * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0
+ *
+ * Note that we take it "backwards" and put the highest-order term in
+ * the lowest-order bit. The X^32 term is "implied"; the LSB is the
+ * X^31 term, etc. The X^0 term (usually shown as "+1") results in
+ * the MSB being 1
+ *
+ * Note that the usual hardware shift register implementation, which
+ * is what we're using (we're merely optimizing it by doing eight-bit
+ * chunks at a time) shifts bits into the lowest-order term. In our
+ * implementation, that means shifting towards the right. Why do we
+ * do it this way? Because the calculated CRC must be transmitted in
+ * order from highest-order term to lowest-order term. UARTs transmit
+ * characters in order from LSB to MSB. By storing the CRC this way
+ * we hand it to the UART in the order low-byte to high-byte; the UART
+ * sends each low-bit to hight-bit; and the result is transmission bit
+ * by bit from highest- to lowest-order term without requiring any bit
+ * shuffling on our part. Reception works similarly
+ *
+ * The feedback terms table consists of 256, 32-bit entries. Notes
+ *
+ * The table can be generated at runtime if desired; code to do so
+ * is shown later. It might not be obvious, but the feedback
+ * terms simply represent the results of eight shift/xor opera
+ * tions for all combinations of data and CRC register values
+ *
+ * The values must be right-shifted by eight bits by the "updcrc
+ * logic; the shift must be unsigned (bring in zeroes). On some
+ * hardware you could probably optimize the shift in assembler by
+ * using byte-swap instructions
+ * polynomial $edb88320
+ *
+ *
+ * CRC32 code derived from work by Gary S. Brown.
+ */
+#include <re_types.h>
+#include <re_crc32.h>
+
+
+static const uint32_t crc32_tab[] = {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+ 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+ 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+ 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+ 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+ 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+ 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+ 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+ 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+ 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+ 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+ 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+ 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+ 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+ 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+ 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+ 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+ 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+ 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+ 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+ 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+ 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+
+/**
+ * A function that calculates the CRC-32 based on the table above is
+ * given below for documentation purposes. An equivalent implementation
+ * of this function that's actually used in the kernel can be found
+ * in sys/libkern.h, where it can be inlined.
+ *
+ * @param crc Initial CRC value
+ * @param buf Buffer to generate CRC from
+ * @param size Number of bytes in buffer
+ *
+ * @return CRC value
+ */
+uint32_t crc32(uint32_t crc, const void *buf, uint32_t size)
+{
+ const uint8_t *p = buf;
+
+ crc = ~crc;
+ while (size--)
+ crc = crc32_tab[(crc ^ *p++) & 0xff] ^ (crc >> 8);
+ return crc ^ ~0U;
+}
diff --git a/src/crc32/mod.mk b/src/crc32/mod.mk
new file mode 100644
index 0000000..528bb29
--- /dev/null
+++ b/src/crc32/mod.mk
@@ -0,0 +1,9 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+ifeq ($(USE_ZLIB),)
+SRCS += crc32/crc32.c
+endif
diff --git a/src/dbg/dbg.c b/src/dbg/dbg.c
new file mode 100644
index 0000000..52e7e67
--- /dev/null
+++ b/src/dbg/dbg.c
@@ -0,0 +1,328 @@
+/**
+ * @file dbg.c Debug printing
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_PTHREAD
+#include <stdlib.h>
+#include <pthread.h>
+#endif
+#include <time.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_tmr.h>
+
+
+#define DEBUG_MODULE "dbg"
+#define DEBUG_LEVEL 0
+#include <re_dbg.h>
+
+
+/** Debug configuration */
+static struct {
+ uint64_t tick; /**< Init ticks */
+ int level; /**< Current debug level */
+ enum dbg_flags flags; /**< Debug flags */
+ dbg_print_h *ph; /**< Optional print handler */
+ void *arg; /**< Handler argument */
+ FILE *f; /**< Logfile */
+#ifdef HAVE_PTHREAD
+ pthread_mutex_t mutex; /**< Thread locking */
+#endif
+} dbg = {
+ 0,
+ DBG_INFO,
+ DBG_ANSI,
+ NULL,
+ NULL,
+ NULL,
+#ifdef HAVE_PTHREAD
+ PTHREAD_MUTEX_INITIALIZER,
+#endif
+};
+
+
+#ifdef HAVE_PTHREAD
+static inline void dbg_lock(void)
+{
+ pthread_mutex_lock(&dbg.mutex);
+}
+
+
+static inline void dbg_unlock(void)
+{
+ pthread_mutex_unlock(&dbg.mutex);
+}
+#else
+#define dbg_lock() /**< Stub */
+#define dbg_unlock() /**< Stub */
+#endif
+
+
+/**
+ * Initialise debug printing
+ *
+ * @param level Debug level
+ * @param flags Debug flags
+ */
+void dbg_init(int level, enum dbg_flags flags)
+{
+ dbg.tick = tmr_jiffies();
+ dbg.level = level;
+ dbg.flags = flags;
+}
+
+
+/**
+ * Close debugging
+ */
+void dbg_close(void)
+{
+ if (dbg.f) {
+ (void)fclose(dbg.f);
+ dbg.f = NULL;
+ }
+}
+
+
+/**
+ * Set debug logfile
+ *
+ * @param name Name of the logfile, NULL to close
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dbg_logfile_set(const char *name)
+{
+ time_t t;
+
+ dbg_close();
+
+ if (!name)
+ return 0;
+
+ dbg.f = fopen(name, "a+");
+ if (!dbg.f)
+ return errno;
+
+ (void)time(&t);
+ (void)re_fprintf(dbg.f, "\n===== Log Started: %s", ctime(&t));
+ (void)fflush(dbg.f);
+
+ return 0;
+}
+
+
+/**
+ * Set optional debug print handler
+ *
+ * @param ph Print handler
+ * @param arg Handler argument
+ */
+void dbg_handler_set(dbg_print_h *ph, void *arg)
+{
+ dbg.ph = ph;
+ dbg.arg = arg;
+}
+
+
+/* NOTE: This function should not allocate memory */
+static void dbg_vprintf(int level, const char *fmt, va_list ap)
+{
+ if (level > dbg.level)
+ return;
+
+ /* Print handler? */
+ if (dbg.ph)
+ return;
+
+ dbg_lock();
+
+ if (dbg.flags & DBG_ANSI) {
+
+ switch (level) {
+
+ case DBG_WARNING:
+ (void)re_fprintf(stderr, "\x1b[31m"); /* Red */
+ break;
+
+ case DBG_NOTICE:
+ (void)re_fprintf(stderr, "\x1b[33m"); /* Yellow */
+ break;
+
+ case DBG_INFO:
+ (void)re_fprintf(stderr, "\x1b[32m"); /* Green */
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (dbg.flags & DBG_TIME) {
+ const uint64_t ticks = tmr_jiffies();
+
+ if (0 == dbg.tick)
+ dbg.tick = tmr_jiffies();
+
+ (void)re_fprintf(stderr, "[%09llu] ", ticks - dbg.tick);
+ }
+
+ (void)re_vfprintf(stderr, fmt, ap);
+
+ if (dbg.flags & DBG_ANSI && level < DBG_DEBUG)
+ (void)re_fprintf(stderr, "\x1b[;m");
+
+ dbg_unlock();
+}
+
+
+/* Formatted output to print handler and/or logfile */
+static void dbg_fmt_vprintf(int level, const char *fmt, va_list ap)
+{
+ char buf[256];
+ int len;
+
+ if (level > dbg.level)
+ return;
+
+ if (!dbg.ph && !dbg.f)
+ return;
+
+ dbg_lock();
+
+ len = re_vsnprintf(buf, sizeof(buf), fmt, ap);
+ if (len <= 0)
+ goto out;
+
+ /* Print handler? */
+ if (dbg.ph) {
+ dbg.ph(level, buf, len, dbg.arg);
+ }
+
+ /* Output to file */
+ if (dbg.f) {
+ if (fwrite(buf, 1, len, dbg.f) > 0)
+ (void)fflush(dbg.f);
+ }
+
+ out:
+ dbg_unlock();
+}
+
+
+/**
+ * Print a formatted debug message
+ *
+ * @param level Debug level
+ * @param fmt Formatted string
+ */
+void dbg_printf(int level, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ dbg_vprintf(level, fmt, ap);
+ va_end(ap);
+
+ va_start(ap, fmt);
+ dbg_fmt_vprintf(level, fmt, ap);
+ va_end(ap);
+}
+
+
+/**
+ * Print a formatted debug message to /dev/null
+ *
+ * @param fmt Formatted string
+ */
+void dbg_noprintf(const char *fmt, ...)
+{
+ (void)fmt;
+}
+
+
+/**
+ * Print a formatted warning message
+ *
+ * @param fmt Formatted string
+ */
+void dbg_warning(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ dbg_vprintf(DBG_WARNING, fmt, ap);
+ va_end(ap);
+
+ va_start(ap, fmt);
+ dbg_fmt_vprintf(DBG_WARNING, fmt, ap);
+ va_end(ap);
+}
+
+
+/**
+ * Print a formatted notice message
+ *
+ * @param fmt Formatted string
+ */
+void dbg_notice(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ dbg_vprintf(DBG_NOTICE, fmt, ap);
+ va_end(ap);
+
+ va_start(ap, fmt);
+ dbg_fmt_vprintf(DBG_NOTICE, fmt, ap);
+ va_end(ap);
+}
+
+
+/**
+ * Print a formatted info message
+ *
+ * @param fmt Formatted string
+ */
+void dbg_info(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ dbg_vprintf(DBG_INFO, fmt, ap);
+ va_end(ap);
+
+ va_start(ap, fmt);
+ dbg_fmt_vprintf(DBG_INFO, fmt, ap);
+ va_end(ap);
+}
+
+
+/**
+ * Get the name of the debug level
+ *
+ * @param level Debug level
+ *
+ * @return String with debug level name
+ */
+const char *dbg_level_str(int level)
+{
+ switch (level) {
+
+ case DBG_EMERG: return "EMERGENCY";
+ case DBG_ALERT: return "ALERT";
+ case DBG_CRIT: return "CRITICAL";
+ case DBG_ERR: return "ERROR";
+ case DBG_WARNING: return "WARNING";
+ case DBG_NOTICE: return "NOTICE";
+ case DBG_INFO: return "INFO";
+ case DBG_DEBUG: return "DEBUG";
+ default: return "???";
+ }
+}
diff --git a/src/dbg/mod.mk b/src/dbg/mod.mk
new file mode 100644
index 0000000..1054d1d
--- /dev/null
+++ b/src/dbg/mod.mk
@@ -0,0 +1,7 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += dbg/dbg.c
diff --git a/src/dns/client.c b/src/dns/client.c
new file mode 100644
index 0000000..9535e07
--- /dev/null
+++ b/src/dns/client.c
@@ -0,0 +1,905 @@
+/**
+ * @file dns/client.c DNS Client
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_sys.h>
+#include <re_dns.h>
+
+
+#define DEBUG_MODULE "dnsc"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+enum {
+ NTX_MAX = 20,
+ QUERY_HASH_SIZE = 16,
+ TCP_HASH_SIZE = 2,
+ CONN_TIMEOUT = 10 * 1000,
+ IDLE_TIMEOUT = 30 * 1000,
+ SRVC_MAX = 32,
+};
+
+
+struct tcpconn {
+ struct le le;
+ struct list ql;
+ struct tmr tmr;
+ struct sa srv;
+ struct tcp_conn *conn;
+ struct mbuf *mb;
+ bool connected;
+ uint16_t flen;
+ struct dnsc *dnsc; /* parent */
+};
+
+
+struct dns_query {
+ struct le le;
+ struct le le_tc;
+ struct tmr tmr;
+ struct mbuf mb;
+ struct list rrlv[3];
+ char *name;
+ const struct sa *srvv;
+ const uint32_t *srvc;
+ struct tcpconn *tc;
+ struct dnsc *dnsc; /* parent */
+ struct dns_query **qp; /* app ref */
+ uint32_t ntx;
+ uint16_t id;
+ uint16_t type;
+ uint16_t dnsclass;
+ uint8_t opcode;
+ dns_query_h *qh;
+ void *arg;
+};
+
+
+struct dnsquery {
+ struct dnshdr hdr;
+ char *name;
+ uint16_t type;
+ uint16_t dnsclass;
+};
+
+
+struct dnsc {
+ struct dnsc_conf conf;
+ struct hash *ht_query;
+ struct hash *ht_tcpconn;
+ struct udp_sock *us;
+ struct sa srvv[SRVC_MAX];
+ uint32_t srvc;
+};
+
+
+static const struct dnsc_conf default_conf = {
+ QUERY_HASH_SIZE,
+ TCP_HASH_SIZE,
+ CONN_TIMEOUT,
+ IDLE_TIMEOUT,
+};
+
+
+static void tcpconn_close(struct tcpconn *tc, int err);
+static int send_tcp(struct dns_query *q);
+static void udp_timeout_handler(void *arg);
+
+
+static bool rr_unlink_handler(struct le *le, void *arg)
+{
+ struct dnsrr *rr = le->data;
+ (void)arg;
+
+ list_unlink(&rr->le_priv);
+ mem_deref(rr);
+
+ return false;
+}
+
+
+static void query_abort(struct dns_query *q)
+{
+ if (q->tc) {
+ list_unlink(&q->le_tc);
+ q->tc = mem_deref(q->tc);
+ }
+
+ tmr_cancel(&q->tmr);
+ hash_unlink(&q->le);
+}
+
+
+static void query_destructor(void *data)
+{
+ struct dns_query *q = data;
+ uint32_t i;
+
+ query_abort(q);
+ mbuf_reset(&q->mb);
+ mem_deref(q->name);
+
+ for (i=0; i<ARRAY_SIZE(q->rrlv); i++)
+ (void)list_apply(&q->rrlv[i], true, rr_unlink_handler, NULL);
+}
+
+
+static void query_handler(struct dns_query *q, int err,
+ const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl)
+{
+ /* deref here - before calling handler */
+ if (q->qp)
+ *q->qp = NULL;
+
+ /* The handler must only be called _once_ */
+ if (q->qh) {
+ q->qh(err, hdr, ansl, authl, addl, q->arg);
+ q->qh = NULL;
+ }
+
+ /* in case we have more (than one) q refs */
+ query_abort(q);
+}
+
+
+static bool query_close_handler(struct le *le, void *arg)
+{
+ struct dns_query *q = le->data;
+ (void)arg;
+
+ query_handler(q, ECONNABORTED, NULL, NULL, NULL, NULL);
+ mem_deref(q);
+
+ return false;
+}
+
+
+static bool query_cmp_handler(struct le *le, void *arg)
+{
+ struct dns_query *q = le->data;
+ struct dnsquery *dq = arg;
+
+ if (q->id != dq->hdr.id)
+ return false;
+
+ if (q->opcode != dq->hdr.opcode)
+ return false;
+
+ if (q->type != dq->type)
+ return false;
+
+ if (q->dnsclass != dq->dnsclass)
+ return false;
+
+ if (str_casecmp(q->name, dq->name))
+ return false;
+
+ return true;
+}
+
+
+static int reply_recv(struct dnsc *dnsc, struct mbuf *mb)
+{
+ struct dns_query *q = NULL;
+ uint32_t i, j, nv[3];
+ struct dnsquery dq;
+ int err = 0;
+
+ if (!dnsc || !mb)
+ return EINVAL;
+
+ dq.name = NULL;
+
+ if (dns_hdr_decode(mb, &dq.hdr) || !dq.hdr.qr) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ err = dns_dname_decode(mb, &dq.name, 0);
+ if (err)
+ goto out;
+
+ if (mbuf_get_left(mb) < 4) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ dq.type = ntohs(mbuf_read_u16(mb));
+ dq.dnsclass = ntohs(mbuf_read_u16(mb));
+
+ q = list_ledata(hash_lookup(dnsc->ht_query, hash_joaat_str_ci(dq.name),
+ query_cmp_handler, &dq));
+ if (!q) {
+ err = ENOENT;
+ goto out;
+ }
+
+ /* try next server */
+ if (dq.hdr.rcode == DNS_RCODE_SRV_FAIL && q->ntx < *q->srvc) {
+
+ if (!q->tc) /* try next UDP server immediately */
+ tmr_start(&q->tmr, 0, udp_timeout_handler, q);
+
+ err = EPROTO;
+ goto out;
+ }
+
+ nv[0] = dq.hdr.nans;
+ nv[1] = dq.hdr.nauth;
+ nv[2] = dq.hdr.nadd;
+
+ for (i=0; i<ARRAY_SIZE(nv); i++) {
+
+ for (j=0; j<nv[i]; j++) {
+
+ struct dnsrr *rr = NULL;
+
+ err = dns_rr_decode(mb, &rr, 0);
+ if (err) {
+ query_handler(q, err, NULL, NULL, NULL, NULL);
+ mem_deref(q);
+ goto out;
+ }
+
+ list_append(&q->rrlv[i], &rr->le_priv, rr);
+ }
+ }
+
+ if (q->type == DNS_QTYPE_AXFR) {
+
+ struct dnsrr *rrh, *rrt;
+
+ rrh = list_ledata(list_head(&q->rrlv[0]));
+ rrt = list_ledata(list_tail(&q->rrlv[0]));
+
+ /* Wait for last AXFR reply with terminating SOA record */
+ if (dq.hdr.rcode == DNS_RCODE_OK && dq.hdr.nans > 0 &&
+ (!rrt || rrt->type != DNS_TYPE_SOA || rrh == rrt)) {
+ DEBUG_INFO("waiting for last SOA record in reply\n");
+ goto out;
+ }
+ }
+
+ query_handler(q, 0, &dq.hdr, &q->rrlv[0], &q->rrlv[1], &q->rrlv[2]);
+ mem_deref(q);
+
+ out:
+ mem_deref(dq.name);
+
+ return err;
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ (void)src;
+ (void)reply_recv(arg, mb);
+}
+
+
+static void tcp_recv_handler(struct mbuf *mbrx, void *arg)
+{
+ struct tcpconn *tc = arg;
+ struct mbuf *mb = tc->mb;
+ int err = 0;
+ size_t n;
+
+ next:
+ /* frame length */
+ if (!tc->flen) {
+
+ n = min(2 - mb->end, mbuf_get_left(mbrx));
+
+ err = mbuf_write_mem(mb, mbuf_buf(mbrx), n);
+ if (err)
+ goto error;
+
+ mbrx->pos += n;
+
+ if (mb->end < 2)
+ return;
+
+ mb->pos = 0;
+ tc->flen = ntohs(mbuf_read_u16(mb));
+ mb->pos = 0;
+ mb->end = 0;
+ }
+
+ /* content */
+ n = min(tc->flen - mb->end, mbuf_get_left(mbrx));
+
+ err = mbuf_write_mem(mb, mbuf_buf(mbrx), n);
+ if (err)
+ goto error;
+
+ mbrx->pos += n;
+
+ if (mb->end < tc->flen)
+ return;
+
+ mb->pos = 0;
+
+ err = reply_recv(tc->dnsc, mb);
+ if (err)
+ goto error;
+
+ /* reset tcp buffer */
+ tc->flen = 0;
+ mb->pos = 0;
+ mb->end = 0;
+
+ /* more data ? */
+ if (mbuf_get_left(mbrx) > 0) {
+ DEBUG_INFO("%u bytes of tcp data left\n", mbuf_get_left(mbrx));
+ goto next;
+ }
+
+ return;
+
+ error:
+ tcpconn_close(tc, err);
+}
+
+
+static void tcpconn_timeout_handler(void *arg)
+{
+ struct tcpconn *tc = arg;
+
+ DEBUG_NOTICE("tcp (%J) %s timeout \n", &tc->srv,
+ tc->connected ? "idle" : "connect");
+
+ tcpconn_close(tc, ETIMEDOUT);
+}
+
+
+static void tcp_estab_handler(void *arg)
+{
+ struct tcpconn *tc = arg;
+ struct le *le = list_head(&tc->ql);
+ int err = 0;
+
+ DEBUG_INFO("connection (%J) established\n", &tc->srv);
+
+ while (le) {
+ struct dns_query *q = le->data;
+
+ le = le->next;
+
+ q->mb.pos = 0;
+ err = tcp_send(tc->conn, &q->mb);
+ if (err)
+ break;
+
+ DEBUG_INFO("tcp send %J\n", &tc->srv);
+ }
+
+ if (err) {
+ tcpconn_close(tc, err);
+ return;
+ }
+
+ tmr_start(&tc->tmr, tc->dnsc->conf.idle_timeout,
+ tcpconn_timeout_handler, tc);
+ tc->connected = true;
+}
+
+
+static void tcp_close_handler(int err, void *arg)
+{
+ struct tcpconn *tc = arg;
+
+ DEBUG_NOTICE("connection (%J) closed: %m\n", &tc->srv, err);
+ tcpconn_close(tc, err);
+}
+
+
+static bool tcpconn_cmp_handler(struct le *le, void *arg)
+{
+ const struct tcpconn *tc = le->data;
+
+ /* avoid trying this connection if dead */
+ if (!tc->conn)
+ return false;
+
+ return sa_cmp(&tc->srv, arg, SA_ALL);
+}
+
+
+static bool tcpconn_fail_handler(struct le *le, void *arg)
+{
+ struct dns_query *q = le->data;
+ int err = *((int *)arg);
+
+ list_unlink(&q->le_tc);
+ q->tc = mem_deref(q->tc);
+
+ if (q->ntx >= *q->srvc) {
+ DEBUG_WARNING("all servers failed, giving up!!\n");
+ err = err ? err : ECONNREFUSED;
+ goto out;
+ }
+
+ /* try next server(s) */
+ err = send_tcp(q);
+ if (err) {
+ DEBUG_WARNING("all servers failed, giving up\n");
+ goto out;
+ }
+
+ out:
+ if (err) {
+ query_handler(q, err, NULL, NULL, NULL, NULL);
+ mem_deref(q);
+ }
+
+ return false;
+}
+
+
+static void tcpconn_close(struct tcpconn *tc, int err)
+{
+ if (!tc)
+ return;
+
+ /* avoid trying this connection again (e.g. same address) */
+ tc->conn = mem_deref(tc->conn);
+ (void)list_apply(&tc->ql, true, tcpconn_fail_handler, &err);
+ mem_deref(tc);
+}
+
+
+static void tcpconn_destructor(void *arg)
+{
+ struct tcpconn *tc = arg;
+
+ hash_unlink(&tc->le);
+ tmr_cancel(&tc->tmr);
+ mem_deref(tc->conn);
+ mem_deref(tc->mb);
+}
+
+
+static int tcpconn_alloc(struct tcpconn **tcpp, struct dnsc *dnsc,
+ const struct sa *srv)
+{
+ struct tcpconn *tc;
+ int err = ENOMEM;
+
+ if (!tcpp || !dnsc || !srv)
+ return EINVAL;
+
+ tc = mem_zalloc(sizeof(struct tcpconn), tcpconn_destructor);
+ if (!tc)
+ goto out;
+
+ hash_append(dnsc->ht_tcpconn, sa_hash(srv, SA_ALL), &tc->le, tc);
+ tc->srv = *srv;
+ tc->dnsc = dnsc;
+
+ tc->mb = mbuf_alloc(1500);
+ if (!tc->mb)
+ goto out;
+
+ err = tcp_connect(&tc->conn, srv, tcp_estab_handler,
+ tcp_recv_handler, tcp_close_handler, tc);
+ if (err)
+ goto out;
+
+ tmr_start(&tc->tmr, tc->dnsc->conf.conn_timeout,
+ tcpconn_timeout_handler, tc);
+ out:
+ if (err)
+ mem_deref(tc);
+ else
+ *tcpp = tc;
+
+ return err;
+}
+
+
+static int send_tcp(struct dns_query *q)
+{
+ const struct sa *srv;
+ struct tcpconn *tc;
+ int err = 0;
+
+ if (!q)
+ return EINVAL;
+
+ while (q->ntx < *q->srvc) {
+
+ srv = &q->srvv[q->ntx++];
+
+ DEBUG_NOTICE("trying tcp server#%u: %J\n", q->ntx-1, srv);
+
+ tc = list_ledata(hash_lookup(q->dnsc->ht_tcpconn,
+ sa_hash(srv, SA_ALL),
+ tcpconn_cmp_handler,
+ (void *)srv));
+ if (!tc) {
+ err = tcpconn_alloc(&tc, q->dnsc, srv);
+ if (err)
+ continue;
+ }
+
+ if (tc->connected) {
+ q->mb.pos = 0;
+ err = tcp_send(tc->conn, &q->mb);
+ if (err) {
+ tcpconn_close(tc, err);
+ continue;
+ }
+
+ tmr_start(&tc->tmr, tc->dnsc->conf.idle_timeout,
+ tcpconn_timeout_handler, tc);
+ DEBUG_NOTICE("tcp send %J\n", srv);
+ }
+
+ list_append(&tc->ql, &q->le_tc, q);
+ q->tc = mem_ref(tc);
+ break;
+ }
+
+ return err;
+}
+
+
+static void tcp_timeout_handler(void *arg)
+{
+ struct dns_query *q = arg;
+
+ query_handler(q, ETIMEDOUT, NULL, NULL, NULL, NULL);
+ mem_deref(q);
+}
+
+
+static int send_udp(struct dns_query *q)
+{
+ const struct sa *srv;
+ int err = ETIMEDOUT;
+ uint32_t i;
+
+ if (!q)
+ return EINVAL;
+
+ for (i=0; i<*q->srvc; i++) {
+
+ srv = &q->srvv[q->ntx++%*q->srvc];
+
+ DEBUG_INFO("trying udp server#%u: %J\n", i, srv);
+
+ q->mb.pos = 0;
+ err = udp_send(q->dnsc->us, srv, &q->mb);
+ if (!err)
+ break;
+ }
+
+ return err;
+}
+
+
+static void udp_timeout_handler(void *arg)
+{
+ struct dns_query *q = arg;
+ int err = ETIMEDOUT;
+
+ if (q->ntx >= NTX_MAX)
+ goto out;
+
+ err = send_udp(q);
+ if (err)
+ goto out;
+
+ tmr_start(&q->tmr, 1000<<MIN(2, q->ntx - 2),
+ udp_timeout_handler, q);
+
+ out:
+ if (err) {
+ query_handler(q, err, NULL, NULL, NULL, NULL);
+ mem_deref(q);
+ }
+}
+
+
+static int query(struct dns_query **qp, struct dnsc *dnsc, uint8_t opcode,
+ const char *name, uint16_t type, uint16_t dnsclass,
+ const struct dnsrr *ans_rr, int proto,
+ const struct sa *srvv, const uint32_t *srvc,
+ bool aa, bool rd, dns_query_h *qh, void *arg)
+{
+ struct dns_query *q = NULL;
+ struct dnshdr hdr;
+ int err = 0;
+ uint32_t i;
+
+ if (!dnsc || !name || !srvv || !srvc || !(*srvc))
+ return EINVAL;
+
+ if (DNS_QTYPE_AXFR == type)
+ proto = IPPROTO_TCP;
+
+ q = mem_zalloc(sizeof(*q), query_destructor);
+ if (!q)
+ goto nmerr;
+
+ hash_append(dnsc->ht_query, hash_joaat_str_ci(name), &q->le, q);
+ tmr_init(&q->tmr);
+ mbuf_init(&q->mb);
+
+ for (i=0; i<ARRAY_SIZE(q->rrlv); i++)
+ list_init(&q->rrlv[i]);
+
+ err = str_dup(&q->name, name);
+ if (err)
+ goto error;
+
+ q->srvv = srvv;
+ q->srvc = srvc;
+ q->id = rand_u16();
+ q->type = type;
+ q->opcode = opcode;
+ q->dnsclass = dnsclass;
+ q->dnsc = dnsc;
+
+ memset(&hdr, 0, sizeof(hdr));
+
+ hdr.id = q->id;
+ hdr.opcode = q->opcode;
+ hdr.aa = aa;
+ hdr.rd = rd;
+ hdr.nq = 1;
+ hdr.nans = ans_rr ? 1 : 0;
+
+ if (proto == IPPROTO_TCP)
+ q->mb.pos += 2;
+
+ err = dns_hdr_encode(&q->mb, &hdr);
+ if (err)
+ goto error;
+
+ err = dns_dname_encode(&q->mb, name, NULL, 0, false);
+ if (err)
+ goto error;
+
+ err |= mbuf_write_u16(&q->mb, htons(type));
+ err |= mbuf_write_u16(&q->mb, htons(dnsclass));
+ if (err)
+ goto error;
+
+ if (ans_rr) {
+ err = dns_rr_encode(&q->mb, ans_rr, 0, NULL, 0);
+ if (err)
+ goto error;
+ }
+
+ q->qh = qh;
+ q->arg = arg;
+
+ switch (proto) {
+
+ case IPPROTO_TCP:
+ q->mb.pos = 0;
+ (void)mbuf_write_u16(&q->mb, htons(q->mb.end - 2));
+
+ err = send_tcp(q);
+ if (err)
+ goto error;
+
+ tmr_start(&q->tmr, 60 * 1000, tcp_timeout_handler, q);
+ break;
+
+ case IPPROTO_UDP:
+ err = send_udp(q);
+ if (err)
+ goto error;
+
+ tmr_start(&q->tmr, 500, udp_timeout_handler, q);
+ break;
+
+ default:
+ err = EPROTONOSUPPORT;
+ goto error;
+ }
+
+ if (qp) {
+ q->qp = qp;
+ *qp = q;
+ }
+
+ return 0;
+
+ nmerr:
+ err = ENOMEM;
+ error:
+ mem_deref(q);
+
+ return err;
+}
+
+
+/**
+ * Query a DNS name
+ *
+ * @param qp Pointer to allocated DNS query
+ * @param dnsc DNS Client
+ * @param name DNS name
+ * @param type DNS Resource Record type
+ * @param dnsclass DNS Class
+ * @param rd Recursion Desired (RD) flag
+ * @param qh Query handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dnsc_query(struct dns_query **qp, struct dnsc *dnsc, const char *name,
+ uint16_t type, uint16_t dnsclass,
+ bool rd, dns_query_h *qh, void *arg)
+{
+ if (!dnsc)
+ return EINVAL;
+
+ return query(qp, dnsc, DNS_OPCODE_QUERY, name, type, dnsclass, NULL,
+ IPPROTO_UDP, dnsc->srvv, &dnsc->srvc, false, rd, qh, arg);
+}
+
+
+/**
+ * Query a DNS name SRV record
+ *
+ * @param qp Pointer to allocated DNS query
+ * @param dnsc DNS Client
+ * @param name DNS name
+ * @param type DNS Resource Record type
+ * @param dnsclass DNS Class
+ * @param proto Protocol
+ * @param srvv DNS Nameservers
+ * @param srvc Number of DNS nameservers
+ * @param rd Recursion Desired (RD) flag
+ * @param qh Query handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dnsc_query_srv(struct dns_query **qp, struct dnsc *dnsc, const char *name,
+ uint16_t type, uint16_t dnsclass, int proto,
+ const struct sa *srvv, const uint32_t *srvc,
+ bool rd, dns_query_h *qh, void *arg)
+{
+ return query(qp, dnsc, DNS_OPCODE_QUERY, name, type, dnsclass,
+ NULL, proto, srvv, srvc, false, rd, qh, arg);
+}
+
+
+/**
+ * Send a DNS query with NOTIFY opcode
+ *
+ * @param qp Pointer to allocated DNS query
+ * @param dnsc DNS Client
+ * @param name DNS name
+ * @param type DNS Resource Record type
+ * @param dnsclass DNS Class
+ * @param ans_rr Answer Resource Record
+ * @param proto Protocol
+ * @param srvv DNS Nameservers
+ * @param srvc Number of DNS nameservers
+ * @param qh Query handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dnsc_notify(struct dns_query **qp, struct dnsc *dnsc, const char *name,
+ uint16_t type, uint16_t dnsclass, const struct dnsrr *ans_rr,
+ int proto, const struct sa *srvv, const uint32_t *srvc,
+ dns_query_h *qh, void *arg)
+{
+ return query(qp, dnsc, DNS_OPCODE_NOTIFY, name, type, dnsclass,
+ ans_rr, proto, srvv, srvc, true, false, qh, arg);
+}
+
+
+static void dnsc_destructor(void *data)
+{
+ struct dnsc *dnsc = data;
+
+ (void)hash_apply(dnsc->ht_query, query_close_handler, NULL);
+ hash_flush(dnsc->ht_tcpconn);
+
+ mem_deref(dnsc->ht_tcpconn);
+ mem_deref(dnsc->ht_query);
+ mem_deref(dnsc->us);
+}
+
+
+/**
+ * Allocate a DNS Client
+ *
+ * @param dcpp Pointer to allocated DNS Client
+ * @param conf Optional DNS configuration, NULL for default
+ * @param srvv DNS servers
+ * @param srvc Number of DNS Servers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dnsc_alloc(struct dnsc **dcpp, const struct dnsc_conf *conf,
+ const struct sa *srvv, uint32_t srvc)
+{
+ struct dnsc *dnsc;
+ int err;
+
+ if (!dcpp)
+ return EINVAL;
+
+ dnsc = mem_zalloc(sizeof(*dnsc), dnsc_destructor);
+ if (!dnsc)
+ return ENOMEM;
+
+ if (conf)
+ dnsc->conf = *conf;
+ else
+ dnsc->conf = default_conf;
+
+ err = dnsc_srv_set(dnsc, srvv, srvc);
+ if (err)
+ goto out;
+
+ err = udp_listen(&dnsc->us, NULL, udp_recv_handler, dnsc);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&dnsc->ht_query, dnsc->conf.query_hash_size);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&dnsc->ht_tcpconn, dnsc->conf.tcp_hash_size);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(dnsc);
+ else
+ *dcpp = dnsc;
+
+ return err;
+}
+
+
+/**
+ * Set the DNS Servers on a DNS Client
+ *
+ * @param dnsc DNS Client
+ * @param srvv DNS Nameservers
+ * @param srvc Number of nameservers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dnsc_srv_set(struct dnsc *dnsc, const struct sa *srvv, uint32_t srvc)
+{
+ uint32_t i;
+
+ if (!dnsc)
+ return EINVAL;
+
+ dnsc->srvc = min((uint32_t)ARRAY_SIZE(dnsc->srvv), srvc);
+
+ if (srvv) {
+ for (i=0; i<dnsc->srvc; i++)
+ dnsc->srvv[i] = srvv[i];
+ }
+
+ return 0;
+}
diff --git a/src/dns/cstr.c b/src/dns/cstr.c
new file mode 100644
index 0000000..9e6c90a
--- /dev/null
+++ b/src/dns/cstr.c
@@ -0,0 +1,61 @@
+/**
+ * @file cstr.c DNS character strings encoding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_dns.h>
+
+
+/**
+ * Encode a DNS character string into a memory buffer
+ *
+ * @param mb Memory buffer to encode into
+ * @param str Character string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_cstr_encode(struct mbuf *mb, const char *str)
+{
+ uint8_t len;
+ int err = 0;
+
+ if (!mb || !str)
+ return EINVAL;
+
+ len = (uint8_t)strlen(str);
+
+ err |= mbuf_write_u8(mb, len);
+ err |= mbuf_write_mem(mb, (const uint8_t *)str, len);
+
+ return err;
+}
+
+
+/**
+ * Decode a DNS character string from a memory buffer
+ *
+ * @param mb Memory buffer to decode from
+ * @param str Pointer to allocated character string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_cstr_decode(struct mbuf *mb, char **str)
+{
+ uint8_t len;
+
+ if (!mb || !str || (mbuf_get_left(mb) < 1))
+ return EINVAL;
+
+ len = mbuf_read_u8(mb);
+
+ if (mbuf_get_left(mb) < len)
+ return EBADMSG;
+
+ return mbuf_strdup(mb, str, len);
+}
diff --git a/src/dns/darwin/srv.c b/src/dns/darwin/srv.c
new file mode 100644
index 0000000..05c92c1
--- /dev/null
+++ b/src/dns/darwin/srv.c
@@ -0,0 +1,83 @@
+/**
+ * @file darwin/srv.c Get DNS Server IP code for Mac OS X
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_dns.h>
+#include "../dns.h"
+#define __CF_USE_FRAMEWORK_INCLUDES__
+#include <SystemConfiguration/SystemConfiguration.h>
+
+
+int get_darwin_dns(char *domain, size_t dsize, struct sa *nsv, uint32_t *n)
+{
+#if TARGET_OS_IPHONE
+ (void)domain;
+ (void)dsize;
+ (void)nsv;
+ (void)n;
+ return ENOSYS;
+#else
+ SCDynamicStoreContext context = {0, NULL, NULL, NULL, NULL};
+ CFArrayRef addresses, domains;
+ SCDynamicStoreRef store;
+ CFStringRef key, dom;
+ CFDictionaryRef dict;
+ uint32_t c, i;
+ int err = ENOENT;
+
+ if (!nsv || !n)
+ return EINVAL;
+
+ store = SCDynamicStoreCreate(NULL, CFSTR("get_darwin_dns"),
+ NULL, &context);
+ if (!store)
+ return ENOENT;
+
+ key = CFSTR("State:/Network/Global/DNS");
+ dict = SCDynamicStoreCopyValue(store, key);
+ if (!dict)
+ goto out1;
+
+ addresses = CFDictionaryGetValue(dict, kSCPropNetDNSServerAddresses);
+ if (!addresses)
+ goto out;
+
+ c = (uint32_t)CFArrayGetCount(addresses);
+ *n = min(*n, c);
+
+ for (i=0; i<*n; i++) {
+ CFStringRef address = CFArrayGetValueAtIndex(addresses, i);
+ char str[64];
+
+ CFStringGetCString(address, str, sizeof(str),
+ kCFStringEncodingUTF8);
+
+ err = sa_set_str(&nsv[i], str, DNS_PORT);
+ if (err)
+ break;
+ }
+
+ domains = CFDictionaryGetValue(dict, kSCPropNetDNSSearchDomains);
+ if (!domains)
+ goto out;
+
+ if (CFArrayGetCount(domains) < 1)
+ goto out;
+
+ dom = CFArrayGetValueAtIndex(domains, 0);
+ CFStringGetCString(dom, domain, dsize, kCFStringEncodingUTF8);
+
+ out:
+ CFRelease(dict);
+ out1:
+ CFRelease(store);
+
+ return err;
+#endif
+}
diff --git a/src/dns/dname.c b/src/dns/dname.c
new file mode 100644
index 0000000..eda9ba4
--- /dev/null
+++ b/src/dns/dname.c
@@ -0,0 +1,219 @@
+/**
+ * @file dname.c DNS domain names
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_dns.h>
+
+
+#define COMP_MASK 0xc0
+#define OFFSET_MASK 0x3fff
+#define COMP_LOOP 255
+
+
+struct dname {
+ struct le he;
+ size_t pos;
+ char *name;
+};
+
+
+static void destructor(void *arg)
+{
+ struct dname *dn = arg;
+
+ hash_unlink(&dn->he);
+ mem_deref(dn->name);
+}
+
+
+static void dname_append(struct hash *ht_dname, const char *name, size_t pos)
+{
+ struct dname *dn;
+
+ if (!ht_dname || pos > OFFSET_MASK || !*name)
+ return;
+
+ dn = mem_zalloc(sizeof(*dn), destructor);
+ if (!dn)
+ return;
+
+ if (str_dup(&dn->name, name)) {
+ mem_deref(dn);
+ return;
+ }
+
+ hash_append(ht_dname, hash_joaat_str_ci(name), &dn->he, dn);
+ dn->pos = pos;
+}
+
+
+static bool lookup_handler(struct le *le, void *arg)
+{
+ struct dname *dn = le->data;
+
+ return 0 == str_casecmp(dn->name, arg);
+}
+
+
+static inline struct dname *dname_lookup(struct hash *ht_dname,
+ const char *name)
+{
+ return list_ledata(hash_lookup(ht_dname, hash_joaat_str_ci(name),
+ lookup_handler, (void *)name));
+}
+
+
+static inline int dname_encode_pointer(struct mbuf *mb, size_t pos)
+{
+ return mbuf_write_u16(mb, htons(pos | (COMP_MASK<<8)));
+}
+
+
+/**
+ * Encode a DNS Domain name into a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param name Domain name
+ * @param ht_dname Domain name hashtable
+ * @param start Start position
+ * @param comp Enable compression
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_dname_encode(struct mbuf *mb, const char *name,
+ struct hash *ht_dname, size_t start, bool comp)
+{
+ struct dname *dn;
+ size_t pos;
+ int err;
+
+ if (!mb || !name)
+ return EINVAL;
+
+ dn = dname_lookup(ht_dname, name);
+ if (dn && comp)
+ return dname_encode_pointer(mb, dn->pos);
+
+ pos = mb->pos;
+ if (!dn)
+ dname_append(ht_dname, name, pos - start);
+ err = mbuf_write_u8(mb, 0);
+
+ if ('.' == name[0] && '\0' == name[1])
+ return err;
+
+ while (err == 0) {
+
+ const size_t lablen = mb->pos - pos - 1;
+
+ if ('\0' == *name) {
+ if (!lablen)
+ break;
+
+ mb->buf[pos] = lablen;
+ err |= mbuf_write_u8(mb, 0);
+ break;
+ }
+ else if ('.' == *name) {
+ if (!lablen)
+ return EINVAL;
+
+ mb->buf[pos] = lablen;
+
+ dn = dname_lookup(ht_dname, name + 1);
+ if (dn && comp) {
+ err |= dname_encode_pointer(mb, dn->pos);
+ break;
+ }
+
+ pos = mb->pos;
+ if (!dn)
+ dname_append(ht_dname, name + 1, pos - start);
+ err |= mbuf_write_u8(mb, 0);
+ }
+ else {
+ err |= mbuf_write_u8(mb, *name);
+ }
+
+ ++name;
+ }
+
+ return err;
+}
+
+
+/**
+ * Decode a DNS domain name from a memory buffer
+ *
+ * @param mb Memory buffer to decode from
+ * @param name Pointer to allocated string with domain name
+ * @param start Start position
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_dname_decode(struct mbuf *mb, char **name, size_t start)
+{
+ uint32_t i = 0, loopc = 0;
+ bool comp = false;
+ size_t pos = 0;
+ char buf[256];
+
+ if (!mb || !name)
+ return EINVAL;
+
+ while (mb->pos < mb->end) {
+
+ uint8_t len = mb->buf[mb->pos++];
+ if (!len) {
+ if (comp)
+ mb->pos = pos;
+
+ buf[i++] = '\0';
+
+ *name = mem_alloc(i, NULL);
+ if (!*name)
+ return ENOMEM;
+
+ str_ncpy(*name, buf, i);
+
+ return 0;
+ }
+ else if ((len & COMP_MASK) == COMP_MASK) {
+ uint16_t offset;
+
+ if (loopc++ > COMP_LOOP)
+ break;
+
+ --mb->pos;
+
+ offset = ntohs(mbuf_read_u16(mb)) & OFFSET_MASK;
+ if (!comp) {
+ pos = mb->pos;
+ comp = true;
+ }
+
+ mb->pos = offset + start;
+ continue;
+ }
+ else if (len > mbuf_get_left(mb))
+ break;
+ else if (len > sizeof(buf) - i - 2)
+ break;
+
+ if (i > 0)
+ buf[i++] = '.';
+
+ while (len--)
+ buf[i++] = mb->buf[mb->pos++];
+ }
+
+ return EINVAL;
+}
diff --git a/src/dns/dns.h b/src/dns/dns.h
new file mode 100644
index 0000000..b2c58e8
--- /dev/null
+++ b/src/dns/dns.h
@@ -0,0 +1,16 @@
+/**
+ * @file dns.h Internal DNS header file
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifdef HAVE_RESOLV
+int get_resolv_dns(char *domain, size_t dsize, struct sa *nsv, uint32_t *n);
+#endif
+#ifdef WIN32
+int get_windns(char *domain, size_t dsize, struct sa *nav, uint32_t *n);
+#endif
+#ifdef DARWIN
+int get_darwin_dns(char *domain, size_t dsize, struct sa *nsv, uint32_t *n);
+#endif
diff --git a/src/dns/hdr.c b/src/dns/hdr.c
new file mode 100644
index 0000000..5133b33
--- /dev/null
+++ b/src/dns/hdr.c
@@ -0,0 +1,137 @@
+/**
+ * @file dns/hdr.c DNS header encoding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_dns.h>
+
+
+enum {
+ QUERY_RESPONSE = 15,
+ OPCODE = 11,
+ AUTH_ANSWER = 10,
+ TRUNCATED = 9,
+ RECURSION_DESIRED = 8,
+ RECURSION_AVAILABLE = 7,
+ ZERO = 4
+};
+
+
+/**
+ * Encode a DNS header
+ *
+ * @param mb Memory buffer to encode header into
+ * @param hdr DNS header
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_hdr_encode(struct mbuf *mb, const struct dnshdr *hdr)
+{
+ uint16_t flags = 0;
+ int err = 0;
+
+ if (!mb || !hdr)
+ return EINVAL;
+
+ flags |= hdr->qr <<QUERY_RESPONSE;
+ flags |= hdr->opcode <<OPCODE;
+ flags |= hdr->aa <<AUTH_ANSWER;
+ flags |= hdr->tc <<TRUNCATED;
+ flags |= hdr->rd <<RECURSION_DESIRED;
+ flags |= hdr->ra <<RECURSION_AVAILABLE;
+ flags |= hdr->z <<ZERO;
+ flags |= hdr->rcode;
+
+ err |= mbuf_write_u16(mb, htons(hdr->id));
+ err |= mbuf_write_u16(mb, htons(flags));
+ err |= mbuf_write_u16(mb, htons(hdr->nq));
+ err |= mbuf_write_u16(mb, htons(hdr->nans));
+ err |= mbuf_write_u16(mb, htons(hdr->nauth));
+ err |= mbuf_write_u16(mb, htons(hdr->nadd));
+
+ return err;
+}
+
+
+/**
+ * Decode a DNS header from a memory buffer
+ *
+ * @param mb Memory buffer to decode header from
+ * @param hdr DNS header (output)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_hdr_decode(struct mbuf *mb, struct dnshdr *hdr)
+{
+ uint16_t flags = 0;
+
+ if (!mb || !hdr || (mbuf_get_left(mb) < DNS_HEADER_SIZE))
+ return EINVAL;
+
+ hdr->id = ntohs(mbuf_read_u16(mb));
+ flags = ntohs(mbuf_read_u16(mb));
+
+ hdr->qr = 0x1 & (flags >> QUERY_RESPONSE);
+ hdr->opcode = 0xf & (flags >> OPCODE);
+ hdr->aa = 0x1 & (flags >> AUTH_ANSWER);
+ hdr->tc = 0x1 & (flags >> TRUNCATED);
+ hdr->rd = 0x1 & (flags >> RECURSION_DESIRED);
+ hdr->ra = 0x1 & (flags >> RECURSION_AVAILABLE);
+ hdr->z = 0x7 & (flags >> ZERO);
+ hdr->rcode = 0xf & (flags >> 0);
+
+ hdr->nq = ntohs(mbuf_read_u16(mb));
+ hdr->nans = ntohs(mbuf_read_u16(mb));
+ hdr->nauth = ntohs(mbuf_read_u16(mb));
+ hdr->nadd = ntohs(mbuf_read_u16(mb));
+
+ return 0;
+}
+
+
+/**
+ * Get the string of a DNS opcode
+ *
+ * @param opcode DNS opcode
+ *
+ * @return Opcode string
+ */
+const char *dns_hdr_opcodename(uint8_t opcode)
+{
+ switch (opcode) {
+
+ case DNS_OPCODE_QUERY: return "QUERY";
+ case DNS_OPCODE_IQUERY: return "IQUERY";
+ case DNS_OPCODE_STATUS: return "STATUS";
+ case DNS_OPCODE_NOTIFY: return "NOTIFY";
+ default: return "??";
+ }
+}
+
+
+/**
+ * Get the string of a DNS response code
+ *
+ * @param rcode Response code
+ *
+ * @return Response code string
+ */
+const char *dns_hdr_rcodename(uint8_t rcode)
+{
+ switch (rcode) {
+
+ case DNS_RCODE_OK: return "OK";
+ case DNS_RCODE_FMT_ERR: return "Format Error";
+ case DNS_RCODE_SRV_FAIL: return "Server Failure";
+ case DNS_RCODE_NAME_ERR: return "Name Error";
+ case DNS_RCODE_NOT_IMPL: return "Not Implemented";
+ case DNS_RCODE_REFUSED: return "Refused";
+ case DNS_RCODE_NOT_AUTH: return "Server Not Authoritative for zone";
+ default: return "??";
+ }
+}
diff --git a/src/dns/mod.mk b/src/dns/mod.mk
new file mode 100644
index 0000000..1137b61
--- /dev/null
+++ b/src/dns/mod.mk
@@ -0,0 +1,27 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += dns/client.c
+SRCS += dns/cstr.c
+SRCS += dns/dname.c
+SRCS += dns/hdr.c
+SRCS += dns/ns.c
+SRCS += dns/rr.c
+SRCS += dns/rrlist.c
+
+ifneq ($(HAVE_RESOLV),)
+SRCS += dns/res.c
+endif
+
+ifeq ($(OS),win32)
+SRCS += dns/win32/srv.c
+endif
+
+ifeq ($(OS),darwin)
+SRCS += dns/darwin/srv.c
+# add libraries for darwin dns servers
+LFLAGS += -framework SystemConfiguration -framework CoreFoundation
+endif
diff --git a/src/dns/ns.c b/src/dns/ns.c
new file mode 100644
index 0000000..aac713d
--- /dev/null
+++ b/src/dns/ns.c
@@ -0,0 +1,150 @@
+/**
+ * @file ns.c DNS Nameserver configuration
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdio.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_dns.h>
+#include "dns.h"
+#ifdef __ANDROID__
+#include <sys/system_properties.h>
+#endif
+
+
+#define DEBUG_MODULE "ns"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static int parse_resolv_conf(char *domain, size_t dsize,
+ struct sa *srvv, uint32_t *n)
+{
+ FILE *f;
+ struct pl dom = pl_null;
+ uint32_t i = 0;
+ int err = 0;
+
+ if (!srvv || !n || !*n)
+ return EINVAL;
+
+ f = fopen("/etc/resolv.conf", "r");
+ if (!f)
+ return errno;
+
+ for (;;) {
+ char line[128];
+ struct pl srv;
+ size_t len;
+
+ if (1 != fscanf(f, "%127[^\n]\n", line))
+ break;
+
+ if ('#' == line[0])
+ continue;
+
+ len = str_len(line);
+
+ /* Set domain if not already set */
+ if (!pl_isset(&dom)) {
+ if (0 == re_regex(line, len, "domain [^ ]+", &dom)) {
+ (void)pl_strcpy(&dom, domain, dsize);
+ }
+
+ if (0 == re_regex(line, len, "search [^ ]+", &dom)) {
+ (void)pl_strcpy(&dom, domain, dsize);
+ }
+ }
+
+ /* Use the first entry */
+ if (i < *n && 0 == re_regex(line, len, "nameserver [^\n]+",
+ &srv)) {
+ err = sa_set(&srvv[i], &srv, DNS_PORT);
+ if (err) {
+ DEBUG_WARNING("sa_set: %r (%m)\n", &srv, err);
+ }
+ ++i;
+ }
+ }
+
+ *n = i;
+
+ (void)fclose(f);
+
+ return err;
+}
+
+
+#ifdef __ANDROID__
+static int get_android_dns(struct sa *nsv, uint32_t *n)
+{
+ char prop[PROP_NAME_MAX] = {0}, value[PROP_VALUE_MAX] = {0};
+ uint32_t i, count = 0;
+ int err;
+
+ for (i=0; i<*n; i++) {
+ re_snprintf(prop, sizeof(prop), "net.dns%u", 1+i);
+
+ if (__system_property_get(prop, value)) {
+
+ err = sa_set_str(&nsv[count], value, DNS_PORT);
+ if (!err)
+ ++count;
+ }
+ }
+ if (count == 0)
+ return ENOENT;
+
+ *n = count;
+
+ return 0;
+}
+#endif
+
+
+/**
+ * Get the DNS domain and nameservers
+ *
+ * @param domain Returned domain name
+ * @param dsize Size of domain name buffer
+ * @param srvv Returned nameservers
+ * @param n Nameservers capacity, actual on return
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_srv_get(char *domain, size_t dsize, struct sa *srvv, uint32_t *n)
+{
+ int err;
+
+ /* Try them all in prioritized order */
+
+#ifdef HAVE_RESOLV
+ err = get_resolv_dns(domain, dsize, srvv, n);
+ if (!err)
+ return 0;
+#endif
+
+#ifdef DARWIN
+ err = get_darwin_dns(domain, dsize, srvv, n);
+ if (!err)
+ return 0;
+#endif
+
+ err = parse_resolv_conf(domain, dsize, srvv, n);
+ if (!err)
+ return 0;
+
+#ifdef WIN32
+ err = get_windns(domain, dsize, srvv, n);
+#endif
+
+#ifdef __ANDROID__
+ err = get_android_dns(srvv, n);
+#endif
+
+ return err;
+}
diff --git a/src/dns/res.c b/src/dns/res.c
new file mode 100644
index 0000000..7df3d46
--- /dev/null
+++ b/src/dns/res.c
@@ -0,0 +1,65 @@
+/**
+ * @file res.c Get DNS Server IP using resolv
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _BSD_SOURCE 1
+#define _DEFAULT_SOURCE 1
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_dns.h>
+#include "dns.h"
+
+
+int get_resolv_dns(char *domain, size_t dsize, struct sa *nsv, uint32_t *n)
+{
+ struct __res_state state;
+ uint32_t i;
+ int ret, err;
+
+#ifdef OPENBSD
+ ret = res_init();
+ state = _res;
+#else
+ memset(&state, 0, sizeof(state));
+ ret = res_ninit(&state);
+#endif
+ if (0 != ret)
+ return ENOENT;
+
+ if (state.dnsrch[0])
+ str_ncpy(domain, state.dnsrch[0], dsize);
+ else if ((char *)state.defdname)
+ str_ncpy(domain, state.defdname, dsize);
+
+ if (!state.nscount) {
+ err = ENOENT;
+ goto out;
+ }
+
+ err = 0;
+ for (i=0; i<min(*n, (uint32_t)state.nscount) && !err; i++) {
+ struct sockaddr_in *addr = &state.nsaddr_list[i];
+ err |= sa_set_sa(&nsv[i], (struct sockaddr *)addr);
+ }
+ if (err)
+ goto out;
+
+ *n = i;
+
+ out:
+#ifdef OPENBSD
+#else
+ res_nclose(&state);
+#endif
+
+ return err;
+}
diff --git a/src/dns/rr.c b/src/dns/rr.c
new file mode 100644
index 0000000..cb06d5d
--- /dev/null
+++ b/src/dns/rr.c
@@ -0,0 +1,630 @@
+/**
+ * @file dns/rr.c DNS Resource Records
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_sa.h>
+#include <re_dns.h>
+
+
+static void rr_destructor(void *data)
+{
+ struct dnsrr *rr = data;
+
+ mem_deref(rr->name);
+
+ switch (rr->type) {
+
+ case DNS_TYPE_NS:
+ mem_deref(rr->rdata.ns.nsdname);
+ break;
+
+ case DNS_TYPE_CNAME:
+ mem_deref(rr->rdata.cname.cname);
+ break;
+
+ case DNS_TYPE_SOA:
+ mem_deref(rr->rdata.soa.mname);
+ mem_deref(rr->rdata.soa.rname);
+ break;
+
+ case DNS_TYPE_PTR:
+ mem_deref(rr->rdata.ptr.ptrdname);
+ break;
+
+ case DNS_TYPE_MX:
+ mem_deref(rr->rdata.mx.exchange);
+ break;
+
+ case DNS_TYPE_SRV:
+ mem_deref(rr->rdata.srv.target);
+ break;
+
+ case DNS_TYPE_NAPTR:
+ mem_deref(rr->rdata.naptr.flags);
+ mem_deref(rr->rdata.naptr.services);
+ mem_deref(rr->rdata.naptr.regexp);
+ mem_deref(rr->rdata.naptr.replace);
+ break;
+ }
+}
+
+
+/**
+ * Allocate a new DNS Resource Record (RR)
+ *
+ * @return Newly allocated Resource Record, or NULL if no memory
+ */
+struct dnsrr *dns_rr_alloc(void)
+{
+ return mem_zalloc(sizeof(struct dnsrr), rr_destructor);
+}
+
+
+/**
+ * Encode a DNS Resource Record
+ *
+ * @param mb Memory buffer to encode into
+ * @param rr DNS Resource Record
+ * @param ttl_offs TTL Offset
+ * @param ht_dname Domain name hash-table
+ * @param start Start position
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_rr_encode(struct mbuf *mb, const struct dnsrr *rr, int64_t ttl_offs,
+ struct hash *ht_dname, size_t start)
+{
+ uint32_t ttl;
+ uint16_t len;
+ size_t start_rdata;
+ int err = 0;
+
+ if (!mb || !rr)
+ return EINVAL;
+
+ ttl = (uint32_t)((rr->ttl > ttl_offs) ? (rr->ttl - ttl_offs) : 0);
+
+ err |= dns_dname_encode(mb, rr->name, ht_dname, start, true);
+ err |= mbuf_write_u16(mb, htons(rr->type));
+ err |= mbuf_write_u16(mb, htons(rr->dnsclass));
+ err |= mbuf_write_u32(mb, htonl(ttl));
+ err |= mbuf_write_u16(mb, htons(rr->rdlen));
+
+ start_rdata = mb->pos;
+
+ switch (rr->type) {
+
+ case DNS_TYPE_A:
+ err |= mbuf_write_u32(mb, htonl(rr->rdata.a.addr));
+ break;
+
+ case DNS_TYPE_NS:
+ err |= dns_dname_encode(mb, rr->rdata.ns.nsdname,
+ ht_dname, start, true);
+ break;
+
+ case DNS_TYPE_CNAME:
+ err |= dns_dname_encode(mb, rr->rdata.cname.cname,
+ ht_dname, start, true);
+ break;
+
+ case DNS_TYPE_SOA:
+ err |= dns_dname_encode(mb, rr->rdata.soa.mname,
+ ht_dname, start, true);
+ err |= dns_dname_encode(mb, rr->rdata.soa.rname,
+ ht_dname, start, true);
+ err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.serial));
+ err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.refresh));
+ err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.retry));
+ err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.expire));
+ err |= mbuf_write_u32(mb, htonl(rr->rdata.soa.ttlmin));
+ break;
+
+ case DNS_TYPE_PTR:
+ err |= dns_dname_encode(mb, rr->rdata.ptr.ptrdname,
+ ht_dname, start, true);
+ break;
+
+ case DNS_TYPE_MX:
+ err |= mbuf_write_u16(mb, htons(rr->rdata.mx.pref));
+ err |= dns_dname_encode(mb, rr->rdata.mx.exchange,
+ ht_dname, start, true);
+ break;
+
+ case DNS_TYPE_AAAA:
+ err |= mbuf_write_mem(mb, rr->rdata.aaaa.addr, 16);
+ break;
+
+ case DNS_TYPE_SRV:
+ err |= mbuf_write_u16(mb, htons(rr->rdata.srv.pri));
+ err |= mbuf_write_u16(mb, htons(rr->rdata.srv.weight));
+ err |= mbuf_write_u16(mb, htons(rr->rdata.srv.port));
+ err |= dns_dname_encode(mb, rr->rdata.srv.target,
+ ht_dname, start, false);
+ break;
+
+ case DNS_TYPE_NAPTR:
+ err |= mbuf_write_u16(mb, htons(rr->rdata.naptr.order));
+ err |= mbuf_write_u16(mb, htons(rr->rdata.naptr.pref));
+ err |= dns_cstr_encode(mb, rr->rdata.naptr.flags);
+ err |= dns_cstr_encode(mb, rr->rdata.naptr.services);
+ err |= dns_cstr_encode(mb, rr->rdata.naptr.regexp);
+ err |= dns_dname_encode(mb, rr->rdata.naptr.replace,
+ ht_dname, start, false);
+ break;
+
+ default:
+ err = EINVAL;
+ break;
+ }
+
+ len = mb->pos - start_rdata;
+ mb->pos = start_rdata - 2;
+ err |= mbuf_write_u16(mb, htons(len));
+ mb->pos += len;
+
+ return err;
+}
+
+
+/**
+ * Decode a DNS Resource Record (RR) from a memory buffer
+ *
+ * @param mb Memory buffer to decode from
+ * @param rr Pointer to allocated Resource Record
+ * @param start Start position
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_rr_decode(struct mbuf *mb, struct dnsrr **rr, size_t start)
+{
+ int err = 0;
+ struct dnsrr *lrr;
+
+ if (!mb || !rr)
+ return EINVAL;
+
+ lrr = dns_rr_alloc();
+ if (!lrr)
+ return ENOMEM;
+
+ err = dns_dname_decode(mb, &lrr->name, start);
+ if (err)
+ goto error;
+
+ if (mbuf_get_left(mb) < 10)
+ goto fmerr;
+
+ lrr->type = ntohs(mbuf_read_u16(mb));
+ lrr->dnsclass = ntohs(mbuf_read_u16(mb));
+ lrr->ttl = ntohl(mbuf_read_u32(mb));
+ lrr->rdlen = ntohs(mbuf_read_u16(mb));
+
+ if (mbuf_get_left(mb) < lrr->rdlen)
+ goto fmerr;
+
+ switch (lrr->type) {
+
+ case DNS_TYPE_A:
+ if (lrr->rdlen != 4)
+ goto fmerr;
+
+ lrr->rdata.a.addr = ntohl(mbuf_read_u32(mb));
+ break;
+
+ case DNS_TYPE_NS:
+ err = dns_dname_decode(mb, &lrr->rdata.ns.nsdname, start);
+ if (err)
+ goto error;
+
+ break;
+
+ case DNS_TYPE_CNAME:
+ err = dns_dname_decode(mb, &lrr->rdata.cname.cname, start);
+ if (err)
+ goto error;
+
+ break;
+
+ case DNS_TYPE_SOA:
+ err = dns_dname_decode(mb, &lrr->rdata.soa.mname, start);
+ if (err)
+ goto error;
+
+ err = dns_dname_decode(mb, &lrr->rdata.soa.rname, start);
+ if (err)
+ goto error;
+
+ if (mbuf_get_left(mb) < 20)
+ goto fmerr;
+
+ lrr->rdata.soa.serial = ntohl(mbuf_read_u32(mb));
+ lrr->rdata.soa.refresh = ntohl(mbuf_read_u32(mb));
+ lrr->rdata.soa.retry = ntohl(mbuf_read_u32(mb));
+ lrr->rdata.soa.expire = ntohl(mbuf_read_u32(mb));
+ lrr->rdata.soa.ttlmin = ntohl(mbuf_read_u32(mb));
+ break;
+
+ case DNS_TYPE_PTR:
+ err = dns_dname_decode(mb, &lrr->rdata.ptr.ptrdname, start);
+ if (err)
+ goto error;
+
+ break;
+
+ case DNS_TYPE_MX:
+ if (mbuf_get_left(mb) < 2)
+ goto fmerr;
+
+ lrr->rdata.mx.pref = ntohs(mbuf_read_u16(mb));
+
+ err = dns_dname_decode(mb, &lrr->rdata.mx.exchange, start);
+ if (err)
+ goto error;
+
+ break;
+
+ case DNS_TYPE_AAAA:
+ if (lrr->rdlen != 16)
+ goto fmerr;
+
+ err = mbuf_read_mem(mb, lrr->rdata.aaaa.addr, 16);
+ if (err)
+ goto error;
+ break;
+
+ case DNS_TYPE_SRV:
+ if (mbuf_get_left(mb) < 6)
+ goto fmerr;
+
+ lrr->rdata.srv.pri = ntohs(mbuf_read_u16(mb));
+ lrr->rdata.srv.weight = ntohs(mbuf_read_u16(mb));
+ lrr->rdata.srv.port = ntohs(mbuf_read_u16(mb));
+
+ err = dns_dname_decode(mb, &lrr->rdata.srv.target, start);
+ if (err)
+ goto error;
+
+ break;
+
+ case DNS_TYPE_NAPTR:
+ if (mbuf_get_left(mb) < 4)
+ goto fmerr;
+
+ lrr->rdata.naptr.order = ntohs(mbuf_read_u16(mb));
+ lrr->rdata.naptr.pref = ntohs(mbuf_read_u16(mb));
+
+ err = dns_cstr_decode(mb, &lrr->rdata.naptr.flags);
+ if (err)
+ goto error;
+
+ err = dns_cstr_decode(mb, &lrr->rdata.naptr.services);
+ if (err)
+ goto error;
+
+ err = dns_cstr_decode(mb, &lrr->rdata.naptr.regexp);
+ if (err)
+ goto error;
+
+ err = dns_dname_decode(mb, &lrr->rdata.naptr.replace, start);
+ if (err)
+ goto error;
+
+ break;
+
+ default:
+ mb->pos += lrr->rdlen;
+ break;
+ }
+
+ *rr = lrr;
+
+ return 0;
+
+ fmerr:
+ err = EINVAL;
+ error:
+ mem_deref(lrr);
+
+ return err;
+}
+
+
+/**
+ * Compare two DNS Resource Records
+ *
+ * @param rr1 First Resource Record
+ * @param rr2 Second Resource Record
+ * @param rdata If true, also compares Resource Record data
+ *
+ * @return True if match, false if not match
+ */
+bool dns_rr_cmp(const struct dnsrr *rr1, const struct dnsrr *rr2, bool rdata)
+{
+ if (!rr1 || !rr2)
+ return false;
+
+ if (rr1 == rr2)
+ return true;
+
+ if (rr1->type != rr2->type)
+ return false;
+
+ if (rr1->dnsclass != rr2->dnsclass)
+ return false;
+
+ if (str_casecmp(rr1->name, rr2->name))
+ return false;
+
+ if (!rdata)
+ return true;
+
+ switch (rr1->type) {
+
+ case DNS_TYPE_A:
+ if (rr1->rdata.a.addr != rr2->rdata.a.addr)
+ return false;
+
+ break;
+
+ case DNS_TYPE_NS:
+ if (str_casecmp(rr1->rdata.ns.nsdname, rr2->rdata.ns.nsdname))
+ return false;
+
+ break;
+
+ case DNS_TYPE_CNAME:
+ if (str_casecmp(rr1->rdata.cname.cname,
+ rr2->rdata.cname.cname))
+ return false;
+
+ break;
+
+ case DNS_TYPE_SOA:
+ if (str_casecmp(rr1->rdata.soa.mname, rr2->rdata.soa.mname))
+ return false;
+
+ if (str_casecmp(rr1->rdata.soa.rname, rr2->rdata.soa.rname))
+ return false;
+
+ if (rr1->rdata.soa.serial != rr2->rdata.soa.serial)
+ return false;
+
+ if (rr1->rdata.soa.refresh != rr2->rdata.soa.refresh)
+ return false;
+
+ if (rr1->rdata.soa.retry != rr2->rdata.soa.retry)
+ return false;
+
+ if (rr1->rdata.soa.expire != rr2->rdata.soa.expire)
+ return false;
+
+ if (rr1->rdata.soa.ttlmin != rr2->rdata.soa.ttlmin)
+ return false;
+
+ break;
+
+ case DNS_TYPE_PTR:
+ if (str_casecmp(rr1->rdata.ptr.ptrdname,
+ rr2->rdata.ptr.ptrdname))
+ return false;
+
+ break;
+
+ case DNS_TYPE_MX:
+ if (rr1->rdata.mx.pref != rr2->rdata.mx.pref)
+ return false;
+
+ if (str_casecmp(rr1->rdata.mx.exchange,
+ rr2->rdata.mx.exchange))
+ return false;
+
+ break;
+
+ case DNS_TYPE_AAAA:
+ if (memcmp(rr1->rdata.aaaa.addr, rr2->rdata.aaaa.addr, 16))
+ return false;
+
+ break;
+
+ case DNS_TYPE_SRV:
+ if (rr1->rdata.srv.pri != rr2->rdata.srv.pri)
+ return false;
+
+ if (rr1->rdata.srv.weight != rr2->rdata.srv.weight)
+ return false;
+
+ if (rr1->rdata.srv.port != rr2->rdata.srv.port)
+ return false;
+
+ if (str_casecmp(rr1->rdata.srv.target, rr2->rdata.srv.target))
+ return false;
+
+ break;
+
+ case DNS_TYPE_NAPTR:
+ if (rr1->rdata.naptr.order != rr2->rdata.naptr.order)
+ return false;
+
+ if (rr1->rdata.naptr.pref != rr2->rdata.naptr.pref)
+ return false;
+
+ /* todo check case sensitiveness */
+ if (str_casecmp(rr1->rdata.naptr.flags,
+ rr2->rdata.naptr.flags))
+ return false;
+
+ /* todo check case sensitiveness */
+ if (str_casecmp(rr1->rdata.naptr.services,
+ rr2->rdata.naptr.services))
+ return false;
+
+ /* todo check case sensitiveness */
+ if (str_casecmp(rr1->rdata.naptr.regexp,
+ rr2->rdata.naptr.regexp))
+ return false;
+
+ /* todo check case sensitiveness */
+ if (str_casecmp(rr1->rdata.naptr.replace,
+ rr2->rdata.naptr.replace))
+ return false;
+
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+
+/**
+ * Get the DNS Resource Record (RR) name
+ *
+ * @param type DNS Resource Record type
+ *
+ * @return DNS Resource Record name
+ */
+const char *dns_rr_typename(uint16_t type)
+{
+ switch (type) {
+
+ case DNS_TYPE_A: return "A";
+ case DNS_TYPE_NS: return "NS";
+ case DNS_TYPE_CNAME: return "CNAME";
+ case DNS_TYPE_SOA: return "SOA";
+ case DNS_TYPE_PTR: return "PTR";
+ case DNS_TYPE_MX: return "MX";
+ case DNS_TYPE_AAAA: return "AAAA";
+ case DNS_TYPE_SRV: return "SRV";
+ case DNS_TYPE_NAPTR: return "NAPTR";
+ case DNS_QTYPE_IXFR: return "IXFR";
+ case DNS_QTYPE_AXFR: return "AXFR";
+ case DNS_QTYPE_ANY: return "ANY";
+ default: return "??";
+ }
+}
+
+
+/**
+ * Get the DNS Resource Record (RR) class name
+ *
+ * @param dnsclass DNS Class
+ *
+ * @return DNS Class name
+ */
+const char *dns_rr_classname(uint16_t dnsclass)
+{
+ switch (dnsclass) {
+
+ case DNS_CLASS_IN: return "IN";
+ case DNS_QCLASS_ANY: return "ANY";
+ default: return "??";
+ }
+}
+
+
+/**
+ * Print a DNS Resource Record
+ *
+ * @param pf Print function
+ * @param rr DNS Resource Record
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dns_rr_print(struct re_printf *pf, const struct dnsrr *rr)
+{
+ static const size_t w = 24;
+ struct sa sa;
+ size_t n, l;
+ int err;
+
+ if (!pf || !rr)
+ return EINVAL;
+
+ l = str_len(rr->name);
+ n = (w > l) ? w - l : 0;
+
+ err = re_hprintf(pf, "%s.", rr->name);
+ while (n--)
+ err |= pf->vph(" ", 1, pf->arg);
+
+ err |= re_hprintf(pf, " %10lld %-4s %-7s ",
+ rr->ttl,
+ dns_rr_classname(rr->dnsclass),
+ dns_rr_typename(rr->type));
+
+ switch (rr->type) {
+
+ case DNS_TYPE_A:
+ sa_set_in(&sa, rr->rdata.a.addr, 0);
+ err |= re_hprintf(pf, "%j", &sa);
+ break;
+
+ case DNS_TYPE_NS:
+ err |= re_hprintf(pf, "%s.", rr->rdata.ns.nsdname);
+ break;
+
+ case DNS_TYPE_CNAME:
+ err |= re_hprintf(pf, "%s.", rr->rdata.cname.cname);
+ break;
+
+ case DNS_TYPE_SOA:
+ err |= re_hprintf(pf, "%s. %s. %u %u %u %u %u",
+ rr->rdata.soa.mname,
+ rr->rdata.soa.rname,
+ rr->rdata.soa.serial,
+ rr->rdata.soa.refresh,
+ rr->rdata.soa.retry,
+ rr->rdata.soa.expire,
+ rr->rdata.soa.ttlmin);
+ break;
+
+ case DNS_TYPE_PTR:
+ err |= re_hprintf(pf, "%s.", rr->rdata.ptr.ptrdname);
+ break;
+
+ case DNS_TYPE_MX:
+ err |= re_hprintf(pf, "%3u %s.", rr->rdata.mx.pref,
+ rr->rdata.mx.exchange);
+ break;
+
+ case DNS_TYPE_AAAA:
+ sa_set_in6(&sa, rr->rdata.aaaa.addr, 0);
+ err |= re_hprintf(pf, "%j", &sa);
+ break;
+
+ case DNS_TYPE_SRV:
+ err |= re_hprintf(pf, "%3u %3u %u %s.",
+ rr->rdata.srv.pri,
+ rr->rdata.srv.weight,
+ rr->rdata.srv.port,
+ rr->rdata.srv.target);
+ break;
+
+ case DNS_TYPE_NAPTR:
+ err |= re_hprintf(pf, "%3u %3u \"%s\" \"%s\" \"%s\" %s.",
+ rr->rdata.naptr.order,
+ rr->rdata.naptr.pref,
+ rr->rdata.naptr.flags,
+ rr->rdata.naptr.services,
+ rr->rdata.naptr.regexp,
+ rr->rdata.naptr.replace);
+ break;
+
+ default:
+ err |= re_hprintf(pf, "?");
+ break;
+ }
+
+ return err;
+}
diff --git a/src/dns/rrlist.c b/src/dns/rrlist.c
new file mode 100644
index 0000000..6e7c572
--- /dev/null
+++ b/src/dns/rrlist.c
@@ -0,0 +1,243 @@
+/**
+ * @file rrlist.c DNS Resource Records list
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_mbuf.h>
+#include <re_fmt.h>
+#include <re_dns.h>
+
+
+enum {
+ CNAME_RECURSE_MAX = 16,
+};
+
+
+struct sort {
+ uint16_t type;
+ uint32_t key;
+};
+
+
+static uint32_t sidx(const struct dnsrr *rr, uint32_t key)
+{
+ uint32_t addr[4];
+
+ switch (rr->type) {
+
+ case DNS_TYPE_A:
+ return rr->rdata.a.addr ^ key;
+
+ case DNS_TYPE_AAAA:
+ memcpy(addr, rr->rdata.aaaa.addr, 16);
+
+ return addr[0] ^ addr[1] ^ addr[2] ^ addr[3] ^ key;
+
+ case DNS_TYPE_SRV:
+ return ((hash_fast_str(rr->rdata.srv.target) & 0xfff) ^ key) +
+ rr->rdata.srv.weight;
+
+ default:
+ return 0;
+ }
+}
+
+
+static bool std_sort_handler(struct le *le1, struct le *le2, void *arg)
+{
+ struct dnsrr *rr1 = le1->data;
+ struct dnsrr *rr2 = le2->data;
+ struct sort *sort = arg;
+
+ if (sort->type != rr1->type)
+ return sort->type != rr2->type;
+
+ if (sort->type != rr2->type)
+ return true;
+
+ switch (sort->type) {
+
+ case DNS_TYPE_MX:
+ return rr1->rdata.mx.pref <= rr2->rdata.mx.pref;
+
+ case DNS_TYPE_SRV:
+ if (rr1->rdata.srv.pri == rr2->rdata.srv.pri)
+ return sidx(rr1, sort->key) >= sidx(rr2, sort->key);
+
+ return rr1->rdata.srv.pri < rr2->rdata.srv.pri;
+
+ case DNS_TYPE_NAPTR:
+ if (rr1->rdata.naptr.order == rr2->rdata.naptr.order)
+ return rr1->rdata.naptr.pref <= rr2->rdata.naptr.pref;
+
+ return rr1->rdata.naptr.order < rr2->rdata.naptr.order;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+
+static bool addr_sort_handler(struct le *le1, struct le *le2, void *arg)
+{
+ struct dnsrr *rr1 = le1->data;
+ struct dnsrr *rr2 = le2->data;
+ struct sort *sort = arg;
+
+ return sidx(rr1, sort->key) >= sidx(rr2, sort->key);
+}
+
+
+/**
+ * Sort a list of DNS Resource Records
+ *
+ * @param rrl DNS Resource Record list
+ * @param type DNS Record type
+ * @param key Sort key
+ */
+void dns_rrlist_sort(struct list *rrl, uint16_t type, size_t key)
+{
+ struct sort sort = {type, (uint32_t)key>>5};
+
+ list_sort(rrl, std_sort_handler, &sort);
+}
+
+
+/**
+ * Sort a list of A/AAAA DNS Resource Records
+ *
+ * @param rrl DNS Resource Record list
+ * @param key Sort key
+ */
+void dns_rrlist_sort_addr(struct list *rrl, size_t key)
+{
+ struct sort sort = {0, (uint32_t)key>>5};
+
+ list_sort(rrl, addr_sort_handler, &sort);
+}
+
+
+static struct dnsrr *rrlist_apply(struct list *rrl, const char *name,
+ uint16_t type1, uint16_t type2,
+ uint16_t dnsclass,
+ bool recurse, uint32_t depth,
+ dns_rrlist_h *rrlh, void *arg)
+{
+ struct le *le = list_head(rrl);
+
+ if (depth > CNAME_RECURSE_MAX)
+ return NULL;
+
+ while (le) {
+
+ struct dnsrr *rr = le->data;
+
+ le = le->next;
+
+ if (name && str_casecmp(name, rr->name))
+ continue;
+
+ if (type1 != DNS_QTYPE_ANY && type2 != DNS_QTYPE_ANY &&
+ rr->type != type1 && rr->type != type2 &&
+ (rr->type != DNS_TYPE_CNAME || !recurse))
+ continue;
+
+ if (dnsclass != DNS_QCLASS_ANY && rr->dnsclass != dnsclass)
+ continue;
+
+ if (!rrlh || rrlh(rr, arg))
+ return rr;
+
+ if (recurse &&
+ DNS_QTYPE_ANY != type1 && DNS_QTYPE_ANY != type2 &&
+ DNS_TYPE_CNAME != type1 && DNS_TYPE_CNAME != type2 &&
+ DNS_TYPE_CNAME == rr->type) {
+ rr = rrlist_apply(rrl, rr->rdata.cname.cname, type1,
+ type2, dnsclass, recurse, ++depth,
+ rrlh, arg);
+ if (rr)
+ return rr;
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Apply a function handler to a list of DNS Resource Records
+ *
+ * @param rrl DNS Resource Record list
+ * @param name If set, filter on domain name
+ * @param type If not DNS_QTYPE_ANY, filter on record type
+ * @param dnsclass If not DNS_QCLASS_ANY, filter on DNS class
+ * @param recurse Cname recursion
+ * @param rrlh Resource record handler
+ * @param arg Handler argument
+ *
+ * @return Matching Resource Record or NULL
+ */
+struct dnsrr *dns_rrlist_apply(struct list *rrl, const char *name,
+ uint16_t type, uint16_t dnsclass,
+ bool recurse, dns_rrlist_h *rrlh, void *arg)
+{
+ return rrlist_apply(rrl, name, type, type, dnsclass,
+ recurse, 0, rrlh, arg);
+}
+
+
+/**
+ * Apply a function handler to a list of DNS Resource Records (two types)
+ *
+ * @param rrl DNS Resource Record list
+ * @param name If set, filter on domain name
+ * @param type1 If not DNS_QTYPE_ANY, filter on record type
+ * @param type2 If not DNS_QTYPE_ANY, filter on record type
+ * @param dnsclass If not DNS_QCLASS_ANY, filter on DNS class
+ * @param recurse Cname recursion
+ * @param rrlh Resource record handler
+ * @param arg Handler argument
+ *
+ * @return Matching Resource Record or NULL
+ */
+struct dnsrr *dns_rrlist_apply2(struct list *rrl, const char *name,
+ uint16_t type1, uint16_t type2,
+ uint16_t dnsclass, bool recurse,
+ dns_rrlist_h *rrlh, void *arg)
+{
+ return rrlist_apply(rrl, name, type1, type2, dnsclass,
+ recurse, 0, rrlh, arg);
+}
+
+
+static bool find_handler(struct dnsrr *rr, void *arg)
+{
+ uint16_t type = *(uint16_t *)arg;
+
+ return rr->type == type;
+}
+
+
+/**
+ * Find a DNS Resource Record in a list
+ *
+ * @param rrl Resource Record list
+ * @param name If set, filter on domain name
+ * @param type If not DNS_QTYPE_ANY, filter on record type
+ * @param dnsclass If not DNS_QCLASS_ANY, filter on DNS class
+ * @param recurse Cname recursion
+ *
+ * @return Matching Resource Record or NULL
+ */
+struct dnsrr *dns_rrlist_find(struct list *rrl, const char *name,
+ uint16_t type, uint16_t dnsclass, bool recurse)
+{
+ return rrlist_apply(rrl, name, type, type, dnsclass,
+ recurse, 0, find_handler, &type);
+}
diff --git a/src/dns/win32/srv.c b/src/dns/win32/srv.c
new file mode 100644
index 0000000..0ca0749
--- /dev/null
+++ b/src/dns/win32/srv.c
@@ -0,0 +1,100 @@
+/**
+ * @file win32/srv.c Get DNS Server IP code for Windows
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <winsock2.h>
+#include <iphlpapi.h>
+#include <io.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_dns.h>
+#include "../dns.h"
+
+
+#define DEBUG_MODULE "win32/srv"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+int get_windns(char *domain, size_t dsize, struct sa *srvv, uint32_t *n)
+{
+ FIXED_INFO * FixedInfo = NULL;
+ ULONG ulOutBufLen;
+ DWORD dwRetVal;
+ IP_ADDR_STRING * pIPAddr;
+ HANDLE hLib;
+ union {
+ FARPROC proc;
+ DWORD (WINAPI *_GetNetworkParams)(FIXED_INFO*, DWORD*);
+ } u;
+ uint32_t i;
+ int err;
+
+ if (!srvv || !n || !*n)
+ return EINVAL;
+
+ hLib = LoadLibrary(TEXT("iphlpapi.dll"));
+ if (!hLib)
+ return ENOSYS;
+
+ u.proc = GetProcAddress(hLib, TEXT("GetNetworkParams"));
+ if (!u.proc) {
+ err = ENOSYS;
+ goto out;
+ }
+
+ FixedInfo = (FIXED_INFO *)GlobalAlloc(GPTR, sizeof( FIXED_INFO ));
+ ulOutBufLen = sizeof( FIXED_INFO );
+
+ if (ERROR_BUFFER_OVERFLOW == (*u._GetNetworkParams)(FixedInfo,
+ &ulOutBufLen)) {
+ GlobalFree( FixedInfo );
+ FixedInfo = (FIXED_INFO *)GlobalAlloc(GPTR, ulOutBufLen);
+ }
+
+ if ((dwRetVal = (*u._GetNetworkParams)( FixedInfo, &ulOutBufLen ))) {
+ DEBUG_WARNING("couldn't get network params (%d)\n", dwRetVal);
+ err = ENOENT;
+ goto out;
+ }
+
+ str_ncpy(domain, FixedInfo->DomainName, dsize);
+
+#if 0
+ printf( "Host Name: %s\n", FixedInfo->HostName);
+ printf( "Domain Name: %s\n", FixedInfo->DomainName);
+ printf( "DNS Servers:\n" );
+ printf( "\t%s\n", FixedInfo->DnsServerList.IpAddress.String );
+#endif
+
+ i = 0;
+ pIPAddr = &FixedInfo->DnsServerList;
+ while (pIPAddr && strlen(pIPAddr->IpAddress.String) > 0) {
+ err = sa_set_str(&srvv[i], pIPAddr->IpAddress.String,
+ DNS_PORT);
+ if (err) {
+ DEBUG_WARNING("sa_set_str: %s (%m)\n",
+ pIPAddr->IpAddress.String, err);
+ }
+ DEBUG_INFO("dns ip %u: %j\n", i, &srvv[i]);
+ ++i;
+ pIPAddr = pIPAddr ->Next;
+
+ if (i >= *n)
+ break;
+ }
+
+ *n = i;
+ DEBUG_INFO("got %u nameservers\n", i);
+ err = i>0 ? 0 : ENOENT;
+
+ out:
+ if (FixedInfo)
+ GlobalFree(FixedInfo);
+ FreeLibrary(hLib);
+ return err;
+}
diff --git a/src/fmt/ch.c b/src/fmt/ch.c
new file mode 100644
index 0000000..24c4017
--- /dev/null
+++ b/src/fmt/ch.c
@@ -0,0 +1,29 @@
+/**
+ * @file ch.c Character format functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+
+
+/**
+ * Convert an ASCII hex character to binary format
+ *
+ * @param ch ASCII hex character
+ *
+ * @return Binary value
+ */
+uint8_t ch_hex(char ch)
+{
+ if ('0' <= ch && ch <= '9')
+ return ch - '0';
+
+ else if ('A' <= ch && ch <= 'F')
+ return ch - 'A' + 10;
+
+ else if ('a' <= ch && ch <= 'f')
+ return ch - 'a' + 10;
+
+ return 0;
+}
diff --git a/src/fmt/hexdump.c b/src/fmt/hexdump.c
new file mode 100644
index 0000000..ca66e63
--- /dev/null
+++ b/src/fmt/hexdump.c
@@ -0,0 +1,57 @@
+/**
+ * @file hexdump.c Hexadecimal dumping
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <ctype.h>
+#include <re_types.h>
+#include <re_fmt.h>
+
+
+/**
+ * Hexadecimal dump of binary buffer. Similar output to HEXDUMP(1)
+ *
+ * @param f File stream for output (e.g. stderr, stdout)
+ * @param p Pointer to data
+ * @param len Number of bytes
+ */
+void hexdump(FILE *f, const void *p, size_t len)
+{
+ const uint8_t *buf = p;
+ uint32_t j;
+ size_t i;
+
+ if (!f || !buf)
+ return;
+
+ for (i=0; i < len; i += 16) {
+
+ (void)re_fprintf(f, "%08x ", i);
+
+ for (j=0; j<16; j++) {
+ const size_t pos = i+j;
+ if (pos < len)
+ (void)re_fprintf(f, " %02x", buf[pos]);
+ else
+ (void)re_fprintf(f, " ");
+
+ if (j == 7)
+ (void)re_fprintf(f, " ");
+ }
+
+ (void)re_fprintf(f, " |");
+
+ for (j=0; j<16; j++) {
+ const size_t pos = i+j;
+ uint8_t v;
+ if (pos >= len)
+ break;
+ v = buf[pos];
+ (void)re_fprintf(f, "%c", isprint(v) ? v : '.');
+ if (j == 7)
+ (void)re_fprintf(f, " ");
+ }
+
+ (void)re_fprintf(f, "|\n");
+ }
+}
diff --git a/src/fmt/mod.mk b/src/fmt/mod.mk
new file mode 100644
index 0000000..04d1736
--- /dev/null
+++ b/src/fmt/mod.mk
@@ -0,0 +1,16 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += fmt/ch.c
+SRCS += fmt/hexdump.c
+SRCS += fmt/pl.c
+SRCS += fmt/print.c
+SRCS += fmt/prm.c
+SRCS += fmt/regex.c
+SRCS += fmt/str.c
+SRCS += fmt/str_error.c
+SRCS += fmt/time.c
+SRCS += fmt/unicode.c
diff --git a/src/fmt/pl.c b/src/fmt/pl.c
new file mode 100644
index 0000000..a35fe77
--- /dev/null
+++ b/src/fmt/pl.c
@@ -0,0 +1,505 @@
+/**
+ * @file pl.c Pointer-length functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <ctype.h>
+#include <sys/types.h>
+#ifdef HAVE_STRINGS_H
+#define __EXTENSIONS__ 1
+#include <strings.h>
+#endif
+#include <string.h>
+#include <stdlib.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_fmt.h>
+
+
+/** Pointer-length NULL initialiser */
+const struct pl pl_null = {NULL, 0};
+
+
+/**
+ * Initialise a pointer-length object from a NULL-terminated string
+ *
+ * @param pl Pointer-length object to be initialised
+ * @param str NULL-terminated string
+ */
+void pl_set_str(struct pl *pl, const char *str)
+{
+ if (!pl || !str)
+ return;
+
+ pl->p = str;
+ pl->l = strlen(str);
+}
+
+
+/**
+ * Initialise a pointer-length object from current position and
+ * length of a memory buffer
+ *
+ * @param pl Pointer-length object to be initialised
+ * @param mb Memory buffer
+ */
+void pl_set_mbuf(struct pl *pl, const struct mbuf *mb)
+{
+ if (!pl || !mb)
+ return;
+
+ pl->p = (char *)mbuf_buf(mb);
+ pl->l = mbuf_get_left(mb);
+}
+
+
+/**
+ * Convert a pointer-length object to a numeric 32-bit value
+ *
+ * @param pl Pointer-length object
+ *
+ * @return 32-bit value
+ */
+uint32_t pl_u32(const struct pl *pl)
+{
+ uint32_t v=0, mul=1;
+ const char *p;
+
+ if (!pl || !pl->p)
+ return 0;
+
+ p = &pl->p[pl->l];
+ while (p > pl->p) {
+ const uint8_t c = *--p - '0';
+ if (c > 9)
+ return 0;
+ v += mul * c;
+ mul *= 10;
+ }
+
+ return v;
+}
+
+
+/**
+ * Convert a hex pointer-length object to a numeric 32-bit value
+ *
+ * @param pl Pointer-length object
+ *
+ * @return 32-bit value
+ */
+uint32_t pl_x32(const struct pl *pl)
+{
+ uint32_t v=0, mul=1;
+ const char *p;
+
+ if (!pl || !pl->p)
+ return 0;
+
+ p = &pl->p[pl->l];
+ while (p > pl->p) {
+
+ const char ch = *--p;
+ uint8_t c;
+
+ if ('0' <= ch && ch <= '9')
+ c = ch - '0';
+ else if ('A' <= ch && ch <= 'F')
+ c = ch - 'A' + 10;
+ else if ('a' <= ch && ch <= 'f')
+ c = ch - 'a' + 10;
+ else
+ return 0;
+
+ v += mul * c;
+ mul *= 16;
+ }
+
+ return v;
+}
+
+
+/**
+ * Convert a pointer-length object to a numeric 64-bit value
+ *
+ * @param pl Pointer-length object
+ *
+ * @return 64-bit value
+ */
+uint64_t pl_u64(const struct pl *pl)
+{
+ uint64_t v=0, mul=1;
+ const char *p;
+
+ if (!pl || !pl->p)
+ return 0;
+
+ p = &pl->p[pl->l];
+ while (p > pl->p) {
+ const uint8_t c = *--p - '0';
+ if (c > 9)
+ return 0;
+ v += mul * c;
+ mul *= 10;
+ }
+
+ return v;
+}
+
+
+/**
+ * Convert a hex pointer-length object to a numeric 64-bit value
+ *
+ * @param pl Pointer-length object
+ *
+ * @return 64-bit value
+ */
+uint64_t pl_x64(const struct pl *pl)
+{
+ uint64_t v=0, mul=1;
+ const char *p;
+
+ if (!pl || !pl->p)
+ return 0;
+
+ p = &pl->p[pl->l];
+ while (p > pl->p) {
+
+ const char ch = *--p;
+ uint8_t c;
+
+ if ('0' <= ch && ch <= '9')
+ c = ch - '0';
+ else if ('A' <= ch && ch <= 'F')
+ c = ch - 'A' + 10;
+ else if ('a' <= ch && ch <= 'f')
+ c = ch - 'a' + 10;
+ else
+ return 0;
+
+ v += mul * c;
+ mul *= 16;
+ }
+
+ return v;
+}
+
+
+/**
+ * Convert a pointer-length object to floating point representation.
+ * Both positive and negative numbers are supported, a string with a
+ * minus sign ('-') is treated as a negative number.
+ *
+ * @param pl Pointer-length object
+ *
+ * @return Double value
+ */
+double pl_float(const struct pl *pl)
+{
+ double v=0, mul=1;
+ const char *p;
+ bool neg = false;
+
+ if (!pl || !pl->p)
+ return 0;
+
+ p = &pl->p[pl->l];
+
+ while (p > pl->p) {
+
+ const char ch = *--p;
+
+ if ('0' <= ch && ch <= '9') {
+ v += mul * (ch - '0');
+ mul *= 10;
+ }
+ else if (ch == '.') {
+ v /= mul;
+ mul = 1;
+ }
+ else if (ch == '-' && p == pl->p) {
+ neg = true;
+ }
+ else {
+ return 0;
+ }
+ }
+
+ return neg ? -v : v;
+}
+
+
+/**
+ * Check if pointer-length object is set
+ *
+ * @param pl Pointer-length object
+ *
+ * @return true if set, false if not set
+ */
+bool pl_isset(const struct pl *pl)
+{
+ return pl ? pl->p && pl->l : false;
+}
+
+
+/**
+ * Copy a pointer-length object to a NULL-terminated string
+ *
+ * @param pl Pointer-length object
+ * @param str Buffer for NULL-terminated string
+ * @param size Size of buffer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int pl_strcpy(const struct pl *pl, char *str, size_t size)
+{
+ size_t len;
+
+ if (!pl || !pl->p || !str || !size)
+ return EINVAL;
+
+ len = min(pl->l, size-1);
+
+ memcpy(str, pl->p, len);
+ str[len] = '\0';
+
+ return 0;
+}
+
+
+/**
+ * Duplicate a pointer-length object to a NULL-terminated string
+ *
+ * @param dst Pointer to destination string (set on return)
+ * @param src Source pointer-length object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int pl_strdup(char **dst, const struct pl *src)
+{
+ char *p;
+
+ if (!dst || !src || !src->p)
+ return EINVAL;
+
+ p = mem_alloc(src->l+1, NULL);
+ if (!p)
+ return ENOMEM;
+
+ memcpy(p, src->p, src->l);
+ p[src->l] = '\0';
+
+ *dst = p;
+
+ return 0;
+}
+
+
+/**
+ * Duplicate a pointer-length object to a new pointer-length object
+ *
+ * @param dst Destination pointer-length object (set on return)
+ * @param src Source pointer-length object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int pl_dup(struct pl *dst, const struct pl *src)
+{
+ char *p;
+
+ if (!dst || !src || !src->p)
+ return EINVAL;
+
+ p = mem_alloc(src->l, NULL);
+ if (!p)
+ return ENOMEM;
+
+ memcpy(p, src->p, src->l);
+
+ dst->p = p;
+ dst->l = src->l;
+
+ return 0;
+}
+
+
+/**
+ * Compare a pointer-length object with a NULL-terminated string
+ * (case-sensitive)
+ *
+ * @param pl Pointer-length object
+ * @param str NULL-terminated string
+ *
+ * @return 0 if match, otherwise errorcode
+ */
+int pl_strcmp(const struct pl *pl, const char *str)
+{
+ struct pl s;
+
+ if (!pl || !str)
+ return EINVAL;
+
+ pl_set_str(&s, str);
+
+ return pl_cmp(pl, &s);
+}
+
+
+/**
+ * Compare a pointer-length object with a NULL-terminated string
+ * (case-insensitive)
+ *
+ * @param pl Pointer-length object
+ * @param str NULL-terminated string
+ *
+ * @return 0 if match, otherwise errorcode
+ */
+int pl_strcasecmp(const struct pl *pl, const char *str)
+{
+ struct pl s;
+
+ if (!pl || !str)
+ return EINVAL;
+
+ pl_set_str(&s, str);
+
+ return pl_casecmp(pl, &s);
+}
+
+
+/**
+ * Compare two pointer-length objects (case-sensitive)
+ *
+ * @param pl1 First pointer-length object
+ * @param pl2 Second pointer-length object
+ *
+ * @return 0 if match, otherwise errorcode
+ */
+int pl_cmp(const struct pl *pl1, const struct pl *pl2)
+{
+ if (!pl1 || !pl2)
+ return EINVAL;
+
+ /* Different length -> no match */
+ if (pl1->l != pl2->l)
+ return EINVAL;
+
+ /* Zero-length strings are always identical */
+ if (pl1->l == 0)
+ return 0;
+
+ /*
+ * ~35% speed increase for fmt/pl test
+ */
+
+ /* The two pl's are the same */
+ if (pl1 == pl2)
+ return 0;
+
+ /* Two different pl's pointing to same string */
+ if (pl1->p == pl2->p)
+ return 0;
+
+ return 0 == memcmp(pl1->p, pl2->p, pl1->l) ? 0 : EINVAL;
+}
+
+
+#ifndef HAVE_STRINGS_H
+static int casecmp(const struct pl *pl, const char *str)
+{
+ size_t i = 0;
+
+#define LOWER(d) ((d) | 0x20202020)
+ const uint32_t *p1 = (uint32_t *)pl->p;
+ const uint32_t *p2 = (uint32_t *)str;
+ const size_t len = pl->l & ~0x3;
+
+ /* Skip any unaligned pointers */
+ if (((size_t)pl->p) & (sizeof(void *) - 1))
+ goto next;
+ if (((size_t)str) & (sizeof(void *) - 1))
+ goto next;
+
+ /* Compare word-wise */
+ for (; i<len; i+=4) {
+ if (LOWER(*p1++) != LOWER(*p2++))
+ return EINVAL;
+ }
+
+ next:
+ /* Compare byte-wise */
+ for (; i<pl->l; i++) {
+ if (tolower(pl->p[i]) != tolower(str[i]))
+ return EINVAL;
+ }
+
+ return 0;
+}
+#endif
+
+
+/**
+ * Compare two pointer-length objects (case-insensitive)
+ *
+ * @param pl1 First pointer-length object
+ * @param pl2 Second pointer-length object
+ *
+ * @return 0 if match, otherwise errorcode
+ */
+int pl_casecmp(const struct pl *pl1, const struct pl *pl2)
+{
+ if (!pl1 || !pl2)
+ return EINVAL;
+
+ /* Different length -> no match */
+ if (pl1->l != pl2->l)
+ return EINVAL;
+
+ /* Zero-length strings are always identical */
+ if (pl1->l == 0)
+ return 0;
+
+ /*
+ * ~35% speed increase for fmt/pl test
+ */
+
+ /* The two pl's are the same */
+ if (pl1 == pl2)
+ return 0;
+
+ /* Two different pl's pointing to same string */
+ if (pl1->p == pl2->p)
+ return 0;
+
+#ifdef HAVE_STRINGS_H
+ return 0 == strncasecmp(pl1->p, pl2->p, pl1->l) ? 0 : EINVAL;
+#else
+ return casecmp(pl1, pl2->p);
+#endif
+}
+
+
+/**
+ * Locate character in pointer-length string
+ *
+ * @param pl Pointer-length string
+ * @param c Character to locate
+ *
+ * @return Pointer to first char if found, otherwise NULL
+ */
+const char *pl_strchr(const struct pl *pl, char c)
+{
+ const char *p, *end;
+
+ if (!pl)
+ return NULL;
+
+ end = pl->p + pl->l;
+ for (p = pl->p; p < end; p++) {
+ if (*p == c)
+ return p;
+ }
+
+ return NULL;
+}
diff --git a/src/fmt/print.c b/src/fmt/print.c
new file mode 100644
index 0000000..85e9ebf
--- /dev/null
+++ b/src/fmt/print.c
@@ -0,0 +1,790 @@
+/**
+ * @file fmt/print.c Formatted printing
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_sa.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <math.h>
+#ifdef _MSC_VER
+#include <float.h>
+#ifndef isinf
+#define isinf(d) (!_finite(d))
+#endif
+#ifndef isnan
+#define isnan(d) _isnan(d)
+#endif
+#endif
+#ifdef SOLARIS
+#include <ieeefp.h>
+#undef isinf
+#define isinf(a) (fpclass((a)) == FP_NINF || fpclass((a)) == FP_PINF)
+#undef isnan
+#define isnan(a) isnand((a))
+#endif
+
+
+enum length_modifier {
+ LENMOD_NONE = 0,
+ LENMOD_LONG = 1,
+ LENMOD_LONG_LONG = 2,
+ LENMOD_SIZE = 42,
+};
+
+enum {
+ DEC_SIZE = 42,
+ NUM_SIZE = 64
+};
+
+static const char prfx_neg[] = "-";
+static const char prfx_hex[] = "0x";
+static const char str_nil[] = "(nil)";
+
+
+static int write_padded(const char *p, size_t sz, size_t pad, char pch,
+ bool plr, const char *prfx, re_vprintf_h *vph,
+ void *arg)
+{
+ const size_t prfx_len = str_len(prfx);
+ int err = 0;
+
+ pad -= MIN(pad, prfx_len);
+
+ if (prfx && pch == '0')
+ err |= vph(prfx, prfx_len, arg);
+
+ while (!plr && (pad-- > sz))
+ err |= vph(&pch, 1, arg);
+
+ if (prfx && pch != '0')
+ err |= vph(prfx, prfx_len, arg);
+
+ if (p && sz)
+ err |= vph(p, sz, arg);
+
+ while (plr && pad-- > sz)
+ err |= vph(&pch, 1, arg);
+
+ return err;
+}
+
+
+static uint32_t local_itoa(char *buf, uint64_t n, uint8_t base, bool uc)
+{
+ char c, *p = buf + NUM_SIZE;
+ uint32_t len = 1;
+ const char a = uc ? 'A' : 'a';
+
+ *--p = '\0';
+ do {
+ const uint64_t dv = n / base;
+ const uint64_t mul = dv * base;
+
+ c = (char)(n - mul);
+
+ if (c < 10)
+ *--p = '0' + c;
+ else
+ *--p = a + (c - 10);
+
+ n = dv;
+ ++len;
+
+ } while (n != 0);
+
+ memmove(buf, p, len);
+
+ return len - 1;
+}
+
+
+static size_t local_ftoa(char *buf, double n, size_t dp)
+{
+ char *p = buf;
+ long long a = (long long)n;
+ double b = n - (double)a;
+
+ b = (b < 0) ? -b : b;
+
+ /* integral part */
+ p += local_itoa(p, (a < 0) ? -a : a, 10, false);
+
+ *p++ = '.';
+
+ /* decimal digits */
+ while (dp--) {
+ char v;
+
+ b *= 10;
+ v = (char)b;
+ b -= v;
+
+ *p++ = '0' + (char)v;
+ }
+
+ *p = '\0';
+
+ return p - buf;
+}
+
+
+/**
+ * Print a formatted string
+ *
+ * @param fmt Formatted string
+ * @param ap Variable argument
+ * @param vph Print handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * Extensions:
+ *
+ * <pre>
+ * %b (char *, size_t) Buffer string with pointer and length
+ * %r (struct pl) Pointer-length object
+ * %w (uint8_t *, size_t) Binary buffer to hexadecimal format
+ * %j (struct sa *) Socket address - address part only
+ * %J (struct sa *) Socket address and port - like 1.2.3.4:1234
+ * %H (re_printf_h *, void *) Print handler with argument
+ * %v (char *fmt, va_list *) Variable argument list
+ * %m (int) Describe an error code
+ * </pre>
+ *
+ * Reserved for the future:
+ *
+ * %k
+ * %y
+ *
+ */
+int re_vhprintf(const char *fmt, va_list ap, re_vprintf_h *vph, void *arg)
+{
+ uint8_t base, *bptr;
+ char pch, ch, num[NUM_SIZE], addr[64], msg[256];
+ enum length_modifier lenmod = LENMOD_NONE;
+ struct re_printf pf;
+ bool fm = false, plr = false;
+ const struct pl *pl;
+ size_t pad = 0, fpad = -1, len, i;
+ const char *str, *p = fmt, *p0 = fmt;
+ const struct sa *sa;
+ re_printf_h *ph;
+ void *ph_arg;
+ va_list *apl;
+ int err = 0;
+ void *ptr;
+ uint64_t n;
+ int64_t sn;
+ bool uc = false;
+ double dbl;
+
+ if (!fmt || !vph)
+ return EINVAL;
+
+ pf.vph = vph;
+ pf.arg = arg;
+
+ for (;*p && !err; p++) {
+
+ if (!fm) {
+ if (*p != '%')
+ continue;
+
+ pch = ' ';
+ plr = false;
+ pad = 0;
+ fpad = -1;
+ lenmod = LENMOD_NONE;
+ uc = false;
+
+ if (p > p0)
+ err |= vph(p0, p - p0, arg);
+
+ fm = true;
+ continue;
+ }
+
+ fm = false;
+ base = 10;
+
+ switch (*p) {
+
+ case '-':
+ plr = true;
+ fm = true;
+ break;
+
+ case '.':
+ fpad = pad;
+ pad = 0;
+ fm = true;
+ break;
+
+ case '%':
+ ch = '%';
+
+ err |= vph(&ch, 1, arg);
+ break;
+
+ case 'b':
+ str = va_arg(ap, const char *);
+ len = va_arg(ap, size_t);
+
+ err |= write_padded(str, str ? len : 0, pad, ' ',
+ plr, NULL, vph, arg);
+ break;
+
+ case 'c':
+ ch = va_arg(ap, int);
+
+ err |= write_padded(&ch, 1, pad, ' ', plr, NULL,
+ vph, arg);
+ break;
+
+ case 'd':
+ case 'i':
+ switch (lenmod) {
+
+ case LENMOD_SIZE:
+ sn = va_arg(ap, ssize_t);
+ break;
+
+ default:
+ case LENMOD_LONG_LONG:
+ sn = va_arg(ap, signed long long);
+ break;
+
+ case LENMOD_LONG:
+ sn = va_arg(ap, signed long);
+ break;
+
+ case LENMOD_NONE:
+ sn = va_arg(ap, signed);
+ break;
+ }
+
+ len = local_itoa(num, (sn < 0) ? -sn : sn, base,
+ false);
+
+ err |= write_padded(num, len, pad,
+ plr ? ' ' : pch, plr,
+ (sn < 0) ? prfx_neg : NULL,
+ vph, arg);
+ break;
+
+ case 'f':
+ case 'F':
+ dbl = va_arg(ap, double);
+
+ if (fpad == (size_t)-1) {
+ fpad = pad;
+ pad = 0;
+ }
+
+ if (isinf(dbl)) {
+ err |= write_padded("inf", 3, fpad,
+ ' ', plr, NULL, vph, arg);
+ }
+ else if (isnan(dbl)) {
+ err |= write_padded("nan", 3, fpad,
+ ' ', plr, NULL, vph, arg);
+ }
+ else {
+ len = local_ftoa(num, dbl,
+ pad ? min(pad, DEC_SIZE) : 6);
+
+ err |= write_padded(num, len, fpad,
+ plr ? ' ' : pch, plr,
+ (dbl<0) ? prfx_neg : NULL,
+ vph, arg);
+ }
+ break;
+
+ case 'H':
+ ph = va_arg(ap, re_printf_h *);
+ ph_arg = va_arg(ap, void *);
+
+ if (ph)
+ err |= ph(&pf, ph_arg);
+ break;
+
+ case 'l':
+ ++lenmod;
+ fm = true;
+ break;
+
+ case 'm':
+ str = str_error(va_arg(ap, int), msg, sizeof(msg));
+ err |= write_padded(str, str_len(str), pad,
+ ' ', plr, NULL, vph, arg);
+ break;
+
+ case 'p':
+ ptr = va_arg(ap, void *);
+
+ if (ptr) {
+ len = local_itoa(num, (unsigned long int)ptr,
+ 16, false);
+ err |= write_padded(num, len, pad,
+ plr ? ' ' : pch, plr,
+ prfx_hex, vph, arg);
+ }
+ else {
+ err |= write_padded(str_nil,
+ sizeof(str_nil) - 1,
+ pad, ' ', plr, NULL,
+ vph, arg);
+ }
+ break;
+
+ case 'r':
+ pl = va_arg(ap, const struct pl *);
+
+ err |= write_padded(pl ? pl->p : NULL,
+ (pl && pl->p) ? pl->l : 0,
+ pad, ' ', plr, NULL, vph, arg);
+ break;
+
+ case 's':
+ str = va_arg(ap, const char *);
+ err |= write_padded(str, str_len(str), pad,
+ ' ', plr, NULL, vph, arg);
+ break;
+
+ case 'X':
+ uc = true;
+ /*@fallthrough@*/
+ case 'x':
+ base = 16;
+ /*@fallthrough@*/
+ case 'u':
+ switch (lenmod) {
+
+ case LENMOD_SIZE:
+ n = va_arg(ap, size_t);
+ break;
+
+ default:
+ case LENMOD_LONG_LONG:
+ n = va_arg(ap, unsigned long long);
+ break;
+
+ case LENMOD_LONG:
+ n = va_arg(ap, unsigned long);
+ break;
+
+ case LENMOD_NONE:
+ n = va_arg(ap, unsigned);
+ break;
+ }
+
+ len = local_itoa(num, n, base, uc);
+
+ err |= write_padded(num, len, pad,
+ plr ? ' ' : pch, plr, NULL,
+ vph, arg);
+ break;
+
+ case 'v':
+ str = va_arg(ap, char *);
+ apl = va_arg(ap, va_list *);
+
+ if (!str || !apl)
+ break;
+
+ err |= re_vhprintf(str, *apl, vph, arg);
+ break;
+
+ case 'W':
+ uc = true;
+ /*@fallthrough@*/
+ case 'w':
+ bptr = va_arg(ap, uint8_t *);
+ len = va_arg(ap, size_t);
+
+ len = bptr ? len : 0;
+ pch = plr ? ' ' : pch;
+
+ while (!plr && pad-- > (len * 2))
+ err |= vph(&pch, 1, arg);
+
+ for (i=0; i<len; i++) {
+ const uint8_t v = *bptr++;
+ uint32_t l = local_itoa(num, v, 16, uc);
+ err |= write_padded(num, l, 2, '0',
+ false, NULL, vph, arg);
+ }
+
+ while (plr && pad-- > (len * 2))
+ err |= vph(&pch, 1, arg);
+
+ break;
+
+ case 'z':
+ lenmod = LENMOD_SIZE;
+ fm = true;
+ break;
+
+ case 'j':
+ sa = va_arg(ap, struct sa *);
+ if (!sa)
+ break;
+ if (sa_ntop(sa, addr, sizeof(addr))) {
+ err |= write_padded("?", 1, pad, ' ',
+ plr, NULL, vph, arg);
+ break;
+ }
+ err |= write_padded(addr, strlen(addr), pad, ' ',
+ plr, NULL, vph, arg);
+ break;
+
+
+ case 'J':
+ sa = va_arg(ap, struct sa *);
+ if (!sa)
+ break;
+ if (sa_ntop(sa, addr, sizeof(addr))) {
+ err |= write_padded("?", 1, pad, ' ',
+ plr, NULL, vph, arg);
+ break;
+ }
+
+#ifdef HAVE_INET6
+ if (AF_INET6 == sa_af(sa)) {
+ ch = '[';
+ err |= vph(&ch, 1, arg);
+ }
+#endif
+ err |= write_padded(addr, strlen(addr), pad, ' ',
+ plr, NULL, vph, arg);
+#ifdef HAVE_INET6
+ if (AF_INET6 == sa_af(sa)) {
+ ch = ']';
+ err |= vph(&ch, 1, arg);
+ }
+#endif
+
+ ch = ':';
+ err |= vph(&ch, 1, arg);
+ len = local_itoa(num, sa_port(sa), 10, false);
+ err |= write_padded(num, len, pad,
+ plr ? ' ' : pch, plr, NULL,
+ vph, arg);
+
+ break;
+
+ default:
+ if (('0' <= *p) && (*p <= '9')) {
+ if (!pad && ('0' == *p)) {
+ pch = '0';
+ }
+ else {
+ pad *= 10;
+ pad += *p - '0';
+ }
+ fm = true;
+ break;
+ }
+
+ ch = '?';
+
+ err |= vph(&ch, 1, arg);
+ break;
+ }
+
+ if (!fm)
+ p0 = p + 1;
+ }
+
+ if (!fm && p > p0)
+ err |= vph(p0, p - p0, arg);
+
+ return err;
+}
+
+
+static int print_handler(const char *p, size_t size, void *arg)
+{
+ struct pl *pl = arg;
+
+ if (size > pl->l)
+ return ENOMEM;
+
+ memcpy((void *)pl->p, p, size);
+
+ pl_advance(pl, size);
+
+ return 0;
+}
+
+
+struct dyn_print {
+ char *str;
+ char *p;
+ size_t l;
+ size_t size;
+};
+
+
+static int print_handler_dyn(const char *p, size_t size, void *arg)
+{
+ struct dyn_print *dp = arg;
+
+ if (size > dp->l - 1) {
+ const size_t new_size = MAX(dp->size + size, dp->size * 2);
+ char *str = mem_realloc(dp->str, new_size);
+ if (!str)
+ return ENOMEM;
+
+ dp->str = str;
+ dp->l += new_size - dp->size;
+ dp->p = dp->str + new_size - dp->l;
+ dp->size = new_size;
+ }
+
+ memcpy(dp->p, p, size);
+
+ dp->p += size;
+ dp->l -= size;
+
+ return 0;
+}
+
+
+struct strm_print {
+ FILE *f;
+ size_t n;
+};
+
+static int print_handler_stream(const char *p, size_t size, void *arg)
+{
+ struct strm_print *sp = arg;
+
+ if (1 != fwrite(p, size, 1, sp->f))
+ return ENOMEM;
+
+ sp->n += size;
+
+ return 0;
+}
+
+
+/**
+ * Print a formatted string to a file stream, using va_list
+ *
+ * @param stream File stream for the output
+ * @param fmt Formatted string
+ * @param ap Variable-arguments list
+ *
+ * @return The number of characters printed, or -1 if error
+ */
+int re_vfprintf(FILE *stream, const char *fmt, va_list ap)
+{
+ struct strm_print sp;
+
+ if (!stream)
+ return -1;
+
+ sp.f = stream;
+ sp.n = 0;
+
+ if (0 != re_vhprintf(fmt, ap, print_handler_stream, &sp))
+ return -1;
+
+ return (int)sp.n;
+}
+
+
+/**
+ * Print a formatted string to stdout, using va_list
+ *
+ * @param fmt Formatted string
+ * @param ap Variable-arguments list
+ *
+ * @return The number of characters printed, or -1 if error
+ */
+int re_vprintf(const char *fmt, va_list ap)
+{
+ return re_vfprintf(stdout, fmt, ap);
+}
+
+
+/**
+ * Print a formatted string to a buffer, using va_list
+ *
+ * @param str Buffer for output string
+ * @param size Size of buffer
+ * @param fmt Formatted string
+ * @param ap Variable-arguments list
+ *
+ * @return The number of characters printed, or -1 if error
+ */
+int re_vsnprintf(char *str, size_t size, const char *fmt, va_list ap)
+{
+ struct pl pl;
+ int err;
+
+ if (!str || !size)
+ return -1;
+
+ pl.p = str;
+ pl.l = size - 1;
+
+ err = re_vhprintf(fmt, ap, print_handler, &pl);
+
+ str[size - pl.l - 1] = '\0';
+
+ return err ? -1 : (int)(size - pl.l - 1);
+}
+
+
+/**
+ * Print a formatted string to a dynamically allocated buffer, using va_list
+ *
+ * @param strp Pointer for output string
+ * @param fmt Formatted string
+ * @param ap Variable-arguments list
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int re_vsdprintf(char **strp, const char *fmt, va_list ap)
+{
+ struct dyn_print dp;
+ int err;
+
+ if (!strp)
+ return EINVAL;
+
+ dp.size = 16;
+ dp.str = mem_alloc(dp.size, NULL);
+ if (!dp.str)
+ return ENOMEM;
+
+ dp.p = dp.str;
+ dp.l = dp.size;
+
+ err = re_vhprintf(fmt, ap, print_handler_dyn, &dp);
+ if (err)
+ goto out;
+
+ *dp.p = '\0';
+
+ out:
+ if (err)
+ mem_deref(dp.str);
+ else
+ *strp = dp.str;
+
+ return err;
+}
+
+
+/**
+ * Print a formatted string
+ *
+ * @param pf Print backend
+ * @param fmt Formatted string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int re_hprintf(struct re_printf *pf, const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ if (!pf)
+ return EINVAL;
+
+ va_start(ap, fmt);
+ err = re_vhprintf(fmt, ap, pf->vph, pf->arg);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Print a formatted string to a file stream
+ *
+ * @param stream File stream for output
+ * @param fmt Formatted string
+ *
+ * @return The number of characters printed, or -1 if error
+ */
+int re_fprintf(FILE *stream, const char *fmt, ...)
+{
+ va_list ap;
+ int n;
+
+ va_start(ap, fmt);
+ n = re_vfprintf(stream, fmt, ap);
+ va_end(ap);
+
+ return n;
+}
+
+
+/**
+ * Print a formatted string to stdout
+ *
+ * @param fmt Formatted string
+ *
+ * @return The number of characters printed, or -1 if error
+ */
+int re_printf(const char *fmt, ...)
+{
+ va_list ap;
+ int n;
+
+ va_start(ap, fmt);
+ n = re_vprintf(fmt, ap);
+ va_end(ap);
+
+ return n;
+}
+
+
+/**
+ * Print a formatted string to a buffer
+ *
+ * @param str Buffer for output string
+ * @param size Size of buffer
+ * @param fmt Formatted string
+ *
+ * @return The number of characters printed, or -1 if error
+ */
+int re_snprintf(char *str, size_t size, const char *fmt, ...)
+{
+ va_list ap;
+ int n;
+
+ va_start(ap, fmt);
+ n = re_vsnprintf(str, size, fmt, ap);
+ va_end(ap);
+
+ return n;
+}
+
+
+/**
+ * Print a formatted string to a buffer
+ *
+ * @param strp Buffer pointer for output string
+ * @param fmt Formatted string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int re_sdprintf(char **strp, const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, fmt);
+ err = re_vsdprintf(strp, fmt, ap);
+ va_end(ap);
+
+ return err;
+}
diff --git a/src/fmt/prm.c b/src/fmt/prm.c
new file mode 100644
index 0000000..9ddcb15
--- /dev/null
+++ b/src/fmt/prm.c
@@ -0,0 +1,96 @@
+/**
+ * @file prm.c Generic parameter decoding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+
+
+/**
+ * Check if a semicolon separated parameter is present
+ *
+ * @param pl PL string to search
+ * @param pname Parameter name
+ *
+ * @return true if found, false if not found
+ */
+bool fmt_param_exists(const struct pl *pl, const char *pname)
+{
+ struct pl semi, eop;
+ char expr[128];
+
+ if (!pl || !pname)
+ return false;
+
+ (void)re_snprintf(expr, sizeof(expr),
+ "[;]*[ \t\r\n]*%s[ \t\r\n;=]*",
+ pname);
+
+ if (re_regex(pl->p, pl->l, expr, &semi, NULL, &eop))
+ return false;
+
+ if (!eop.l && eop.p < pl->p + pl->l)
+ return false;
+
+ return semi.l > 0 || pl->p == semi.p;
+}
+
+
+/**
+ * Fetch a semicolon separated parameter from a PL string
+ *
+ * @param pl PL string to search
+ * @param pname Parameter name
+ * @param val Parameter value, set on return
+ *
+ * @return true if found, false if not found
+ */
+bool fmt_param_get(const struct pl *pl, const char *pname, struct pl *val)
+{
+ struct pl semi;
+ char expr[128];
+
+ if (!pl || !pname)
+ return false;
+
+ (void)re_snprintf(expr, sizeof(expr),
+ "[;]*[ \t\r\n]*%s[ \t\r\n]*=[ \t\r\n]*[~ \t\r\n;]+",
+ pname);
+
+ if (re_regex(pl->p, pl->l, expr, &semi, NULL, NULL, NULL, val))
+ return false;
+
+ return semi.l > 0 || pl->p == semi.p;
+}
+
+
+/**
+ * Apply a function handler for each semicolon separated parameter
+ *
+ * @param pl PL string to search
+ * @param ph Parameter handler
+ * @param arg Handler argument
+ */
+void fmt_param_apply(const struct pl *pl, fmt_param_h *ph, void *arg)
+{
+ struct pl prmv, prm, semi, name, val;
+
+ if (!pl || !ph)
+ return;
+
+ prmv = *pl;
+
+ while (!re_regex(prmv.p, prmv.l, "[ \t\r\n]*[~;]+[;]*",
+ NULL, &prm, &semi)) {
+
+ pl_advance(&prmv, semi.p + semi.l - prmv.p);
+
+ if (re_regex(prm.p, prm.l,
+ "[^ \t\r\n=]+[ \t\r\n]*[=]*[ \t\r\n]*[~ \t\r\n]*",
+ &name, NULL, NULL, NULL, &val))
+ break;
+
+ ph(&name, &val, arg);
+ }
+}
diff --git a/src/fmt/regex.c b/src/fmt/regex.c
new file mode 100644
index 0000000..1983fad
--- /dev/null
+++ b/src/fmt/regex.c
@@ -0,0 +1,258 @@
+/**
+ * @file regex.c Implements basic regular expressions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <ctype.h>
+#include <re_types.h>
+#include <re_fmt.h>
+
+
+/** Defines a character range */
+struct chr {
+ uint8_t min; /**< Minimum value */
+ uint8_t max; /**< Maximum value */
+};
+
+
+static bool expr_match(const struct chr *chrv, uint32_t n, uint8_t c,
+ bool neg)
+{
+ uint32_t i;
+
+ for (i=0; i<n; i++) {
+
+ if (c < chrv[i].min)
+ continue;
+
+ if (c > chrv[i].max)
+ continue;
+
+ break;
+ }
+
+ return neg ? (i == n) : (i != n);
+}
+
+
+/**
+ * Parse a string using basic regular expressions. Any number of matching
+ * expressions can be given, and each match will be stored in a "struct pl"
+ * pointer-length type.
+ *
+ * @param ptr String to parse
+ * @param len Length of string
+ * @param expr Regular expressions string
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * Example:
+ *
+ * We parse the buffer for any numerical values, to get a match we must have
+ * 1 or more occurences of the digits 0-9. The result is stored in 'num',
+ * which is of pointer-length type and will point to the first location in
+ * the buffer that contains "42".
+ *
+ * <pre>
+ const char buf[] = "foo 42 bar";
+ struct pl num;
+ int err = re_regex(buf, strlen(buf), "[0-9]+", &num);
+
+ here num contains a pointer to '42'
+ * </pre>
+ */
+int re_regex(const char *ptr, size_t len, const char *expr, ...)
+{
+ struct chr chrv[64];
+ const char *p, *ep;
+ bool fm, range = false, ec = false, neg = false, qesc = false;
+ uint32_t n = 0;
+ va_list ap;
+ bool eesc;
+ size_t l;
+
+ if (!ptr || !expr)
+ return EINVAL;
+
+ again:
+ eesc = false;
+ fm = false;
+ l = len--;
+ p = ptr++;
+ ep = expr;
+
+ va_start(ap, expr);
+
+ if (!l)
+ goto out;
+
+ for (; *ep; ep++) {
+
+ if ('\\' == *ep && !eesc) {
+ eesc = true;
+ continue;
+ }
+
+ if (!fm) {
+
+ /* Start of character class */
+ if ('[' == *ep && !eesc) {
+ n = 0;
+ fm = true;
+ ec = false;
+ neg = false;
+ range = false;
+ qesc = false;
+ continue;
+ }
+
+ if (!l)
+ break;
+
+ if (tolower(*ep) != tolower(*p)) {
+ va_end(ap);
+ goto again;
+ }
+
+ eesc = false;
+ ++p;
+ --l;
+ continue;
+ }
+ /* End of character class */
+ else if (ec) {
+
+ uint32_t nm, nmin, nmax;
+ struct pl lpl, *pl = va_arg(ap, struct pl *);
+ bool quote = false, esc = false;
+
+ /* Match 0 or more times */
+ if ('*' == *ep) {
+ nmin = 0;
+ nmax = -1;
+ }
+ /* Match 1 or more times */
+ else if ('+' == *ep) {
+ nmin = 1;
+ nmax = -1;
+ }
+ /* Match exactly n times */
+ else if ('1' <= *ep && *ep <= '9') {
+ nmin = *ep - '0';
+ nmax = *ep - '0';
+ }
+ else
+ break;
+
+ fm = false;
+
+ lpl.p = p;
+ lpl.l = 0;
+
+ for (nm = 0; l && nm < nmax; nm++, p++, l--, lpl.l++) {
+
+ if (qesc) {
+
+ if (esc) {
+ esc = false;
+ continue;
+ }
+
+ switch (*p) {
+
+ case '\\':
+ esc = true;
+ continue;
+
+ case '"':
+ quote = !quote;
+ continue;
+ }
+
+ if (quote)
+ continue;
+ }
+
+ if (!expr_match(chrv, n, tolower(*p), neg))
+ break;
+ }
+
+ /* Strip quotes */
+ if (qesc && lpl.l > 1 &&
+ lpl.p[0] == '"' && lpl.p[lpl.l - 1] == '"') {
+
+ lpl.p += 1;
+ lpl.l -= 2;
+ nm -= 2;
+ }
+
+ if ((nm < nmin) || (nm > nmax)) {
+ va_end(ap);
+ goto again;
+ }
+
+ if (pl)
+ *pl = lpl;
+
+ eesc = false;
+ continue;
+ }
+
+ if (eesc) {
+ eesc = false;
+ goto chr;
+ }
+
+ switch (*ep) {
+
+ /* End of character class */
+ case ']':
+ ec = true;
+ continue;
+
+ /* Negate with quote escape */
+ case '~':
+ if (n)
+ break;
+
+ qesc = true;
+ neg = true;
+ continue;
+
+ /* Negate */
+ case '^':
+ if (n)
+ break;
+
+ neg = true;
+ continue;
+
+ /* Range */
+ case '-':
+ if (!n || range)
+ break;
+
+ range = true;
+ --n;
+ continue;
+ }
+
+ chr:
+ chrv[n].max = tolower(*ep);
+
+ if (range)
+ range = false;
+ else
+ chrv[n].min = tolower(*ep);
+
+ if (++n > ARRAY_SIZE(chrv))
+ break;
+ }
+ out:
+ va_end(ap);
+
+ if (fm)
+ return EINVAL;
+
+ return *ep ? ENOENT : 0;
+}
diff --git a/src/fmt/str.c b/src/fmt/str.c
new file mode 100644
index 0000000..37981af
--- /dev/null
+++ b/src/fmt/str.c
@@ -0,0 +1,142 @@
+/**
+ * @file fmt/str.c String format functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#undef __STRICT_ANSI__ /* for mingw32 */
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_fmt.h>
+
+
+/**
+ * Convert a ascii hex string to binary format
+ *
+ * @param hex Destinatin binary buffer
+ * @param len Length of binary buffer
+ * @param str Source ascii string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int str_hex(uint8_t *hex, size_t len, const char *str)
+{
+ size_t i;
+
+ if (!hex || !str || (strlen(str) != (2 * len)))
+ return EINVAL;
+
+ for (i=0; i<len*2; i+=2) {
+ hex[i/2] = ch_hex(str[i]) << 4;
+ hex[i/2] += ch_hex(str[i+1]);
+ }
+
+ return 0;
+}
+
+
+/**
+ * Copy a 0-terminated string with maximum length
+ *
+ * @param dst Destinatin string
+ * @param src Source string
+ * @param n Maximum size of destination, including 0-terminator
+ */
+void str_ncpy(char *dst, const char *src, size_t n)
+{
+ if (!dst || !src || !n)
+ return;
+
+ (void)strncpy(dst, src, n-1);
+ dst[n-1] = '\0'; /* strncpy does not null terminate if overflow */
+}
+
+
+/**
+ * Duplicate a 0-terminated string
+ *
+ * @param dst Pointer to destination string (set on return)
+ * @param src Source string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int str_dup(char **dst, const char *src)
+{
+ char *p;
+ size_t sz;
+
+ if (!dst || !src)
+ return EINVAL;
+
+ sz = strlen(src) + 1;
+
+ p = mem_alloc(sz, NULL);
+ if (!p)
+ return ENOMEM;
+
+ memcpy(p, src, sz);
+
+ *dst = p;
+
+ return 0;
+}
+
+
+/**
+ * Compare two 0-terminated strings
+ *
+ * @param s1 First string
+ * @param s2 Second string
+ *
+ * @return an integer less than, equal to, or greater than zero if s1 is found
+ * respectively, to be less than, to match, or be greater than s2
+ */
+int str_cmp(const char *s1, const char *s2)
+{
+ if (!s1 || !s2)
+ return 1;
+
+ return strcmp(s1, s2);
+}
+
+
+/**
+ * Compare two 0-terminated strings, ignoring case
+ *
+ * @param s1 First string
+ * @param s2 Second string
+ *
+ * @return an integer less than, equal to, or greater than zero if s1 is found
+ * respectively, to be less than, to match, or be greater than s2
+ */
+int str_casecmp(const char *s1, const char *s2)
+{
+ /* Same strings -> equal */
+ if (s1 == s2)
+ return 0;
+
+ if (!s1 || !s2)
+ return 1;
+
+#ifdef WIN32
+ return _stricmp(s1, s2);
+#else
+ return strcasecmp(s1, s2);
+#endif
+}
+
+
+/**
+ * Calculate the length of a string, safe version.
+ *
+ * @param s String
+ *
+ * @return Length of the string
+ */
+size_t str_len(const char *s)
+{
+ return s ? strlen(s) : 0;
+}
diff --git a/src/fmt/str_error.c b/src/fmt/str_error.c
new file mode 100644
index 0000000..fd345c1
--- /dev/null
+++ b/src/fmt/str_error.c
@@ -0,0 +1,51 @@
+/**
+ * @file str_error.c System error messages
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _GNU_SOURCE 1
+#define __EXTENSIONS__ 1
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+
+
+/**
+ * Look up an error message string corresponding to an error number.
+ *
+ * @param errnum Error Code
+ * @param buf Buffer for storing error message
+ * @param sz Buffer size
+ *
+ * @return Error message string
+ */
+const char *str_error(int errnum, char *buf, size_t sz)
+{
+ const char *s;
+
+ if (!buf || !sz)
+ return NULL;
+
+ buf[0] = '\0';
+#ifdef HAVE_STRERROR_R
+
+#ifdef __GLIBC__
+ s = strerror_r(errnum, buf, sz);
+#else
+ (void)strerror_r(errnum, buf, sz);
+ s = buf;
+#endif
+
+#elif defined (WIN32) & !defined (__MINGW32__)
+ (void)strerror_s(buf, sz, errnum);
+ s = buf;
+#else
+ /* fallback */
+ (void)errnum;
+ s = "unknown error";
+#endif
+
+ buf[sz - 1] = '\0';
+
+ return s;
+}
diff --git a/src/fmt/time.c b/src/fmt/time.c
new file mode 100644
index 0000000..e86396b9
--- /dev/null
+++ b/src/fmt/time.c
@@ -0,0 +1,81 @@
+/**
+ * @file time.c Time formatting
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <time.h>
+#include <re_types.h>
+#include <re_fmt.h>
+
+
+static const char *dayv[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+
+static const char *monv[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+
+
+/**
+ * Print Greenwich Mean Time
+ *
+ * @param pf Print function for output
+ * @param ts Time in seconds since the Epoch or NULL for current time
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int fmt_gmtime(struct re_printf *pf, void *ts)
+{
+ const struct tm *tm;
+ time_t t;
+
+ if (!ts) {
+ t = time(NULL);
+ ts = &t;
+ }
+
+ tm = gmtime(ts);
+ if (!tm)
+ return EINVAL;
+
+ return re_hprintf(pf, "%s, %02u %s %u %02u:%02u:%02u GMT",
+ dayv[min((unsigned)tm->tm_wday, ARRAY_SIZE(dayv)-1)],
+ tm->tm_mday,
+ monv[min((unsigned)tm->tm_mon, ARRAY_SIZE(monv)-1)],
+ tm->tm_year + 1900,
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+}
+
+
+/**
+ * Print the human readable time
+ *
+ * @param pf Print function for output
+ * @param seconds Pointer to number of seconds
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int fmt_human_time(struct re_printf *pf, const uint32_t *seconds)
+{
+ /* max 136 years */
+ const uint32_t sec = *seconds%60;
+ const uint32_t min = *seconds/60%60;
+ const uint32_t hrs = *seconds/60/60%24;
+ const uint32_t days = *seconds/60/60/24;
+ int err = 0;
+
+ if (days)
+ err |= re_hprintf(pf, "%u day%s ", days, 1==days?"":"s");
+
+ if (hrs) {
+ err |= re_hprintf(pf, "%u hour%s ", hrs, 1==hrs?"":"s");
+ }
+
+ if (min) {
+ err |= re_hprintf(pf, "%u min%s ", min, 1==min?"":"s");
+ }
+
+ if (sec) {
+ err |= re_hprintf(pf, "%u sec%s", sec, 1==sec?"":"s");
+ }
+
+ return err;
+}
diff --git a/src/fmt/unicode.c b/src/fmt/unicode.c
new file mode 100644
index 0000000..1dde72e
--- /dev/null
+++ b/src/fmt/unicode.c
@@ -0,0 +1,230 @@
+/**
+ * @file unicode.c Unicode character coding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <ctype.h>
+#include <re_types.h>
+#include <re_fmt.h>
+
+
+static const char *hex_chars = "0123456789ABCDEF";
+
+
+/**
+ * UTF-8 encode
+ *
+ * @param pf Print function for output
+ * @param str Input string to encode
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int utf8_encode(struct re_printf *pf, const char *str)
+{
+ char ubuf[6] = "\\u00", ebuf[2] = "\\";
+
+ if (!pf)
+ return EINVAL;
+
+ if (!str)
+ return 0;
+
+ while (*str) {
+ const uint8_t c = *str++; /* NOTE: must be unsigned 8-bit */
+ bool unicode = false;
+ char ec = 0;
+ int err;
+
+ switch (c) {
+
+ case '"': ec = '"'; break;
+ case '\\': ec = '\\'; break;
+ case '/': ec = '/'; break;
+ case '\b': ec = 'b'; break;
+ case '\f': ec = 'f'; break;
+ case '\n': ec = 'n'; break;
+ case '\r': ec = 'r'; break;
+ case '\t': ec = 't'; break;
+ default:
+ if (c < ' ') {
+ unicode = true;
+ }
+ /* chars in range 0x80-0xff are not escaped */
+ break;
+ }
+
+ if (unicode) {
+ ubuf[4] = hex_chars[(c>>4) & 0xf];
+ ubuf[5] = hex_chars[c & 0xf];
+
+ err = pf->vph(ubuf, sizeof(ubuf), pf->arg);
+ }
+ else if (ec) {
+ ebuf[1] = ec;
+
+ err = pf->vph(ebuf, sizeof(ebuf), pf->arg);
+ }
+ else {
+ err = pf->vph((char *)&c, 1, pf->arg);
+ }
+
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+
+/**
+ * UTF-8 decode
+ *
+ * @param pf Print function for output
+ * @param pl Input buffer to decode
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int utf8_decode(struct re_printf *pf, const struct pl *pl)
+{
+ int uhi = -1;
+ size_t i;
+
+ if (!pf)
+ return EINVAL;
+
+ if (!pl)
+ return 0;
+
+ for (i=0; i<pl->l; i++) {
+
+ char ch = pl->p[i];
+ int err;
+
+ if (ch == '\\') {
+
+ unsigned u = 0;
+ char ubuf[4];
+ size_t ulen;
+
+ ++i;
+
+ if (i >= pl->l)
+ return EBADMSG;
+
+ ch = pl->p[i];
+
+ switch (ch) {
+
+ case 'b':
+ ch = '\b';
+ break;
+
+ case 'f':
+ ch = '\f';
+ break;
+
+ case 'n':
+ ch = '\n';
+ break;
+
+ case 'r':
+ ch = '\r';
+ break;
+
+ case 't':
+ ch = '\t';
+ break;
+
+ case 'u':
+ if (i+4 >= pl->l)
+ return EBADMSG;
+
+ if (!isxdigit(pl->p[i+1]) ||
+ !isxdigit(pl->p[i+2]) ||
+ !isxdigit(pl->p[i+3]) ||
+ !isxdigit(pl->p[i+4]))
+ return EBADMSG;
+
+ u |= ((uint16_t)ch_hex(pl->p[++i])) << 12;
+ u |= ((uint16_t)ch_hex(pl->p[++i])) << 8;
+ u |= ((uint16_t)ch_hex(pl->p[++i])) << 4;
+ u |= ((uint16_t)ch_hex(pl->p[++i])) << 0;
+
+ /* UTF-16 surrogate pair */
+ if (u >= 0xd800 && u <= 0xdbff) {
+ uhi = (u - 0xd800) * 0x400;
+ continue;
+ }
+ else if (u >= 0xdc00 && u <= 0xdfff) {
+ if (uhi < 0)
+ continue;
+
+ u = uhi + u - 0xdc00 + 0x10000;
+ }
+
+ uhi = -1;
+
+ ulen = utf8_byteseq(ubuf, u);
+
+ err = pf->vph(ubuf, ulen, pf->arg);
+ if (err)
+ return err;
+
+ continue;
+ }
+ }
+
+ uhi = -1;
+
+ err = pf->vph(&ch, 1, pf->arg);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Encode Unicode code point into binary UTF-8
+ *
+ * @param u Binary UTF-8 buffer
+ * @param cp Unicode code point
+ *
+ * @return length of UTF-8 byte sequence
+ */
+size_t utf8_byteseq(char u[4], unsigned cp)
+{
+ if (!u)
+ return 0;
+
+ if (cp <= 0x7f) {
+ u[0] = cp;
+ return 1;
+ }
+ else if (cp <= 0x7ff) {
+ u[0] = 0xc0 | (cp>>6 & 0x1f);
+ u[1] = 0x80 | (cp & 0x3f);
+ return 2;
+ }
+ else if (cp <= 0xffff) {
+ u[0] = 0xe0 | (cp>>12 & 0x0f);
+ u[1] = 0x80 | (cp>>6 & 0x3f);
+ u[2] = 0x80 | (cp & 0x3f);
+ return 3;
+ }
+ else if (cp <= 0x10ffff) {
+ u[0] = 0xf0 | (cp>>18 & 0x07);
+ u[1] = 0x80 | (cp>>12 & 0x3f);
+ u[2] = 0x80 | (cp>>6 & 0x3f);
+ u[3] = 0x80 | (cp & 0x3f);
+ return 4;
+ }
+ else {
+ /* The replacement character (U+FFFD) */
+ u[0] = (char)0xef;
+ u[1] = (char)0xbf;
+ u[2] = (char)0xbd;
+ return 3;
+ }
+}
diff --git a/src/hash/func.c b/src/hash/func.c
new file mode 100644
index 0000000..3943c94
--- /dev/null
+++ b/src/hash/func.c
@@ -0,0 +1,369 @@
+/**
+ * @file func.c Hashmap functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <ctype.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_hash.h>
+
+
+/**
+ * Calculate hash-value using "Jenkins One-at-a-time" hash algorithm.
+ *
+ * @param key Pointer to key
+ * @param len Key length
+ *
+ * @return Calculated hash-value
+ */
+uint32_t hash_joaat(const uint8_t *key, size_t len)
+{
+ uint32_t hash = 0;
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ hash += key[i];
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+
+ return hash;
+}
+
+
+/**
+ * Calculate hash-value for a case-insensitive string
+ *
+ * @param str String
+ * @param len Length of string
+ *
+ * @return Calculated hash-value
+ */
+uint32_t hash_joaat_ci(const char *str, size_t len)
+{
+ uint32_t hash = 0;
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ hash += tolower(str[i]);
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+
+ return hash;
+}
+
+
+/**
+ * Calculate hash-value for a NULL-terminated string
+ *
+ * @param str String
+ *
+ * @return Calculated hash-value
+ */
+uint32_t hash_joaat_str(const char *str)
+{
+ uint32_t hash = 0;
+
+ while (*str) {
+ hash += *str++;
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+
+ return hash;
+}
+
+
+/**
+ * Calculate hash-value for a case-insensitive NULL-terminated string
+ *
+ * @param str String
+ *
+ * @return Calculated hash-value
+ */
+uint32_t hash_joaat_str_ci(const char *str)
+{
+ uint32_t hash = 0;
+
+ while (*str) {
+ hash += tolower(*str++);
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+
+ return hash;
+}
+
+
+/**
+ * Calculate hash-value for a pointer-length object
+ *
+ * @param pl Pointer-length object
+ *
+ * @return Calculated hash-value
+ */
+uint32_t hash_joaat_pl(const struct pl *pl)
+{
+ return pl ? hash_joaat((const uint8_t *)pl->p, pl->l) : 0;
+}
+
+
+/**
+ * Calculate hash-value for a case-insensitive pointer-length object
+ *
+ * @param pl Pointer-length object
+ *
+ * @return Calculated hash-value
+ */
+uint32_t hash_joaat_pl_ci(const struct pl *pl)
+{
+ return pl ? hash_joaat_ci(pl->p, pl->l) : 0;
+}
+
+
+/*
+ * My best guess at if you are big-endian or little-endian. This may
+ * need adjustment.
+ */
+#if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \
+ __BYTE_ORDER == __LITTLE_ENDIAN) || \
+ (defined(i386) || defined(__i386__) || defined(__i486__) || \
+ defined(__i586__) || defined(__i686__) || \
+ defined(vax) || defined(MIPSEL))
+# define HASH_LITTLE_ENDIAN 1
+# define HASH_BIG_ENDIAN 0
+#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \
+ __BYTE_ORDER == __BIG_ENDIAN) || \
+ (defined(sparc) || defined(POWERPC) || \
+ defined(mc68000) || defined(sel))
+# define HASH_LITTLE_ENDIAN 0
+# define HASH_BIG_ENDIAN 1
+#else
+# define HASH_LITTLE_ENDIAN 0
+# define HASH_BIG_ENDIAN 0
+#endif
+
+#define hashsize(n) ((uint32_t)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+
+#define mix(a,b,c) { \
+ a -= c; a ^= rot(c, 4); c += b; \
+ b -= a; b ^= rot(a, 6); a += c; \
+ c -= b; c ^= rot(b, 8); b += a; \
+ a -= c; a ^= rot(c,16); c += b; \
+ b -= a; b ^= rot(a,19); a += c; \
+ c -= b; c ^= rot(b, 4); b += a; \
+ }
+
+
+#define final(a,b,c) \
+ { \
+ c ^= b; c -= rot(b,14); \
+ a ^= c; a -= rot(c,11); \
+ b ^= a; b -= rot(a,25); \
+ c ^= b; c -= rot(b,16); \
+ a ^= c; a -= rot(c,4); \
+ b ^= a; b -= rot(a,14); \
+ c ^= b; c -= rot(b,24); \
+ }
+
+
+static uint32_t hashlittle( const void *key, size_t length, uint32_t initval)
+{
+ uint32_t a,b,c;
+ union { const void *ptr; size_t i; } u;
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
+
+ u.ptr = key;
+ if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+ const uint32_t *k = (const uint32_t *)key;
+
+ while (length > 12) {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 12;
+ k += 3;
+ }
+
+#ifndef VALGRIND
+ switch (length) {
+
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+ case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+ case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
+ case 6 : b+=k[1]&0xffff; a+=k[0]; break;
+ case 5 : b+=k[1]&0xff; a+=k[0]; break;
+ case 4 : a+=k[0]; break;
+ case 3 : a+=k[0]&0xffffff; break;
+ case 2 : a+=k[0]&0xffff; break;
+ case 1 : a+=k[0]&0xff; break;
+ case 0 : return c;
+ }
+
+#else /* make valgrind happy */
+
+ const uint8_t *k8 = (const uint8_t *)k;
+ switch (length) {
+
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
+ case 10: c+=((uint32_t)k8[9])<<8; /* fall through */
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
+ case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]; break;
+ case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
+ case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */
+ case 1 : a+=k8[0]; break;
+ case 0 : return c;
+ }
+
+#endif /* !valgrind */
+
+ }
+ else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+ const uint16_t *k = (const uint16_t *)key;
+ const uint8_t *k8;
+
+ while (length > 12) {
+ a += k[0] + (((uint32_t)k[1])<<16);
+ b += k[2] + (((uint32_t)k[3])<<16);
+ c += k[4] + (((uint32_t)k[5])<<16);
+ mix(a,b,c);
+ length -= 12;
+ k += 6;
+ }
+
+ k8 = (const uint8_t *)k;
+
+ switch (length) {
+
+ case 12: c+=k[4]+(((uint32_t)k[5])<<16);
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
+ case 10: c+=k[4];
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
+ case 6 : b+=k[2];
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
+ case 2 : a+=k[0];
+ break;
+ case 1 : a+=k8[0];
+ break;
+ case 0 : return c;
+ }
+ }
+ else {
+ const uint8_t *k = (const uint8_t *)key;
+
+ while (length > 12) {
+ a += k[0];
+ a += ((uint32_t)k[1])<<8;
+ a += ((uint32_t)k[2])<<16;
+ a += ((uint32_t)k[3])<<24;
+ b += k[4];
+ b += ((uint32_t)k[5])<<8;
+ b += ((uint32_t)k[6])<<16;
+ b += ((uint32_t)k[7])<<24;
+ c += k[8];
+ c += ((uint32_t)k[9])<<8;
+ c += ((uint32_t)k[10])<<16;
+ c += ((uint32_t)k[11])<<24;
+ mix(a,b,c);
+ length -= 12;
+ k += 12;
+ }
+
+ /* all the case statements fall through */
+ switch (length) {
+
+ case 12: c+=((uint32_t)k[11])<<24;
+ case 11: c+=((uint32_t)k[10])<<16;
+ case 10: c+=((uint32_t)k[9])<<8;
+ case 9 : c+=k[8];
+ case 8 : b+=((uint32_t)k[7])<<24;
+ case 7 : b+=((uint32_t)k[6])<<16;
+ case 6 : b+=((uint32_t)k[5])<<8;
+ case 5 : b+=k[4];
+ case 4 : a+=((uint32_t)k[3])<<24;
+ case 3 : a+=((uint32_t)k[2])<<16;
+ case 2 : a+=((uint32_t)k[1])<<8;
+ case 1 : a+=k[0];
+ break;
+ case 0 : return c;
+ }
+ }
+
+ final(a,b,c);
+ return c;
+}
+
+
+/**
+ * Calculate hash-value using fast hash algorithm.
+ *
+ * @param k Pointer to key
+ * @param len Key length
+ *
+ * @return Calculated hash-value
+ */
+uint32_t hash_fast(const char *k, size_t len)
+{
+ static volatile int random_seed = 0x304a0012;
+
+ if (!k)
+ return 0;
+
+ return hashlittle(k, len, random_seed);
+}
+
+
+/**
+ * Calculate hash-value for a NULL-terminated string
+ *
+ * @param str String
+ *
+ * @return Calculated hash-value
+ */
+uint32_t hash_fast_str(const char *str)
+{
+ return hash_fast(str, str_len(str));
+}
diff --git a/src/hash/hash.c b/src/hash/hash.c
new file mode 100644
index 0000000..45aa11e
--- /dev/null
+++ b/src/hash/hash.c
@@ -0,0 +1,218 @@
+/**
+ * @file hash.c Hashmap table
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+
+
+/** Defines a hashmap table */
+struct hash {
+ struct list *bucket; /**< Bucket with linked lists */
+ uint32_t bsize; /**< Bucket size */
+};
+
+
+static void hash_destructor(void *data)
+{
+ struct hash *h = data;
+
+ mem_deref(h->bucket);
+}
+
+
+/**
+ * Allocate a new hashmap table
+ *
+ * @param hp Address of hashmap pointer
+ * @param bsize Bucket size
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int hash_alloc(struct hash **hp, uint32_t bsize)
+{
+ struct hash *h;
+ int err = 0;
+
+ if (!hp || !bsize)
+ return EINVAL;
+
+ /* Validate bucket size */
+ if (bsize & (bsize-1))
+ return EINVAL;
+
+ h = mem_zalloc(sizeof(*h), hash_destructor);
+ if (!h)
+ return ENOMEM;
+
+ h->bsize = bsize;
+
+ h->bucket = mem_zalloc(bsize*sizeof(*h->bucket), NULL);
+ if (!h->bucket) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(h);
+ else
+ *hp = h;
+
+ return err;
+}
+
+
+/**
+ * Add an element to the hashmap table
+ *
+ * @param h Hashmap table
+ * @param key Hash key
+ * @param le List element
+ * @param data Element data
+ */
+void hash_append(struct hash *h, uint32_t key, struct le *le, void *data)
+{
+ if (!h || !le)
+ return;
+
+ list_append(&h->bucket[key & (h->bsize-1)], le, data);
+}
+
+
+/**
+ * Unlink an element from the hashmap table
+ *
+ * @param le List element
+ */
+void hash_unlink(struct le *le)
+{
+ list_unlink(le);
+}
+
+
+/**
+ * Apply a handler function to all elements in the hashmap with a matching key
+ *
+ * @param h Hashmap table
+ * @param key Hash key
+ * @param ah Apply handler
+ * @param arg Handler argument
+ *
+ * @return List element if traversing stopped, otherwise NULL
+ */
+struct le *hash_lookup(const struct hash *h, uint32_t key, list_apply_h *ah,
+ void *arg)
+{
+ if (!h || !ah)
+ return NULL;
+
+ return list_apply(&h->bucket[key & (h->bsize-1)], true, ah, arg);
+}
+
+
+/**
+ * Apply a handler function to all elements in the hashmap
+ *
+ * @param h Hashmap table
+ * @param ah Apply handler
+ * @param arg Handler argument
+ *
+ * @return List element if traversing stopped, otherwise NULL
+ */
+struct le *hash_apply(const struct hash *h, list_apply_h *ah, void *arg)
+{
+ struct le *le = NULL;
+ uint32_t i;
+
+ if (!h || !ah)
+ return NULL;
+
+ for (i=0; (i<h->bsize) && !le; i++)
+ le = list_apply(&h->bucket[i], true, ah, arg);
+
+ return le;
+}
+
+
+/**
+ * Return bucket list for a given index
+ *
+ * @param h Hashmap table
+ * @param key Hash key
+ *
+ * @return Bucket list if valid input, otherwise NULL
+ */
+struct list *hash_list(const struct hash *h, uint32_t key)
+{
+ return h ? &h->bucket[key & (h->bsize - 1)] : NULL;
+}
+
+
+/**
+ * Get hash bucket size
+ *
+ * @param h Hashmap table
+ *
+ * @return hash bucket size
+ */
+uint32_t hash_bsize(const struct hash *h)
+{
+ return h ? h->bsize : 0;
+}
+
+
+/**
+ * Flush a hashmap and free all elements
+ *
+ * @param h Hashmap table
+ */
+void hash_flush(struct hash *h)
+{
+ uint32_t i;
+
+ if (!h)
+ return;
+
+ for (i=0; i<h->bsize; i++)
+ list_flush(&h->bucket[i]);
+}
+
+
+/**
+ * Clear a hashmap without dereferencing the elements
+ *
+ * @param h Hashmap table
+ */
+void hash_clear(struct hash *h)
+{
+ uint32_t i;
+
+ if (!h)
+ return;
+
+ for (i=0; i<h->bsize; i++)
+ list_clear(&h->bucket[i]);
+}
+
+
+/**
+ * Calculate a valid hash size from a random size
+ *
+ * @param size Requested size
+ *
+ * @return Valid hash size
+ */
+uint32_t hash_valid_size(uint32_t size)
+{
+ uint32_t x;
+
+ for (x=0; (uint32_t)1<<x < size && x < 31; x++)
+ ;
+
+ return 1<<x;
+}
diff --git a/src/hash/mod.mk b/src/hash/mod.mk
new file mode 100644
index 0000000..0e10b41
--- /dev/null
+++ b/src/hash/mod.mk
@@ -0,0 +1,8 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += hash/hash.c
+SRCS += hash/func.c
diff --git a/src/hmac/apple/hmac.c b/src/hmac/apple/hmac.c
new file mode 100644
index 0000000..34099d6
--- /dev/null
+++ b/src/hmac/apple/hmac.c
@@ -0,0 +1,83 @@
+/**
+ * @file apple/hmac.c HMAC using Apple API
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+#include <string.h>
+#include <CommonCrypto/CommonHMAC.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_hmac.h>
+
+
+enum { KEY_SIZE = 256 };
+
+struct hmac {
+ CCHmacContext ctx;
+ uint8_t key[KEY_SIZE];
+ size_t key_len;
+ CCHmacAlgorithm algo;
+};
+
+
+static void destructor(void *arg)
+{
+ struct hmac *hmac = arg;
+
+ memset(&hmac->ctx, 0, sizeof(hmac->ctx));
+}
+
+
+int hmac_create(struct hmac **hmacp, enum hmac_hash hash,
+ const uint8_t *key, size_t key_len)
+{
+ struct hmac *hmac;
+ CCHmacAlgorithm algo;
+
+ if (!hmacp || !key || !key_len || key_len > KEY_SIZE)
+ return EINVAL;
+
+ switch (hash) {
+
+ case HMAC_HASH_SHA1:
+ algo = kCCHmacAlgSHA1;
+ break;
+
+ case HMAC_HASH_SHA256:
+ algo = kCCHmacAlgSHA256;
+ break;
+
+ default:
+ return ENOTSUP;
+ }
+
+ hmac = mem_zalloc(sizeof(*hmac), destructor);
+ if (!hmac)
+ return ENOMEM;
+
+ memcpy(hmac->key, key, key_len);
+ hmac->key_len = key_len;
+ hmac->algo = algo;
+
+ *hmacp = hmac;
+
+ return 0;
+}
+
+
+int hmac_digest(struct hmac *hmac, uint8_t *md, size_t md_len,
+ const uint8_t *data, size_t data_len)
+{
+ if (!hmac || !md || !md_len || !data || !data_len)
+ return EINVAL;
+
+ /* reset state */
+ CCHmacInit(&hmac->ctx, hmac->algo, hmac->key, hmac->key_len);
+
+ CCHmacUpdate(&hmac->ctx, data, data_len);
+ CCHmacFinal(&hmac->ctx, md);
+
+ return 0;
+}
diff --git a/src/hmac/hmac.c b/src/hmac/hmac.c
new file mode 100644
index 0000000..ce89aad
--- /dev/null
+++ b/src/hmac/hmac.c
@@ -0,0 +1,63 @@
+/**
+ * @file hmac/hmac.c HMAC-SHA1
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_sha.h>
+#include <re_hmac.h>
+
+
+struct hmac {
+ uint8_t key[SHA_DIGEST_LENGTH];
+ size_t key_len;
+};
+
+
+static void destructor(void *arg)
+{
+ struct hmac *hmac = arg;
+
+ memset(hmac, 0, sizeof(*hmac));
+}
+
+
+int hmac_create(struct hmac **hmacp, enum hmac_hash hash,
+ const uint8_t *key, size_t key_len)
+{
+ struct hmac *hmac;
+
+ if (!hmacp || !key || !key_len)
+ return EINVAL;
+
+ if (hash != HMAC_HASH_SHA1)
+ return ENOTSUP;
+
+ if (key_len > SHA_DIGEST_LENGTH)
+ return EINVAL;
+
+ hmac = mem_zalloc(sizeof(*hmac), destructor);
+ if (!hmac)
+ return ENOMEM;
+
+ memcpy(hmac->key, key, key_len);
+ hmac->key_len = key_len;
+
+ *hmacp = hmac;
+
+ return 0;
+}
+
+
+int hmac_digest(struct hmac *hmac, uint8_t *md, size_t md_len,
+ const uint8_t *data, size_t data_len)
+{
+ if (!hmac || !md || !md_len || !data || !data_len)
+ return EINVAL;
+
+ hmac_sha1(hmac->key, hmac->key_len, data, data_len, md, md_len);
+
+ return 0;
+}
diff --git a/src/hmac/hmac_sha1.c b/src/hmac/hmac_sha1.c
new file mode 100644
index 0000000..1fdf0c5
--- /dev/null
+++ b/src/hmac/hmac_sha1.c
@@ -0,0 +1,99 @@
+/**
+ * @file hmac_sha1.c Implements HMAC-SHA1 as of RFC 2202
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#ifdef USE_OPENSSL
+#include <openssl/sha.h>
+#include <openssl/hmac.h>
+#include <openssl/err.h>
+#else
+#include <re_sha.h>
+#endif
+#include <re_hmac.h>
+
+
+/** SHA-1 Block size */
+#ifndef SHA_BLOCKSIZE
+#define SHA_BLOCKSIZE 64
+#endif
+
+
+/**
+ * Function to compute the digest
+ *
+ * @param k Secret key
+ * @param lk Length of the key in bytes
+ * @param d Data
+ * @param ld Length of data in bytes
+ * @param out Digest output
+ * @param t Size of digest output
+ */
+void hmac_sha1(const uint8_t *k, /* secret key */
+ size_t lk, /* length of the key in bytes */
+ const uint8_t *d, /* data */
+ size_t ld, /* length of data in bytes */
+ uint8_t *out, /* output buffer, at least "t" bytes */
+ size_t t)
+{
+#ifdef USE_OPENSSL
+ (void)t;
+
+ if (!HMAC(EVP_sha1(), k, (int)lk, d, ld, out, NULL))
+ ERR_clear_error();
+#else
+ SHA_CTX ictx, octx;
+ uint8_t isha[SHA_DIGEST_LENGTH], osha[SHA_DIGEST_LENGTH];
+ uint8_t key[SHA_DIGEST_LENGTH];
+ uint8_t buf[SHA_BLOCKSIZE];
+ size_t i;
+
+ if (lk > SHA_BLOCKSIZE) {
+ SHA_CTX tctx;
+
+ SHA1_Init(&tctx);
+ SHA1_Update(&tctx, k, lk);
+ SHA1_Final(key, &tctx);
+
+ k = key;
+ lk = SHA_DIGEST_LENGTH;
+ }
+
+ /**** Inner Digest ****/
+
+ SHA1_Init(&ictx);
+
+ /* Pad the key for inner digest */
+ for (i = 0 ; i < lk ; ++i)
+ buf[i] = k[i] ^ 0x36;
+ for (i = lk ; i < SHA_BLOCKSIZE ; ++i)
+ buf[i] = 0x36;
+
+ SHA1_Update(&ictx, buf, SHA_BLOCKSIZE);
+ SHA1_Update(&ictx, d, ld);
+
+ SHA1_Final(isha, &ictx);
+
+ /**** Outer Digest ****/
+
+ SHA1_Init(&octx);
+
+ /* Pad the key for outter digest */
+
+ for (i = 0 ; i < lk ; ++i)
+ buf[i] = k[i] ^ 0x5c;
+ for (i = lk ; i < SHA_BLOCKSIZE ; ++i)
+ buf[i] = 0x5c;
+
+ SHA1_Update(&octx, buf, SHA_BLOCKSIZE);
+ SHA1_Update(&octx, isha, SHA_DIGEST_LENGTH);
+
+ SHA1_Final(osha, &octx);
+
+ /* truncate and print the results */
+ t = t > SHA_DIGEST_LENGTH ? SHA_DIGEST_LENGTH : t;
+ memcpy(out, osha, t);
+#endif
+}
diff --git a/src/hmac/mod.mk b/src/hmac/mod.mk
new file mode 100644
index 0000000..ee5d2fb
--- /dev/null
+++ b/src/hmac/mod.mk
@@ -0,0 +1,15 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += hmac/hmac_sha1.c
+
+ifneq ($(USE_OPENSSL_HMAC),)
+SRCS += hmac/openssl/hmac.c
+else ifneq ($(USE_APPLE_COMMONCRYPTO),)
+SRCS += hmac/apple/hmac.c
+else
+SRCS += hmac/hmac.c
+endif
diff --git a/src/hmac/openssl/hmac.c b/src/hmac/openssl/hmac.c
new file mode 100644
index 0000000..212567f
--- /dev/null
+++ b/src/hmac/openssl/hmac.c
@@ -0,0 +1,135 @@
+/**
+ * @file openssl/hmac.c HMAC using OpenSSL
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <openssl/hmac.h>
+#include <openssl/err.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_hmac.h>
+
+
+struct hmac {
+ HMAC_CTX *ctx;
+};
+
+
+static void destructor(void *arg)
+{
+ struct hmac *hmac = arg;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+
+ if (hmac->ctx)
+ HMAC_CTX_free(hmac->ctx);
+#else
+ if (hmac->ctx)
+ HMAC_CTX_cleanup(hmac->ctx);
+ mem_deref(hmac->ctx);
+#endif
+}
+
+
+int hmac_create(struct hmac **hmacp, enum hmac_hash hash,
+ const uint8_t *key, size_t key_len)
+{
+ struct hmac *hmac;
+ const EVP_MD *evp;
+ int err = 0;
+
+ if (!hmacp || !key || !key_len)
+ return EINVAL;
+
+ switch (hash) {
+
+ case HMAC_HASH_SHA1:
+ evp = EVP_sha1();
+ break;
+
+ case HMAC_HASH_SHA256:
+ evp = EVP_sha256();
+ break;
+
+ default:
+ return ENOTSUP;
+ }
+
+ hmac = mem_zalloc(sizeof(*hmac), destructor);
+ if (!hmac)
+ return ENOMEM;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+
+ hmac->ctx = HMAC_CTX_new();
+ if (!hmac->ctx) {
+ ERR_clear_error();
+ err = ENOMEM;
+ goto out;
+ }
+#else
+ hmac->ctx = mem_zalloc(sizeof(*hmac->ctx), NULL);
+ if (!hmac->ctx) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ HMAC_CTX_init(hmac->ctx);
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x00909000)
+ if (!HMAC_Init_ex(hmac->ctx, key, (int)key_len, evp, NULL)) {
+ ERR_clear_error();
+ err = EPROTO;
+ }
+#else
+ HMAC_Init_ex(hmac->ctx, key, (int)key_len, evp, NULL);
+#endif
+
+ out:
+ if (err)
+ mem_deref(hmac);
+ else
+ *hmacp = hmac;
+
+ return err;
+}
+
+
+int hmac_digest(struct hmac *hmac, uint8_t *md, size_t md_len,
+ const uint8_t *data, size_t data_len)
+{
+ unsigned int len = (unsigned int)md_len;
+
+ if (!hmac || !md || !md_len || !data || !data_len)
+ return EINVAL;
+
+#if (OPENSSL_VERSION_NUMBER >= 0x00909000)
+ /* the HMAC context must be reset here */
+ if (!HMAC_Init_ex(hmac->ctx, 0, 0, 0, NULL))
+ goto error;
+
+ if (!HMAC_Update(hmac->ctx, data, (int)data_len))
+ goto error;
+ if (!HMAC_Final(hmac->ctx, md, &len))
+ goto error;
+
+ return 0;
+
+ error:
+ ERR_clear_error();
+ return EPROTO;
+
+#else
+ /* the HMAC context must be reset here */
+ HMAC_Init_ex(hmac->ctx, 0, 0, 0, NULL);
+
+ HMAC_Update(hmac->ctx, data, (int)data_len);
+ HMAC_Final(hmac->ctx, md, &len);
+
+ return 0;
+#endif
+}
diff --git a/src/http/auth.c b/src/http/auth.c
new file mode 100644
index 0000000..68dd171
--- /dev/null
+++ b/src/http/auth.c
@@ -0,0 +1,167 @@
+/**
+ * @file http/auth.c HTTP Authentication
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+
+#include <string.h>
+#include <time.h>
+#include <re_types.h>
+#include <re_sys.h>
+#include <re_md5.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_fmt.h>
+#include <re_msg.h>
+#include <re_httpauth.h>
+#include <re_http.h>
+
+
+enum {
+ NONCE_EXPIRES = 300,
+ NONCE_MIN_SIZE = 33,
+};
+
+
+static uint64_t secret;
+static bool secret_set;
+
+
+/**
+ * Print HTTP digest authentication challenge
+ *
+ * @param pf Print function for output
+ * @param auth Authentication parameteres
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int http_auth_print_challenge(struct re_printf *pf,
+ const struct http_auth *auth)
+{
+ uint8_t key[MD5_SIZE];
+ uint64_t nv[2];
+
+ if (!auth)
+ return 0;
+
+ if (!secret_set) {
+ secret = rand_u64();
+ secret_set = true;
+ }
+
+ nv[0] = time(NULL);
+ nv[1] = secret;
+
+ md5((uint8_t *)nv, sizeof(nv), key);
+
+ return re_hprintf(pf,
+ "Digest realm=\"%s\", nonce=\"%w%llx\", "
+ "qop=\"auth\"%s",
+ auth->realm,
+ key, sizeof(key), nv[0],
+ auth->stale ? ", stale=true" : "");
+}
+
+
+static int chk_nonce(const struct pl *nonce, uint32_t expires)
+{
+ uint8_t nkey[MD5_SIZE], ckey[MD5_SIZE];
+ uint64_t nv[2];
+ struct pl pl;
+ int64_t age;
+ unsigned i;
+
+ if (!nonce || !nonce->p || nonce->l < NONCE_MIN_SIZE)
+ return EINVAL;
+
+ pl = *nonce;
+
+ for (i=0; i<sizeof(nkey); i++) {
+ nkey[i] = ch_hex(*pl.p++) << 4;
+ nkey[i] += ch_hex(*pl.p++);
+ pl.l -= 2;
+ }
+
+ nv[0] = pl_x64(&pl);
+ nv[1] = secret;
+
+ md5((uint8_t *)nv, sizeof(nv), ckey);
+
+ if (memcmp(nkey, ckey, MD5_SIZE))
+ return EAUTH;
+
+ age = time(NULL) - nv[0];
+
+ if (age < 0 || age > expires)
+ return ETIMEDOUT;
+
+ return 0;
+}
+
+
+/**
+ * Check HTTP digest authorization
+ *
+ * @param hval Authorization header value
+ * @param method Request method
+ * @param auth Authentication parameteres
+ * @param authh Authentication handler
+ * @param arg Authentication handler argument
+ *
+ * @return true if check is passed, otherwise false
+ */
+bool http_auth_check(const struct pl *hval, const struct pl *method,
+ struct http_auth *auth, http_auth_h *authh, void *arg)
+{
+ struct httpauth_digest_resp resp;
+ uint8_t ha1[MD5_SIZE];
+
+ if (!hval || !method || !auth || !authh)
+ return false;
+
+ if (httpauth_digest_response_decode(&resp, hval))
+ return false;
+
+ if (pl_strcasecmp(&resp.realm, auth->realm))
+ return false;
+
+ if (chk_nonce(&resp.nonce, NONCE_EXPIRES)) {
+ auth->stale = true;
+ return false;
+ }
+
+ if (authh(&resp.username, ha1, arg))
+ return false;
+
+ if (httpauth_digest_response_auth(&resp, method, ha1))
+ return false;
+
+ return true;
+}
+
+
+/**
+ * Check HTTP digest authorization of an HTTP request
+ *
+ * @param msg HTTP message
+ * @param auth Authentication parameteres
+ * @param authh Authentication handler
+ * @param arg Authentication handler argument
+ *
+ * @return true if check is passed, otherwise false
+ */
+bool http_auth_check_request(const struct http_msg *msg,
+ struct http_auth *auth,
+ http_auth_h *authh, void *arg)
+{
+ const struct http_hdr *hdr;
+
+ if (!msg)
+ return false;
+
+ hdr = http_msg_hdr(msg, HTTP_HDR_AUTHORIZATION);
+ if (!hdr)
+ return false;
+
+ return http_auth_check(&hdr->val, &msg->met, auth, authh, arg);
+}
diff --git a/src/http/chunk.c b/src/http/chunk.c
new file mode 100644
index 0000000..ec772f0
--- /dev/null
+++ b/src/http/chunk.c
@@ -0,0 +1,111 @@
+/**
+ * @file http/chunk.c Chunked Transfer Encoding
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include "http.h"
+
+
+static int decode_chunk_size(struct http_chunk *chunk, struct mbuf *mb)
+{
+ while (mbuf_get_left(mb)) {
+
+ char ch = (char)mbuf_read_u8(mb);
+ uint8_t c;
+
+ if (ch == '\n') {
+ if (chunk->digit) {
+ chunk->digit = false;
+ chunk->param = false;
+
+ return 0;
+ }
+ else
+ continue;
+ }
+
+ if (chunk->param)
+ continue;
+
+ if ('0' <= ch && ch <= '9')
+ c = ch - '0';
+ else if ('A' <= ch && ch <= 'F')
+ c = ch - 'A' + 10;
+ else if ('a' <= ch && ch <= 'f')
+ c = ch - 'a' + 10;
+ else if (ch == '\r' || ch == ' ' || ch == '\t')
+ continue;
+ else if (ch == ';' && chunk->digit) {
+ chunk->param = true;
+ continue;
+ }
+ else
+ return EPROTO;
+
+ chunk->digit = true;
+
+ chunk->size <<= 4;
+ chunk->size += c;
+ }
+
+ return ENODATA;
+}
+
+
+static int decode_trailer(struct http_chunk *chunk, struct mbuf *mb)
+{
+ while (mbuf_get_left(mb)) {
+
+ char ch = (char)mbuf_read_u8(mb);
+
+ if (ch == '\n') {
+ if (++chunk->lf >= 2)
+ return 0;
+ }
+ else if (ch != '\r')
+ chunk->lf = 0;
+ }
+
+ return ENODATA;
+}
+
+
+int http_chunk_decode(struct http_chunk *chunk, struct mbuf *mb, size_t *size)
+{
+ int err;
+
+ if (!chunk || !mb || !size)
+ return EINVAL;
+
+ if (chunk->trailer) {
+ err = decode_trailer(chunk, mb);
+ if (err)
+ return err;
+
+ *size = 0;
+
+ return 0;
+ }
+
+ err = decode_chunk_size(chunk, mb);
+ if (err)
+ return err;
+
+ if (chunk->size == 0) {
+ chunk->trailer = true;
+ chunk->lf = 1;
+
+ err = decode_trailer(chunk, mb);
+ if (err)
+ return err;
+ }
+
+ *size = chunk->size;
+ chunk->size = 0;
+
+ return 0;
+}
diff --git a/src/http/client.c b/src/http/client.c
new file mode 100644
index 0000000..39afa39
--- /dev/null
+++ b/src/http/client.c
@@ -0,0 +1,701 @@
+/**
+ * @file http/client.c HTTP Client
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_tmr.h>
+#include <re_srtp.h>
+#include <re_tcp.h>
+#include <re_tls.h>
+#include <re_dns.h>
+#include <re_msg.h>
+#include <re_http.h>
+#include "http.h"
+
+
+enum {
+ CONN_TIMEOUT = 30000,
+ RECV_TIMEOUT = 60000,
+ IDLE_TIMEOUT = 900000,
+ BUFSIZE_MAX = 524288,
+ CONN_BSIZE = 256,
+};
+
+struct http_cli {
+ struct list reql;
+ struct hash *ht_conn;
+ struct dnsc *dnsc;
+ struct tls *tls;
+};
+
+struct conn;
+
+struct http_req {
+ struct http_chunk chunk;
+ struct sa srvv[16];
+ struct le le;
+ struct http_req **reqp;
+ struct http_cli *cli;
+ struct http_msg *msg;
+ struct dns_query *dq;
+ struct conn *conn;
+ struct mbuf *mbreq;
+ struct mbuf *mb;
+ char *host;
+ http_resp_h *resph;
+ http_data_h *datah;
+ http_conn_h *connh;
+ void *arg;
+ size_t rx_len;
+ unsigned srvc;
+ uint16_t port;
+ bool chunked;
+ bool secure;
+ bool close;
+};
+
+
+struct conn {
+ struct tmr tmr;
+ struct sa addr;
+ struct le he;
+ struct http_req *req;
+ struct tls_conn *sc;
+ struct tcp_conn *tc;
+ uint64_t usec;
+};
+
+
+static void req_close(struct http_req *req, int err,
+ const struct http_msg *msg);
+static int req_connect(struct http_req *req);
+static void timeout_handler(void *arg);
+
+
+static void cli_destructor(void *arg)
+{
+ struct http_cli *cli = arg;
+ struct le *le = cli->reql.head;
+
+ while (le) {
+ struct http_req *req = le->data;
+
+ le = le->next;
+ req_close(req, ECONNABORTED, NULL);
+ }
+
+ hash_flush(cli->ht_conn);
+ mem_deref(cli->ht_conn);
+ mem_deref(cli->dnsc);
+ mem_deref(cli->tls);
+}
+
+
+static void req_destructor(void *arg)
+{
+ struct http_req *req = arg;
+
+ list_unlink(&req->le);
+ mem_deref(req->msg);
+ mem_deref(req->dq);
+ mem_deref(req->conn);
+ mem_deref(req->mbreq);
+ mem_deref(req->mb);
+ mem_deref(req->host);
+}
+
+
+static void conn_destructor(void *arg)
+{
+ struct conn *conn = arg;
+
+ tmr_cancel(&conn->tmr);
+ hash_unlink(&conn->he);
+ mem_deref(conn->sc);
+ mem_deref(conn->tc);
+}
+
+
+static void conn_idle(struct conn *conn)
+{
+ tmr_start(&conn->tmr, IDLE_TIMEOUT, timeout_handler, conn);
+ conn->req = NULL;
+}
+
+
+static void req_close(struct http_req *req, int err,
+ const struct http_msg *msg)
+{
+ list_unlink(&req->le);
+ req->dq = mem_deref(req->dq);
+ req->datah = NULL;
+
+ if (req->conn) {
+ if (req->connh)
+ req->connh(req->conn->tc, req->conn->sc, req->arg);
+
+ if (err || req->close || req->connh)
+ mem_deref(req->conn);
+ else
+ conn_idle(req->conn);
+
+ req->conn = NULL;
+ }
+
+ req->connh = NULL;
+
+ if (req->reqp) {
+ *req->reqp = NULL;
+ req->reqp = NULL;
+ }
+
+ if (req->resph) {
+ if (msg)
+ msg->mb->pos = 0;
+
+ req->resph(err, msg, req->arg);
+ req->resph = NULL;
+ }
+
+ mem_deref(req);
+}
+
+
+static void try_next(struct conn *conn, int err)
+{
+ struct http_req *req = conn->req;
+ bool retry = conn->usec > 1;
+
+ mem_deref(conn);
+
+ if (!req)
+ return;
+
+ req->conn = NULL;
+
+ if (retry)
+ ++req->srvc;
+
+ if (req->srvc > 0 && !req->msg) {
+
+ err = req_connect(req);
+ if (!err)
+ return;
+ }
+
+ req_close(req, err, NULL);
+}
+
+
+static int write_body_buf(struct http_msg *msg, const uint8_t *buf, size_t sz)
+{
+ if ((msg->mb->pos + sz) > BUFSIZE_MAX)
+ return EOVERFLOW;
+
+ return mbuf_write_mem(msg->mb, buf, sz);
+}
+
+
+static int write_body(struct http_req *req, struct mbuf *mb)
+{
+ const size_t size = min(mbuf_get_left(mb), req->rx_len);
+ int err;
+
+ if (size == 0)
+ return 0;
+
+ if (req->datah)
+ err = req->datah(mbuf_buf(mb), size, req->msg, req->arg);
+ else
+ err = write_body_buf(req->msg, mbuf_buf(mb), size);
+
+ if (err)
+ return err;
+
+ req->rx_len -= size;
+ mb->pos += size;
+
+ return 0;
+}
+
+
+static int req_recv(struct http_req *req, struct mbuf *mb, bool *last)
+{
+ int err;
+
+ *last = false;
+
+ if (!req->chunked) {
+
+ err = write_body(req, mb);
+ if (err)
+ return err;
+
+ if (req->rx_len == 0)
+ *last = true;
+
+ return 0;
+ }
+
+ while (mbuf_get_left(mb)) {
+
+ if (req->rx_len == 0) {
+
+ err = http_chunk_decode(&req->chunk, mb, &req->rx_len);
+ if (err == ENODATA)
+ return 0;
+ else if (err)
+ return err;
+ else if (req->rx_len == 0) {
+ *last = true;
+ return 0;
+ }
+ }
+
+ err = write_body(req, mb);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+
+static void timeout_handler(void *arg)
+{
+ struct conn *conn = arg;
+
+ try_next(conn, ETIMEDOUT);
+}
+
+
+static void estab_handler(void *arg)
+{
+ struct conn *conn = arg;
+ struct http_req *req = conn->req;
+ int err;
+
+ if (!req)
+ return;
+
+ err = tcp_send(conn->tc, req->mbreq);
+ if (err) {
+ try_next(conn, err);
+ return;
+ }
+
+ tmr_start(&conn->tmr, RECV_TIMEOUT, timeout_handler, conn);
+}
+
+
+static void recv_handler(struct mbuf *mb, void *arg)
+{
+ const struct http_hdr *hdr;
+ struct conn *conn = arg;
+ struct http_req *req = conn->req;
+ size_t pos;
+ bool last;
+ int err;
+
+ if (!req)
+ return;
+
+ if (req->msg) {
+ err = req_recv(req, mb, &last);
+ if (err || last)
+ goto out;
+
+ return;
+ }
+
+ if (req->mb) {
+
+ const size_t len = mbuf_get_left(mb);
+
+ if ((mbuf_get_left(req->mb) + len) > BUFSIZE_MAX) {
+ err = EOVERFLOW;
+ goto out;
+ }
+
+ pos = req->mb->pos;
+ req->mb->pos = req->mb->end;
+
+ err = mbuf_write_mem(req->mb, mbuf_buf(mb), len);
+ if (err)
+ goto out;
+
+ req->mb->pos = pos;
+ }
+ else {
+ req->mb = mem_ref(mb);
+ }
+
+ pos = req->mb->pos;
+
+ err = http_msg_decode(&req->msg, req->mb, false);
+ if (err) {
+ if (err == ENODATA) {
+ req->mb->pos = pos;
+ return;
+ }
+ goto out;
+ }
+
+ if (req->datah)
+ tmr_cancel(&conn->tmr);
+
+ hdr = http_msg_hdr(req->msg, HTTP_HDR_CONNECTION);
+ if (hdr && !pl_strcasecmp(&hdr->val, "close"))
+ req->close = true;
+
+ if (http_msg_hdr_has_value(req->msg, HTTP_HDR_TRANSFER_ENCODING,
+ "chunked"))
+ req->chunked = true;
+ else
+ req->rx_len = req->msg->clen;
+
+ err = req_recv(req, req->mb, &last);
+ if (err || last)
+ goto out;
+
+ return;
+
+ out:
+ req_close(req, err, req->msg);
+}
+
+
+static void close_handler(int err, void *arg)
+{
+ struct conn *conn = arg;
+
+ try_next(conn, err ? err : ECONNRESET);
+}
+
+
+static bool conn_cmp(struct le *le, void *arg)
+{
+ const struct conn *conn = le->data;
+ const struct http_req *req = arg;
+
+ if (!sa_cmp(&req->srvv[req->srvc], &conn->addr, SA_ALL))
+ return false;
+
+ if (req->secure != !!conn->sc)
+ return false;
+
+ return conn->req == NULL;
+}
+
+
+static int conn_connect(struct http_req *req)
+{
+ const struct sa *addr = &req->srvv[req->srvc];
+ struct conn *conn;
+ int err;
+
+ conn = list_ledata(hash_lookup(req->cli->ht_conn,
+ sa_hash(addr, SA_ALL), conn_cmp, req));
+ if (conn) {
+ err = tcp_send(conn->tc, req->mbreq);
+ if (!err) {
+ tmr_start(&conn->tmr, RECV_TIMEOUT,
+ timeout_handler, conn);
+
+ req->conn = conn;
+ conn->req = req;
+
+ ++conn->usec;
+
+ return 0;
+ }
+
+ mem_deref(conn);
+ }
+
+ conn = mem_zalloc(sizeof(*conn), conn_destructor);
+ if (!conn)
+ return ENOMEM;
+
+ hash_append(req->cli->ht_conn, sa_hash(addr, SA_ALL), &conn->he, conn);
+
+ conn->addr = *addr;
+ conn->usec = 1;
+
+ err = tcp_connect(&conn->tc, addr, estab_handler, recv_handler,
+ close_handler, conn);
+ if (err)
+ goto out;
+
+#ifdef USE_TLS
+ if (req->secure) {
+
+ err = tls_start_tcp(&conn->sc, req->cli->tls, conn->tc, 0);
+ if (err)
+ goto out;
+ }
+#endif
+
+ tmr_start(&conn->tmr, CONN_TIMEOUT, timeout_handler, conn);
+
+ req->conn = conn;
+ conn->req = req;
+
+ out:
+ if (err)
+ mem_deref(conn);
+
+ return err;
+}
+
+
+static int req_connect(struct http_req *req)
+{
+ int err = EINVAL;
+
+ while (req->srvc > 0) {
+
+ --req->srvc;
+
+ req->mb = mem_deref(req->mb);
+
+ err = conn_connect(req);
+ if (!err)
+ break;
+ }
+
+ return err;
+}
+
+
+static bool rr_handler(struct dnsrr *rr, void *arg)
+{
+ struct http_req *req = arg;
+
+ if (req->srvc >= ARRAY_SIZE(req->srvv))
+ return true;
+
+ switch (rr->type) {
+
+ case DNS_TYPE_A:
+ sa_set_in(&req->srvv[req->srvc++], rr->rdata.a.addr,
+ req->port);
+ break;
+
+ case DNS_TYPE_AAAA:
+ sa_set_in6(&req->srvv[req->srvc++], rr->rdata.aaaa.addr,
+ req->port);
+ break;
+ }
+
+ return false;
+}
+
+
+static void query_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg)
+{
+ struct http_req *req = arg;
+ (void)hdr;
+ (void)authl;
+ (void)addl;
+
+ dns_rrlist_apply2(ansl, req->host, DNS_TYPE_A, DNS_TYPE_AAAA,
+ DNS_CLASS_IN, true, rr_handler, req);
+ if (req->srvc == 0) {
+ err = err ? err : EDESTADDRREQ;
+ goto fail;
+ }
+
+ err = req_connect(req);
+ if (err)
+ goto fail;
+
+ return;
+
+ fail:
+ req_close(req, err, NULL);
+}
+
+
+/**
+ * Send an HTTP request
+ *
+ * @param reqp Pointer to allocated HTTP request object
+ * @param cli HTTP Client
+ * @param met Request method
+ * @param uri Request URI
+ * @param resph Response handler
+ * @param datah Content handler (optional)
+ * @param arg Handler argument
+ * @param fmt Formatted HTTP headers and body (optional)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int http_request(struct http_req **reqp, struct http_cli *cli, const char *met,
+ const char *uri, http_resp_h *resph, http_data_h *datah,
+ void *arg, const char *fmt, ...)
+{
+ struct pl scheme, host, port, path;
+ struct http_req *req;
+ uint16_t defport;
+ bool secure;
+ va_list ap;
+ int err;
+
+ if (!cli || !met || !uri)
+ return EINVAL;
+
+ if (re_regex(uri, strlen(uri), "[a-z]+://[^:/]+[:]*[0-9]*[^]+",
+ &scheme, &host, NULL, &port, &path) || scheme.p != uri)
+ return EINVAL;
+
+ if (!pl_strcasecmp(&scheme, "http") ||
+ !pl_strcasecmp(&scheme, "ws")) {
+ secure = false;
+ defport = 80;
+ }
+#ifdef USE_TLS
+ else if (!pl_strcasecmp(&scheme, "https") ||
+ !pl_strcasecmp(&scheme, "wss")) {
+ secure = true;
+ defport = 443;
+ }
+#endif
+ else
+ return ENOTSUP;
+
+ req = mem_zalloc(sizeof(*req), req_destructor);
+ if (!req)
+ return ENOMEM;
+
+ list_append(&cli->reql, &req->le, req);
+
+ req->cli = cli;
+ req->secure = secure;
+ req->port = pl_isset(&port) ? pl_u32(&port) : defport;
+ req->resph = resph;
+ req->datah = datah;
+ req->arg = arg;
+
+ err = pl_strdup(&req->host, &host);
+ if (err)
+ goto out;
+
+ req->mbreq = mbuf_alloc(1024);
+ if (!req->mbreq) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = mbuf_printf(req->mbreq,
+ "%s %r HTTP/1.1\r\n"
+ "Host: %r\r\n",
+ met, &path, &host);
+ if (fmt) {
+ va_start(ap, fmt);
+ err |= mbuf_vprintf(req->mbreq, fmt, ap);
+ va_end(ap);
+ }
+ else {
+ err |= mbuf_write_str(req->mbreq, "\r\n");
+ }
+ if (err)
+ goto out;
+
+ req->mbreq->pos = 0;
+
+ if (!sa_set_str(&req->srvv[0], req->host, req->port)) {
+
+ req->srvc = 1;
+
+ err = req_connect(req);
+ if (err)
+ goto out;
+ }
+ else {
+ err = dnsc_query(&req->dq, cli->dnsc, req->host,
+ DNS_TYPE_A, DNS_CLASS_IN, true,
+ query_handler, req);
+ if (err)
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(req);
+ else if (reqp) {
+ req->reqp = reqp;
+ *reqp = req;
+ }
+
+ return err;
+}
+
+
+/**
+ * Set HTTP request connection handler
+ *
+ * @param req HTTP request object
+ * @param connh Connection handler
+ */
+void http_req_set_conn_handler(struct http_req *req, http_conn_h *connh)
+{
+ if (!req)
+ return;
+
+ req->connh = connh;
+}
+
+
+/**
+ * Allocate an HTTP client instance
+ *
+ * @param clip Pointer to allocated HTTP client
+ * @param dnsc DNS Client
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int http_client_alloc(struct http_cli **clip, struct dnsc *dnsc)
+{
+ struct http_cli *cli;
+ int err;
+
+ if (!clip || !dnsc)
+ return EINVAL;
+
+ cli = mem_zalloc(sizeof(*cli), cli_destructor);
+ if (!cli)
+ return ENOMEM;
+
+ err = hash_alloc(&cli->ht_conn, CONN_BSIZE);
+ if (err)
+ goto out;
+
+#ifdef USE_TLS
+ err = tls_alloc(&cli->tls, TLS_METHOD_SSLV23, NULL, NULL);
+#else
+ err = 0;
+#endif
+ if (err)
+ goto out;
+
+ cli->dnsc = mem_ref(dnsc);
+
+ out:
+ if (err)
+ mem_deref(cli);
+ else
+ *clip = cli;
+
+ return err;
+}
diff --git a/src/http/http.h b/src/http/http.h
new file mode 100644
index 0000000..ba63999
--- /dev/null
+++ b/src/http/http.h
@@ -0,0 +1,17 @@
+/**
+ * @file http.h HTTP Private Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct http_chunk {
+ size_t size;
+ unsigned lf;
+ bool trailer;
+ bool digit;
+ bool param;
+};
+
+
+int http_chunk_decode(struct http_chunk *chunk, struct mbuf *mb, size_t *size);
diff --git a/src/http/mod.mk b/src/http/mod.mk
new file mode 100644
index 0000000..4394f58
--- /dev/null
+++ b/src/http/mod.mk
@@ -0,0 +1,11 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += http/auth.c
+SRCS += http/chunk.c
+SRCS += http/client.c
+SRCS += http/msg.c
+SRCS += http/server.c
diff --git a/src/http/msg.c b/src/http/msg.c
new file mode 100644
index 0000000..7b1f68a
--- /dev/null
+++ b/src/http/msg.c
@@ -0,0 +1,539 @@
+/**
+ * @file http/msg.c HTTP Message decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_msg.h>
+#include <re_http.h>
+
+
+enum {
+ STARTLINE_MAX = 8192,
+};
+
+
+static void hdr_destructor(void *arg)
+{
+ struct http_hdr *hdr = arg;
+
+ list_unlink(&hdr->le);
+}
+
+
+static void destructor(void *arg)
+{
+ struct http_msg *msg = arg;
+
+ list_flush(&msg->hdrl);
+ mem_deref(msg->_mb);
+ mem_deref(msg->mb);
+}
+
+
+static enum http_hdrid hdr_hash(const struct pl *name)
+{
+ if (!name->l)
+ return HTTP_HDR_NONE;
+
+ switch (name->p[0]) {
+
+ case 'x':
+ case 'X':
+ if (name->l > 1 && name->p[1] == '-')
+ return HTTP_HDR_NONE;
+
+ break;
+ }
+
+ return (enum http_hdrid)(hash_joaat_ci(name->p, name->l) & 0xfff);
+}
+
+
+static inline bool hdr_comma_separated(enum http_hdrid id)
+{
+ switch (id) {
+
+ case HTTP_HDR_ACCEPT:
+ case HTTP_HDR_ACCEPT_CHARSET:
+ case HTTP_HDR_ACCEPT_ENCODING:
+ case HTTP_HDR_ACCEPT_LANGUAGE:
+ case HTTP_HDR_ACCEPT_RANGES:
+ case HTTP_HDR_ALLOW:
+ case HTTP_HDR_CACHE_CONTROL:
+ case HTTP_HDR_CONNECTION:
+ case HTTP_HDR_CONTENT_ENCODING:
+ case HTTP_HDR_CONTENT_LANGUAGE:
+ case HTTP_HDR_EXPECT:
+ case HTTP_HDR_IF_MATCH:
+ case HTTP_HDR_IF_NONE_MATCH:
+ case HTTP_HDR_PRAGMA:
+ case HTTP_HDR_SEC_WEBSOCKET_EXTENSIONS:
+ case HTTP_HDR_SEC_WEBSOCKET_PROTOCOL:
+ case HTTP_HDR_SEC_WEBSOCKET_VERSION:
+ case HTTP_HDR_TE:
+ case HTTP_HDR_TRAILER:
+ case HTTP_HDR_TRANSFER_ENCODING:
+ case HTTP_HDR_UPGRADE:
+ case HTTP_HDR_VARY:
+ case HTTP_HDR_VIA:
+ case HTTP_HDR_WARNING:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+
+static inline int hdr_add(struct http_msg *msg, const struct pl *name,
+ enum http_hdrid id, const char *p, ssize_t l)
+{
+ struct http_hdr *hdr;
+ int err = 0;
+
+ hdr = mem_zalloc(sizeof(*hdr), hdr_destructor);
+ if (!hdr)
+ return ENOMEM;
+
+ hdr->name = *name;
+ hdr->val.p = p;
+ hdr->val.l = MAX(l, 0);
+ hdr->id = id;
+
+ list_append(&msg->hdrl, &hdr->le, hdr);
+
+ /* parse common headers */
+ switch (id) {
+
+ case HTTP_HDR_CONTENT_TYPE:
+ err = msg_ctype_decode(&msg->ctyp, &hdr->val);
+ break;
+
+ case HTTP_HDR_CONTENT_LENGTH:
+ msg->clen = pl_u32(&hdr->val);
+ break;
+
+ default:
+ break;
+ }
+
+ if (err)
+ mem_deref(hdr);
+
+ return err;
+}
+
+
+/**
+ * Decode a HTTP message
+ *
+ * @param msgp Pointer to allocated HTTP Message
+ * @param mb Buffer containing HTTP Message
+ * @param req True for request, false for response
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int http_msg_decode(struct http_msg **msgp, struct mbuf *mb, bool req)
+{
+ struct pl b, s, e, name, scode;
+ const char *p, *cv;
+ struct http_msg *msg;
+ bool comsep, quote;
+ enum http_hdrid id = HTTP_HDR_NONE;
+ uint32_t ws, lf;
+ size_t l;
+ int err;
+
+ if (!msgp || !mb)
+ return EINVAL;
+
+ p = (const char *)mbuf_buf(mb);
+ l = mbuf_get_left(mb);
+
+ if (re_regex(p, l, "[\r\n]*[^\r\n]+[\r]*[\n]1", &b, &s, NULL, &e))
+ return (l > STARTLINE_MAX) ? EBADMSG : ENODATA;
+
+ msg = mem_zalloc(sizeof(*msg), destructor);
+ if (!msg)
+ return ENOMEM;
+
+ msg->_mb = mem_ref(mb);
+
+ msg->mb = mbuf_alloc(8192);
+ if (!msg->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ if (req) {
+ if (re_regex(s.p, s.l, "[a-z]+ [^? ]+[^ ]* HTTP/[0-9.]+",
+ &msg->met, &msg->path, &msg->prm, &msg->ver) ||
+ msg->met.p != s.p) {
+ err = EBADMSG;
+ goto out;
+ }
+ }
+ else {
+ if (re_regex(s.p, s.l, "HTTP/[0-9.]+ [0-9]+[ ]*[^]*",
+ &msg->ver, &scode, NULL, &msg->reason) ||
+ msg->ver.p != s.p + 5) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ msg->scode = pl_u32(&scode);
+ }
+
+ l -= e.p + e.l - p;
+ p = e.p + e.l;
+
+ name.p = cv = NULL;
+ name.l = ws = lf = 0;
+ comsep = false;
+ quote = false;
+
+ for (; l > 0; p++, l--) {
+
+ switch (*p) {
+
+ case ' ':
+ case '\t':
+ lf = 0; /* folding */
+ ++ws;
+ break;
+
+ case '\r':
+ ++ws;
+ break;
+
+ case '\n':
+ ++ws;
+
+ if (!name.p) {
+ ++p; --l; /* no headers */
+ err = 0;
+ goto out;
+ }
+
+ if (!lf++)
+ break;
+
+ ++p; --l; /* eoh */
+
+ /*@fallthrough@*/
+
+ default:
+ if (lf || (*p == ',' && comsep && !quote)) {
+
+ if (!name.l) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ err = hdr_add(msg, &name, id, cv ? cv : p,
+ cv ? p - cv - ws : 0);
+ if (err)
+ goto out;
+
+ if (!lf) { /* comma separated */
+ cv = NULL;
+ break;
+ }
+
+ if (lf > 1) { /* eoh */
+ err = 0;
+ goto out;
+ }
+
+ comsep = false;
+ name.p = NULL;
+ cv = NULL;
+ lf = 0;
+ }
+
+ if (!name.p) {
+ name.p = p;
+ name.l = 0;
+ ws = 0;
+ }
+
+ if (!name.l) {
+ if (*p != ':') {
+ ws = 0;
+ break;
+ }
+
+ name.l = MAX((int)(p - name.p - ws), 0);
+ if (!name.l) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ id = hdr_hash(&name);
+ comsep = hdr_comma_separated(id);
+ break;
+ }
+
+ if (!cv) {
+ quote = false;
+ cv = p;
+ }
+
+ if (*p == '"')
+ quote = !quote;
+
+ ws = 0;
+ break;
+ }
+ }
+
+ err = ENODATA;
+
+ out:
+ if (err)
+ mem_deref(msg);
+ else {
+ *msgp = msg;
+ mb->pos = mb->end - l;
+ }
+
+ return err;
+}
+
+
+/**
+ * Get a HTTP Header from a HTTP Message
+ *
+ * @param msg HTTP Message
+ * @param id HTTP Header ID
+ *
+ * @return HTTP Header if found, NULL if not found
+ */
+const struct http_hdr *http_msg_hdr(const struct http_msg *msg,
+ enum http_hdrid id)
+{
+ return http_msg_hdr_apply(msg, true, id, NULL, NULL);
+}
+
+
+/**
+ * Apply a function handler to certain HTTP Headers
+ *
+ * @param msg HTTP Message
+ * @param fwd True to traverse forwards, false to traverse backwards
+ * @param id HTTP Header ID
+ * @param h Function handler
+ * @param arg Handler argument
+ *
+ * @return HTTP Header if handler returns true, otherwise NULL
+ */
+const struct http_hdr *http_msg_hdr_apply(const struct http_msg *msg,
+ bool fwd, enum http_hdrid id,
+ http_hdr_h *h, void *arg)
+{
+ struct le *le;
+
+ if (!msg)
+ return NULL;
+
+ le = fwd ? msg->hdrl.head : msg->hdrl.tail;
+
+ while (le) {
+ const struct http_hdr *hdr = le->data;
+
+ le = fwd ? le->next : le->prev;
+
+ if (hdr->id != id)
+ continue;
+
+ if (!h || h(hdr, arg))
+ return hdr;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get an unknown HTTP Header from a HTTP Message
+ *
+ * @param msg HTTP Message
+ * @param name Header name
+ *
+ * @return HTTP Header if found, NULL if not found
+ */
+const struct http_hdr *http_msg_xhdr(const struct http_msg *msg,
+ const char *name)
+{
+ return http_msg_xhdr_apply(msg, true, name, NULL, NULL);
+}
+
+
+/**
+ * Apply a function handler to certain unknown HTTP Headers
+ *
+ * @param msg HTTP Message
+ * @param fwd True to traverse forwards, false to traverse backwards
+ * @param name HTTP Header name
+ * @param h Function handler
+ * @param arg Handler argument
+ *
+ * @return HTTP Header if handler returns true, otherwise NULL
+ */
+const struct http_hdr *http_msg_xhdr_apply(const struct http_msg *msg,
+ bool fwd, const char *name,
+ http_hdr_h *h, void *arg)
+{
+ struct le *le;
+ struct pl pl;
+
+ if (!msg || !name)
+ return NULL;
+
+ pl_set_str(&pl, name);
+
+ le = fwd ? msg->hdrl.head : msg->hdrl.tail;
+
+ while (le) {
+ const struct http_hdr *hdr = le->data;
+
+ le = fwd ? le->next : le->prev;
+
+ if (pl_casecmp(&hdr->name, &pl))
+ continue;
+
+ if (!h || h(hdr, arg))
+ return hdr;
+ }
+
+ return NULL;
+}
+
+
+static bool count_handler(const struct http_hdr *hdr, void *arg)
+{
+ uint32_t *n = arg;
+ (void)hdr;
+
+ ++(*n);
+
+ return false;
+}
+
+
+/**
+ * Count the number of HTTP Headers
+ *
+ * @param msg HTTP Message
+ * @param id HTTP Header ID
+ *
+ * @return Number of HTTP Headers
+ */
+uint32_t http_msg_hdr_count(const struct http_msg *msg, enum http_hdrid id)
+{
+ uint32_t n = 0;
+
+ http_msg_hdr_apply(msg, true, id, count_handler, &n);
+
+ return n;
+}
+
+
+/**
+ * Count the number of unknown HTTP Headers
+ *
+ * @param msg HTTP Message
+ * @param name HTTP Header name
+ *
+ * @return Number of HTTP Headers
+ */
+uint32_t http_msg_xhdr_count(const struct http_msg *msg, const char *name)
+{
+ uint32_t n = 0;
+
+ http_msg_xhdr_apply(msg, true, name, count_handler, &n);
+
+ return n;
+}
+
+
+static bool value_handler(const struct http_hdr *hdr, void *arg)
+{
+ return 0 == pl_strcasecmp(&hdr->val, (const char *)arg);
+}
+
+
+/**
+ * Check if a HTTP Header matches a certain value
+ *
+ * @param msg HTTP Message
+ * @param id HTTP Header ID
+ * @param value Header value to check
+ *
+ * @return True if value matches, false if not
+ */
+bool http_msg_hdr_has_value(const struct http_msg *msg, enum http_hdrid id,
+ const char *value)
+{
+ return NULL != http_msg_hdr_apply(msg, true, id, value_handler,
+ (void *)value);
+}
+
+
+/**
+ * Check if an unknown HTTP Header matches a certain value
+ *
+ * @param msg HTTP Message
+ * @param name HTTP Header name
+ * @param value Header value to check
+ *
+ * @return True if value matches, false if not
+ */
+bool http_msg_xhdr_has_value(const struct http_msg *msg, const char *name,
+ const char *value)
+{
+ return NULL != http_msg_xhdr_apply(msg, true, name, value_handler,
+ (void *)value);
+}
+
+
+/**
+ * Print a HTTP Message
+ *
+ * @param pf Print function for output
+ * @param msg HTTP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int http_msg_print(struct re_printf *pf, const struct http_msg *msg)
+{
+ struct le *le;
+ int err;
+
+ if (!msg)
+ return 0;
+
+ if (pl_isset(&msg->met))
+ err = re_hprintf(pf, "%r %r%r HTTP/%r\n", &msg->met,
+ &msg->path, &msg->prm, &msg->ver);
+ else
+ err = re_hprintf(pf, "HTTP/%r %u %r\n", &msg->ver, msg->scode,
+ &msg->reason);
+
+ for (le=msg->hdrl.head; le; le=le->next) {
+
+ const struct http_hdr *hdr = le->data;
+
+ err |= re_hprintf(pf, "%r: %r (%i)\n", &hdr->name, &hdr->val,
+ hdr->id);
+ }
+
+ return err;
+}
diff --git a/src/http/server.c b/src/http/server.c
new file mode 100644
index 0000000..ff04770
--- /dev/null
+++ b/src/http/server.c
@@ -0,0 +1,522 @@
+/**
+ * @file http/server.c HTTP Server
+ *
+ * Copyright (C) 2011 Creytiv.com
+ */
+
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_fmt.h>
+#include <re_tmr.h>
+#include <re_srtp.h>
+#include <re_tcp.h>
+#include <re_tls.h>
+#include <re_msg.h>
+#include <re_http.h>
+
+
+enum {
+ TIMEOUT_IDLE = 600000,
+ TIMEOUT_INIT = 10000,
+ BUFSIZE_MAX = 524288,
+};
+
+struct http_sock {
+ struct list connl;
+ struct tcp_sock *ts;
+ struct tls *tls;
+ http_req_h *reqh;
+ void *arg;
+};
+
+struct http_conn {
+ struct le le;
+ struct tmr tmr;
+ struct sa peer;
+ struct http_sock *sock;
+ struct tcp_conn *tc;
+ struct tls_conn *sc;
+ struct mbuf *mb;
+};
+
+
+static void conn_close(struct http_conn *conn);
+
+
+static void sock_destructor(void *arg)
+{
+ struct http_sock *sock = arg;
+ struct le *le;
+
+ for (le=sock->connl.head; le;) {
+
+ struct http_conn *conn = le->data;
+
+ le = le->next;
+
+ conn_close(conn);
+ mem_deref(conn);
+ }
+
+ mem_deref(sock->tls);
+ mem_deref(sock->ts);
+}
+
+
+static void conn_destructor(void *arg)
+{
+ struct http_conn *conn = arg;
+
+ list_unlink(&conn->le);
+ tmr_cancel(&conn->tmr);
+ mem_deref(conn->sc);
+ mem_deref(conn->tc);
+ mem_deref(conn->mb);
+}
+
+
+static void conn_close(struct http_conn *conn)
+{
+ list_unlink(&conn->le);
+ tmr_cancel(&conn->tmr);
+ conn->sc = mem_deref(conn->sc);
+ conn->tc = mem_deref(conn->tc);
+ conn->sock = NULL;
+}
+
+
+static void timeout_handler(void *arg)
+{
+ struct http_conn *conn = arg;
+
+ conn_close(conn);
+ mem_deref(conn);
+}
+
+
+static void recv_handler(struct mbuf *mb, void *arg)
+{
+ struct http_conn *conn = arg;
+ int err = 0;
+
+ if (conn->mb) {
+
+ const size_t len = mbuf_get_left(mb), pos = conn->mb->pos;
+
+ if ((mbuf_get_left(conn->mb) + len) > BUFSIZE_MAX) {
+ err = EOVERFLOW;
+ goto out;
+ }
+
+ conn->mb->pos = conn->mb->end;
+
+ err = mbuf_write_mem(conn->mb, mbuf_buf(mb), len);
+ if (err)
+ goto out;
+
+ conn->mb->pos = pos;
+ }
+ else {
+ conn->mb = mem_ref(mb);
+ }
+
+ while (conn->mb) {
+ size_t end, pos = conn->mb->pos;
+ struct http_msg *msg;
+
+ err = http_msg_decode(&msg, conn->mb, true);
+ if (err) {
+ if (err == ENODATA) {
+ conn->mb->pos = pos;
+ err = 0;
+ break;
+ }
+
+ goto out;
+ }
+
+ if (mbuf_get_left(conn->mb) < msg->clen) {
+ conn->mb->pos = pos;
+ mem_deref(msg);
+ break;
+ }
+
+ mem_deref(msg->mb);
+ msg->mb = mem_ref(msg->_mb);
+
+ mb = conn->mb;
+
+ end = mb->end;
+ mb->end = mb->pos + msg->clen;
+
+ if (end > mb->end) {
+ struct mbuf *mbn = mbuf_alloc(end - mb->end);
+ if (!mbn) {
+ mem_deref(msg);
+ err = ENOMEM;
+ goto out;
+ }
+
+ (void)mbuf_write_mem(mbn, mb->buf + mb->end,
+ end - mb->end);
+ mbn->pos = 0;
+
+ mem_deref(conn->mb);
+ conn->mb = mbn;
+ }
+ else {
+ conn->mb = mem_deref(conn->mb);
+ }
+
+ if (conn->sock)
+ conn->sock->reqh(conn, msg, conn->sock->arg);
+
+ mem_deref(msg);
+
+ if (!conn->tc) {
+ err = ENOTCONN;
+ goto out;
+ }
+
+ tmr_start(&conn->tmr, TIMEOUT_IDLE, timeout_handler, conn);
+ }
+
+ out:
+ if (err) {
+ conn_close(conn);
+ mem_deref(conn);
+ }
+}
+
+
+static void close_handler(int err, void *arg)
+{
+ struct http_conn *conn = arg;
+ (void)err;
+
+ conn_close(conn);
+ mem_deref(conn);
+}
+
+
+static void connect_handler(const struct sa *peer, void *arg)
+{
+ struct http_sock *sock = arg;
+ struct http_conn *conn;
+ int err;
+
+ conn = mem_zalloc(sizeof(*conn), conn_destructor);
+ if (!conn) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ list_append(&sock->connl, &conn->le, conn);
+ conn->peer = *peer;
+ conn->sock = sock;
+
+ err = tcp_accept(&conn->tc, sock->ts, NULL, recv_handler,
+ close_handler, conn);
+ if (err)
+ goto out;
+
+#ifdef USE_TLS
+ if (sock->tls) {
+ err = tls_start_tcp(&conn->sc, sock->tls, conn->tc, 0);
+ if (err)
+ goto out;
+ }
+#endif
+
+ tmr_start(&conn->tmr, TIMEOUT_INIT, timeout_handler, conn);
+
+ out:
+ if (err) {
+ mem_deref(conn);
+ tcp_reject(sock->ts);
+ }
+}
+
+
+/**
+ * Create an HTTP socket
+ *
+ * @param sockp Pointer to returned HTTP Socket
+ * @param laddr Network address to listen on
+ * @param reqh Request handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int http_listen(struct http_sock **sockp, const struct sa *laddr,
+ http_req_h *reqh, void *arg)
+{
+ struct http_sock *sock;
+ int err;
+
+ if (!sockp || !laddr || !reqh)
+ return EINVAL;
+
+ sock = mem_zalloc(sizeof(*sock), sock_destructor);
+ if (!sock)
+ return ENOMEM;
+
+ err = tcp_listen(&sock->ts, laddr, connect_handler, sock);
+ if (err)
+ goto out;
+
+ sock->reqh = reqh;
+ sock->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(sock);
+ else
+ *sockp = sock;
+
+ return err;
+}
+
+
+/**
+ * Create an HTTP secure socket
+ *
+ * @param sockp Pointer to returned HTTP Socket
+ * @param laddr Network address to listen on
+ * @param cert File path of TLS certificate
+ * @param reqh Request handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int https_listen(struct http_sock **sockp, const struct sa *laddr,
+ const char *cert, http_req_h *reqh, void *arg)
+{
+ struct http_sock *sock;
+ int err;
+
+ if (!sockp || !laddr || !cert || !reqh)
+ return EINVAL;
+
+ err = http_listen(&sock, laddr, reqh, arg);
+ if (err)
+ return err;
+
+#ifdef USE_TLS
+ err = tls_alloc(&sock->tls, TLS_METHOD_SSLV23, cert, NULL);
+#else
+ err = EPROTONOSUPPORT;
+#endif
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(sock);
+ else
+ *sockp = sock;
+
+ return err;
+}
+
+
+/**
+ * Get the TCP socket of an HTTP socket
+ *
+ * @param sock HTTP socket
+ *
+ * @return TCP socket
+ */
+struct tcp_sock *http_sock_tcp(struct http_sock *sock)
+{
+ return sock ? sock->ts : NULL;
+}
+
+
+/**
+ * Get the peer address of an HTTP connection
+ *
+ * @param conn HTTP connection
+ *
+ * @return Peer address
+ */
+const struct sa *http_conn_peer(const struct http_conn *conn)
+{
+ return conn ? &conn->peer : NULL;
+}
+
+
+/**
+ * Get the TCP connection of an HTTP connection
+ *
+ * @param conn HTTP connection
+ *
+ * @return TCP connection
+ */
+struct tcp_conn *http_conn_tcp(struct http_conn *conn)
+{
+ return conn ? conn->tc : NULL;
+}
+
+
+/**
+ * Get the TLS connection of an HTTP connection
+ *
+ * @param conn HTTP connection
+ *
+ * @return TLS connection
+ */
+struct tls_conn *http_conn_tls(struct http_conn *conn)
+{
+ return conn ? conn->sc : NULL;
+}
+
+
+/**
+ * Close the HTTP connection
+ *
+ * @param conn HTTP connection
+ */
+void http_conn_close(struct http_conn *conn)
+{
+ if (!conn)
+ return;
+
+ conn->sc = mem_deref(conn->sc);
+ conn->tc = mem_deref(conn->tc);
+}
+
+
+static int http_vreply(struct http_conn *conn, uint16_t scode,
+ const char *reason, const char *fmt, va_list ap)
+{
+ struct mbuf *mb;
+ int err;
+
+ if (!conn || !scode || !reason)
+ return EINVAL;
+
+ if (!conn->tc)
+ return ENOTCONN;
+
+ mb = mbuf_alloc(8192);
+ if (!mb)
+ return ENOMEM;
+
+ err = mbuf_printf(mb, "HTTP/1.1 %u %s\r\n", scode, reason);
+ if (fmt)
+ err |= mbuf_vprintf(mb, fmt, ap);
+ else
+ err |= mbuf_write_str(mb, "Content-Length: 0\r\n\r\n");
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ err = tcp_send(conn->tc, mb);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Send an HTTP response
+ *
+ * @param conn HTTP connection
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ * @param fmt Formatted HTTP message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int http_reply(struct http_conn *conn, uint16_t scode, const char *reason,
+ const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, fmt);
+ err = http_vreply(conn, scode, reason, fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Send an HTTP response with content formatting
+ *
+ * @param conn HTTP connection
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ * @param ctype Content type
+ * @param fmt Formatted HTTP content
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int http_creply(struct http_conn *conn, uint16_t scode, const char *reason,
+ const char *ctype, const char *fmt, ...)
+{
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ if (!ctype || !fmt)
+ return EINVAL;
+
+ mb = mbuf_alloc(8192);
+ if (!mb)
+ return ENOMEM;
+
+ va_start(ap, fmt);
+ err = mbuf_vprintf(mb, fmt, ap);
+ va_end(ap);
+ if (err)
+ goto out;
+
+ err = http_reply(conn, scode, reason,
+ "Content-Type: %s\r\n"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ ctype,
+ mb->end,
+ mb->buf, mb->end);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Send an HTTP error response
+ *
+ * @param conn HTTP connection
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int http_ereply(struct http_conn *conn, uint16_t scode, const char *reason)
+{
+ return http_creply(conn, scode, reason, "text/html",
+ "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head><title>%u %s</title></head>\n"
+ "<body><h2>%u %s</h2></body>\n"
+ "</html>\n",
+ scode, reason,
+ scode, reason);
+}
diff --git a/src/httpauth/basic.c b/src/httpauth/basic.c
new file mode 100644
index 0000000..e7cc34f
--- /dev/null
+++ b/src/httpauth/basic.c
@@ -0,0 +1,13 @@
+/**
+ * @file basic.c HTTP Basic authentication
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_md5.h>
+#include <re_fmt.h>
+#include <re_httpauth.h>
+
+
+/* todo */
diff --git a/src/httpauth/digest.c b/src/httpauth/digest.c
new file mode 100644
index 0000000..584511e
--- /dev/null
+++ b/src/httpauth/digest.c
@@ -0,0 +1,214 @@
+/**
+ * @file digest.c HTTP Digest authentication (RFC 2617)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_md5.h>
+#include <re_sys.h>
+#include <re_httpauth.h>
+
+
+typedef void (digest_decode_h)(const struct pl *name, const struct pl *val,
+ void *arg);
+
+
+static const struct pl param_algorithm = PL("algorithm");
+static const struct pl param_cnonce = PL("cnonce");
+static const struct pl param_nc = PL("nc");
+static const struct pl param_nonce = PL("nonce");
+static const struct pl param_opaque = PL("opaque");
+static const struct pl param_qop = PL("qop");
+static const struct pl param_realm = PL("realm");
+static const struct pl param_response = PL("response");
+static const struct pl param_uri = PL("uri");
+static const struct pl param_username = PL("username");
+static const struct pl param_stale = PL("stale");
+
+
+static void challenge_decode(const struct pl *name, const struct pl *val,
+ void *arg)
+{
+ struct httpauth_digest_chall *chall = arg;
+
+ if (!pl_casecmp(name, ¶m_realm))
+ chall->realm = *val;
+ else if (!pl_casecmp(name, ¶m_nonce))
+ chall->nonce = *val;
+ else if (!pl_casecmp(name, ¶m_opaque))
+ chall->opaque= *val;
+ else if (!pl_casecmp(name, ¶m_stale))
+ chall->stale = *val;
+ else if (!pl_casecmp(name, ¶m_algorithm))
+ chall->algorithm = *val;
+ else if (!pl_casecmp(name, ¶m_qop))
+ chall->qop = *val;
+}
+
+
+static void response_decode(const struct pl *name, const struct pl *val,
+ void *arg)
+{
+ struct httpauth_digest_resp *resp = arg;
+
+ if (!pl_casecmp(name, ¶m_realm))
+ resp->realm = *val;
+ else if (!pl_casecmp(name, ¶m_nonce))
+ resp->nonce = *val;
+ else if (!pl_casecmp(name, ¶m_response))
+ resp->response = *val;
+ else if (!pl_casecmp(name, ¶m_username))
+ resp->username = *val;
+ else if (!pl_casecmp(name, ¶m_uri))
+ resp->uri = *val;
+ else if (!pl_casecmp(name, ¶m_nc))
+ resp->nc = *val;
+ else if (!pl_casecmp(name, ¶m_cnonce))
+ resp->cnonce = *val;
+ else if (!pl_casecmp(name, ¶m_qop))
+ resp->qop = *val;
+}
+
+
+static int digest_decode(const struct pl *hval, digest_decode_h *dech,
+ void *arg)
+{
+ struct pl r = *hval, start, end, name, val;
+
+ if (re_regex(r.p, r.l, "[ \t\r\n]*Digest[ \t\r\n]+", &start, &end) ||
+ start.p != r.p)
+ return EBADMSG;
+
+ pl_advance(&r, end.p - r.p);
+
+ while (!re_regex(r.p, r.l,
+ "[ \t\r\n,]+[a-z]+[ \t\r\n]*=[ \t\r\n]*[~ \t\r\n,]*",
+ NULL, &name, NULL, NULL, &val)) {
+
+ pl_advance(&r, val.p + val.l - r.p);
+
+ dech(&name, &val, arg);
+ }
+
+ return 0;
+}
+
+
+/**
+ * Decode a Digest challenge
+ *
+ * @param chall Digest challenge object to decode into
+ * @param hval Header value to decode from
+ *
+ * @return 0 if successfully decoded, otherwise errorcode
+ */
+int httpauth_digest_challenge_decode(struct httpauth_digest_chall *chall,
+ const struct pl *hval)
+{
+ int err;
+
+ if (!chall || !hval)
+ return EINVAL;
+
+ memset(chall, 0, sizeof(*chall));
+
+ err = digest_decode(hval, challenge_decode, chall);
+ if (err)
+ return err;
+
+ if (!chall->realm.p || !chall->nonce.p)
+ return EBADMSG;
+
+ return 0;
+}
+
+
+/**
+ * Decode a Digest response
+ *
+ * @param resp Digest response object to decode into
+ * @param hval Header value to decode from
+ *
+ * @return 0 if successfully decoded, otherwise errorcode
+ */
+int httpauth_digest_response_decode(struct httpauth_digest_resp *resp,
+ const struct pl *hval)
+{
+ int err;
+
+ if (!resp || !hval)
+ return EINVAL;
+
+ memset(resp, 0, sizeof(*resp));
+
+ err = digest_decode(hval, response_decode, resp);
+ if (err)
+ return err;
+
+ if (!resp->realm.p ||
+ !resp->nonce.p ||
+ !resp->response.p ||
+ !resp->username.p ||
+ !resp->uri.p)
+ return EBADMSG;
+
+ return 0;
+}
+
+
+/**
+ * Authenticate a digest response
+ *
+ * @param resp Digest response
+ * @param method Request method
+ * @param ha1 HA1 value from MD5(username:realm:password)
+ *
+ * @return 0 if successfully authenticated, otherwise errorcode
+ */
+int httpauth_digest_response_auth(const struct httpauth_digest_resp *resp,
+ const struct pl *method, const uint8_t *ha1)
+{
+ uint8_t ha2[MD5_SIZE], digest[MD5_SIZE], response[MD5_SIZE];
+ const char *p;
+ uint32_t i;
+ int err;
+
+ if (!resp || !method || !ha1)
+ return EINVAL;
+
+ if (resp->response.l != 32)
+ return EAUTH;
+
+ err = md5_printf(ha2, "%r:%r", method, &resp->uri);
+ if (err)
+ return err;
+
+ if (pl_isset(&resp->qop))
+ err = md5_printf(digest, "%w:%r:%r:%r:%r:%w",
+ ha1, (size_t)MD5_SIZE,
+ &resp->nonce,
+ &resp->nc,
+ &resp->cnonce,
+ &resp->qop,
+ ha2, sizeof(ha2));
+ else
+ err = md5_printf(digest, "%w:%r:%w",
+ ha1, (size_t)MD5_SIZE,
+ &resp->nonce,
+ ha2, sizeof(ha2));
+ if (err)
+ return err;
+
+ for (i=0, p=resp->response.p; i<sizeof(response); i++) {
+ response[i] = ch_hex(*p++) << 4;
+ response[i] += ch_hex(*p++);
+ }
+
+ if (memcmp(digest, response, MD5_SIZE))
+ return EAUTH;
+
+ return 0;
+}
diff --git a/src/httpauth/mod.mk b/src/httpauth/mod.mk
new file mode 100644
index 0000000..da0c931
--- /dev/null
+++ b/src/httpauth/mod.mk
@@ -0,0 +1,8 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+#SRCS += httpauth/basic.c
+SRCS += httpauth/digest.c
diff --git a/src/ice/cand.c b/src/ice/cand.c
new file mode 100644
index 0000000..5d4ee05
--- /dev/null
+++ b/src/ice/cand.c
@@ -0,0 +1,322 @@
+/**
+ * @file cand.c ICE Candidates
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_sys.h>
+#include <re_stun.h>
+#include <re_turn.h>
+#include <re_ice.h>
+#include "ice.h"
+
+
+#define DEBUG_MODULE "icecand"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static void cand_destructor(void *arg)
+{
+ struct ice_cand *cand = arg;
+
+ list_unlink(&cand->le);
+ mem_deref(cand->foundation);
+ mem_deref(cand->ifname);
+
+ if (cand != cand->base)
+ mem_deref(cand->base);
+}
+
+
+/** Foundation is a hash of IP address and candidate type */
+static int compute_foundation(struct ice_cand *cand)
+{
+ uint32_t v;
+
+ v = sa_hash(&cand->addr, SA_ADDR);
+ v ^= cand->type;
+
+ return re_sdprintf(&cand->foundation, "%08x", v);
+}
+
+
+static int cand_alloc(struct ice_cand **candp, struct icem *icem,
+ enum ice_cand_type type, unsigned compid,
+ uint32_t prio, const char *ifname,
+ enum ice_transp transp, const struct sa *addr)
+{
+ struct ice_cand *cand;
+ int err;
+
+ if (!icem)
+ return EINVAL;
+
+ cand = mem_zalloc(sizeof(*cand), cand_destructor);
+ if (!cand)
+ return ENOMEM;
+
+ list_append(&icem->lcandl, &cand->le, cand);
+
+ cand->type = type;
+ cand->compid = compid;
+ cand->prio = prio;
+ cand->transp = transp;
+
+ sa_cpy(&cand->addr, addr);
+
+ err = compute_foundation(cand);
+
+ if (ifname)
+ err |= str_dup(&cand->ifname, ifname);
+
+ if (err)
+ mem_deref(cand);
+ else if (candp)
+ *candp = cand;
+
+ return err;
+}
+
+
+int icem_lcand_add_base(struct icem *icem, unsigned compid, uint16_t lprio,
+ const char *ifname, enum ice_transp transp,
+ const struct sa *addr)
+{
+ struct icem_comp *comp;
+ struct ice_cand *cand;
+ int err;
+
+ comp = icem_comp_find(icem, compid);
+ if (!comp)
+ return ENOENT;
+
+ err = cand_alloc(&cand, icem, ICE_CAND_TYPE_HOST, compid,
+ ice_cand_calc_prio(ICE_CAND_TYPE_HOST, lprio, compid),
+ ifname, transp, addr);
+ if (err)
+ return err;
+
+ /* the base is itself */
+ cand->base = cand;
+
+ sa_set_port(&cand->addr, comp->lport);
+
+ return 0;
+}
+
+
+int icem_lcand_add(struct icem *icem, struct ice_cand *base,
+ enum ice_cand_type type,
+ const struct sa *addr)
+{
+ struct ice_cand *cand;
+ int err;
+
+ if (!base)
+ return EINVAL;
+
+ err = cand_alloc(&cand, icem, type, base->compid,
+ ice_cand_calc_prio(type, 0, base->compid),
+ base->ifname, base->transp, addr);
+ if (err)
+ return err;
+
+ cand->base = mem_ref(base);
+ sa_cpy(&cand->rel, &base->addr);
+
+ return 0;
+}
+
+
+int icem_rcand_add(struct icem *icem, enum ice_cand_type type, unsigned compid,
+ uint32_t prio, const struct sa *addr,
+ const struct sa *rel_addr, const struct pl *foundation)
+{
+ struct ice_cand *rcand;
+ int err;
+
+ if (!icem || !foundation)
+ return EINVAL;
+
+ rcand = mem_zalloc(sizeof(*rcand), cand_destructor);
+ if (!rcand)
+ return ENOMEM;
+
+ list_append(&icem->rcandl, &rcand->le, rcand);
+
+ rcand->type = type;
+ rcand->compid = compid;
+ rcand->prio = prio;
+
+ sa_cpy(&rcand->addr, addr);
+ sa_cpy(&rcand->rel, rel_addr);
+
+ err = pl_strdup(&rcand->foundation, foundation);
+
+ if (err)
+ mem_deref(rcand);
+
+ return err;
+}
+
+
+int icem_rcand_add_prflx(struct ice_cand **rcp, struct icem *icem,
+ unsigned compid, uint32_t prio,
+ const struct sa *addr)
+{
+ struct ice_cand *rcand;
+ int err;
+
+ if (!icem || !addr)
+ return EINVAL;
+
+ rcand = mem_zalloc(sizeof(*rcand), cand_destructor);
+ if (!rcand)
+ return ENOMEM;
+
+ list_append(&icem->rcandl, &rcand->le, rcand);
+
+ rcand->type = ICE_CAND_TYPE_PRFLX;
+ rcand->compid = compid;
+ rcand->prio = prio;
+ rcand->addr = *addr;
+
+ err = re_sdprintf(&rcand->foundation, "%08x", rand_u32());
+ if (err)
+ goto out;
+
+ icecomp_printf(icem_comp_find(icem, compid),
+ "added PeerReflexive remote candidate"
+ " with priority %u (%J)\n", prio, addr);
+
+ out:
+ if (err)
+ mem_deref(rcand);
+ else if (rcp)
+ *rcp = rcand;
+
+ return err;
+}
+
+
+struct ice_cand *icem_cand_find(const struct list *lst, unsigned compid,
+ const struct sa *addr)
+{
+ struct le *le;
+
+ for (le = list_head(lst); le; le = le->next) {
+
+ struct ice_cand *cand = le->data;
+
+ if (compid && cand->compid != compid)
+ continue;
+
+ if (addr && !sa_cmp(&cand->addr, addr, SA_ALL))
+ continue;
+
+ return cand;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find the highest priority LCAND on the check-list of type HOST/RELAY
+ *
+ * @param icem ICE Media object
+ * @param compid Component ID
+ *
+ * @return Local candidate if found, otherwise NULL
+ */
+struct ice_cand *icem_lcand_find_checklist(const struct icem *icem,
+ unsigned compid)
+{
+ struct le *le;
+
+ for (le = icem->checkl.head; le; le = le->next) {
+ struct ice_candpair *cp = le->data;
+
+ if (cp->lcand->compid != compid)
+ continue;
+
+ switch (cp->lcand->type) {
+
+ case ICE_CAND_TYPE_HOST:
+ case ICE_CAND_TYPE_RELAY:
+ return cp->lcand;
+
+ default:
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+
+struct ice_cand *icem_lcand_base(struct ice_cand *lcand)
+{
+ return lcand ? lcand->base : NULL;
+}
+
+
+const struct sa *icem_lcand_addr(const struct ice_cand *cand)
+{
+ return cand ? &cand->addr : NULL;
+}
+
+
+int icem_cands_debug(struct re_printf *pf, const struct list *lst)
+{
+ struct le *le;
+ int err;
+
+ err = re_hprintf(pf, " (%u)\n", list_count(lst));
+
+ for (le = list_head(lst); le && !err; le = le->next) {
+
+ const struct ice_cand *cand = le->data;
+
+ err |= re_hprintf(pf, " {%u} fnd=%-2s prio=%08x %24H",
+ cand->compid, cand->foundation, cand->prio,
+ icem_cand_print, cand);
+
+ if (sa_isset(&cand->rel, SA_ADDR))
+ err |= re_hprintf(pf, " (rel-addr=%J)", &cand->rel);
+
+ err |= re_hprintf(pf, "\n");
+ }
+
+ return err;
+}
+
+
+int icem_cand_print(struct re_printf *pf, const struct ice_cand *cand)
+{
+ int err = 0;
+
+ if (!cand)
+ return 0;
+
+ if (cand->ifname)
+ err |= re_hprintf(pf, "%s:", cand->ifname);
+
+ err |= re_hprintf(pf, "%s:%J",
+ ice_cand_type2name(cand->type), &cand->addr);
+
+ return err;
+}
+
+enum ice_cand_type icem_cand_type(const struct ice_cand *cand)
+{
+ return cand ? cand->type : (enum ice_cand_type)-1;
+}
diff --git a/src/ice/candpair.c b/src/ice/candpair.c
new file mode 100644
index 0000000..d095ca7
--- /dev/null
+++ b/src/ice/candpair.c
@@ -0,0 +1,449 @@
+/**
+ * @file candpair.c ICE Candidate Pairs
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_stun.h>
+#include <re_ice.h>
+#include "ice.h"
+
+
+#define DEBUG_MODULE "cndpair"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static void candpair_destructor(void *arg)
+{
+ struct ice_candpair *cp = arg;
+
+ list_unlink(&cp->le);
+ mem_deref(cp->ct_conn);
+ mem_deref(cp->lcand);
+ mem_deref(cp->rcand);
+}
+
+
+static bool sort_handler(struct le *le1, struct le *le2, void *arg)
+{
+ const struct ice_candpair *cp1 = le1->data, *cp2 = le2->data;
+ (void)arg;
+
+ return cp1->pprio >= cp2->pprio;
+}
+
+
+static void candpair_set_pprio(struct ice_candpair *cp)
+{
+ uint32_t g, d;
+
+ if (ICE_ROLE_CONTROLLING == cp->icem->lrole) {
+ g = cp->lcand->prio;
+ d = cp->rcand->prio;
+ }
+ else {
+ g = cp->rcand->prio;
+ d = cp->lcand->prio;
+ }
+
+ cp->pprio = ice_calc_pair_prio(g, d);
+}
+
+
+/**
+ * Add candidate pair to list, sorted by pair priority (highest is first)
+ */
+static void list_add_sorted(struct list *list, struct ice_candpair *cp)
+{
+ struct le *le;
+
+ /* find our slot */
+ for (le = list_tail(list); le; le = le->prev) {
+ struct ice_candpair *cp0 = le->data;
+
+ if (cp->pprio < cp0->pprio) {
+ list_insert_after(list, le, &cp->le, cp);
+ return;
+ }
+ }
+
+ list_prepend(list, &cp->le, cp);
+}
+
+
+int icem_candpair_alloc(struct ice_candpair **cpp, struct icem *icem,
+ struct ice_cand *lcand, struct ice_cand *rcand)
+{
+ struct ice_candpair *cp;
+ struct icem_comp *comp;
+
+ if (!icem || !lcand || !rcand)
+ return EINVAL;
+
+ comp = icem_comp_find(icem, lcand->compid);
+ if (!comp)
+ return ENOENT;
+
+ cp = mem_zalloc(sizeof(*cp), candpair_destructor);
+ if (!cp)
+ return ENOMEM;
+
+ cp->icem = icem;
+ cp->comp = comp;
+ cp->lcand = mem_ref(lcand);
+ cp->rcand = mem_ref(rcand);
+ cp->state = ICE_CANDPAIR_FROZEN;
+ cp->def = comp->def_lcand == lcand && comp->def_rcand == rcand;
+
+ candpair_set_pprio(cp);
+
+ list_add_sorted(&icem->checkl, cp);
+
+ if (cpp)
+ *cpp = cp;
+
+ return 0;
+}
+
+
+int icem_candpair_clone(struct ice_candpair **cpp, struct ice_candpair *cp0,
+ struct ice_cand *lcand, struct ice_cand *rcand)
+{
+ struct ice_candpair *cp;
+
+ if (!cp0)
+ return EINVAL;
+
+ cp = mem_zalloc(sizeof(*cp), candpair_destructor);
+ if (!cp)
+ return ENOMEM;
+
+ cp->icem = cp0->icem;
+ cp->comp = cp0->comp;
+ cp->lcand = mem_ref(lcand ? lcand : cp0->lcand);
+ cp->rcand = mem_ref(rcand ? rcand : cp0->rcand);
+ cp->def = cp0->def;
+ cp->valid = cp0->valid;
+ cp->nominated = cp0->nominated;
+ cp->state = cp0->state;
+ cp->pprio = cp0->pprio;
+ cp->err = cp0->err;
+ cp->scode = cp0->scode;
+
+ list_add_sorted(&cp0->icem->checkl, cp);
+
+ if (cpp)
+ *cpp = cp;
+
+ return 0;
+}
+
+
+/**
+ * Computing Pair Priority and Ordering Pairs
+ *
+ * @param lst Checklist (struct ice_candpair)
+ */
+void icem_candpair_prio_order(struct list *lst)
+{
+ struct le *le;
+
+ for (le = list_head(lst); le; le = le->next) {
+ struct ice_candpair *cp = le->data;
+
+ candpair_set_pprio(cp);
+ }
+
+ list_sort(lst, sort_handler, NULL);
+}
+
+
+/* cancel transaction */
+void icem_candpair_cancel(struct ice_candpair *cp)
+{
+ if (!cp)
+ return;
+
+ cp->ct_conn = mem_deref(cp->ct_conn);
+}
+
+
+void icem_candpair_make_valid(struct ice_candpair *cp)
+{
+ if (!cp)
+ return;
+
+ cp->err = 0;
+ cp->scode = 0;
+ cp->valid = true;
+
+ icem_candpair_set_state(cp, ICE_CANDPAIR_SUCCEEDED);
+
+ list_unlink(&cp->le);
+ list_add_sorted(&cp->icem->validl, cp);
+}
+
+
+void icem_candpair_failed(struct ice_candpair *cp, int err, uint16_t scode)
+{
+ if (!cp)
+ return;
+
+ cp->err = err;
+ cp->scode = scode;
+ cp->valid = false;
+
+ icem_candpair_set_state(cp, ICE_CANDPAIR_FAILED);
+}
+
+
+void icem_candpair_set_state(struct ice_candpair *cp,
+ enum ice_candpair_state state)
+{
+ if (!cp)
+ return;
+ if (cp->state == state || icem_candpair_iscompleted(cp))
+ return;
+
+ icecomp_printf(cp->comp,
+ "%5s <---> %5s FSM: %10s ===> %-10s\n",
+ ice_cand_type2name(cp->lcand->type),
+ ice_cand_type2name(cp->rcand->type),
+ ice_candpair_state2name(cp->state),
+ ice_candpair_state2name(state));
+
+ cp->state = state;
+}
+
+
+/**
+ * Delete all Candidate-Pairs where the Local candidate is of a given type
+ *
+ * @param lst Checklist or Validlist
+ * @param type Candidate type
+ * @param compid Component ID
+ */
+void icem_candpairs_flush(struct list *lst, enum ice_cand_type type,
+ unsigned compid)
+{
+ struct le *le = list_head(lst);
+
+ while (le) {
+
+ struct ice_candpair *cp = le->data;
+
+ le = le->next;
+
+ if (cp->lcand->compid != compid)
+ continue;
+
+ if (cp->lcand->type != type)
+ continue;
+
+ mem_deref(cp);
+ }
+}
+
+
+bool icem_candpair_iscompleted(const struct ice_candpair *cp)
+{
+ if (!cp)
+ return false;
+
+ return cp->state == ICE_CANDPAIR_FAILED ||
+ cp->state == ICE_CANDPAIR_SUCCEEDED;
+}
+
+
+/**
+ * Compare local and remote candidates of two candidate pairs
+ *
+ * @param cp1 First Candidate pair
+ * @param cp2 Second Candidate pair
+ *
+ * @return true if match
+ */
+bool icem_candpair_cmp(const struct ice_candpair *cp1,
+ const struct ice_candpair *cp2)
+{
+ if (!sa_cmp(&cp1->lcand->addr, &cp2->lcand->addr, SA_ALL))
+ return false;
+
+ return sa_cmp(&cp1->rcand->addr, &cp2->rcand->addr, SA_ALL);
+}
+
+
+/**
+ * Find the highest-priority candidate-pair in a given list, with
+ * optional match parameters
+ *
+ * @param lst List of candidate pairs
+ * @param lcand Local candidate (optional)
+ * @param rcand Remote candidate (optional)
+ *
+ * @return Matching candidate pair if found, otherwise NULL
+ *
+ * note: assume list is sorted by priority
+ */
+struct ice_candpair *icem_candpair_find(const struct list *lst,
+ const struct ice_cand *lcand,
+ const struct ice_cand *rcand)
+{
+ struct le *le;
+
+ for (le = list_head(lst); le; le = le->next) {
+
+ struct ice_candpair *cp = le->data;
+
+ if (!cp->lcand || !cp->rcand) {
+ DEBUG_WARNING("corrupt candpair %p\n", cp);
+ continue;
+ }
+
+ if (lcand && cp->lcand != lcand)
+ continue;
+
+ if (rcand && cp->rcand != rcand)
+ continue;
+
+ return cp;
+ }
+
+ return NULL;
+}
+
+
+struct ice_candpair *icem_candpair_find_st(const struct list *lst,
+ unsigned compid,
+ enum ice_candpair_state state)
+{
+ struct le *le;
+
+ for (le = list_head(lst); le; le = le->next) {
+
+ struct ice_candpair *cp = le->data;
+
+ if (compid && cp->lcand->compid != compid)
+ continue;
+
+ if (cp->state != state)
+ continue;
+
+ return cp;
+ }
+
+ return NULL;
+}
+
+
+struct ice_candpair *icem_candpair_find_compid(const struct list *lst,
+ unsigned compid)
+{
+ struct le *le;
+
+ for (le = list_head(lst); le; le = le->next) {
+
+ struct ice_candpair *cp = le->data;
+
+ if (cp->lcand->compid != compid)
+ continue;
+
+ return cp;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find a remote candidate in the checklist or validlist
+ *
+ * @param icem ICE Media object
+ * @param rcand Remote candidate
+ *
+ * @return Candidate pair if found, otherwise NULL
+ */
+struct ice_candpair *icem_candpair_find_rcand(struct icem *icem,
+ const struct ice_cand *rcand)
+{
+ struct ice_candpair *cp;
+
+ cp = icem_candpair_find(&icem->checkl, NULL, rcand);
+ if (cp)
+ return cp;
+
+ cp = icem_candpair_find(&icem->validl, NULL, rcand);
+ if (cp)
+ return cp;
+
+ return NULL;
+}
+
+
+bool icem_candpair_cmp_fnd(const struct ice_candpair *cp1,
+ const struct ice_candpair *cp2)
+{
+ if (!cp1 || !cp2)
+ return false;
+
+ return 0 == strcmp(cp1->lcand->foundation, cp2->lcand->foundation) &&
+ 0 == strcmp(cp1->rcand->foundation, cp2->rcand->foundation);
+}
+
+
+int icem_candpair_debug(struct re_printf *pf, const struct ice_candpair *cp)
+{
+ int err;
+
+ if (!cp)
+ return 0;
+
+ err = re_hprintf(pf, "{comp=%u} %10s {%c%c%c} %28H <---> %28H",
+ cp->lcand->compid,
+ ice_candpair_state2name(cp->state),
+ cp->def ? 'D' : ' ',
+ cp->valid ? 'V' : ' ',
+ cp->nominated ? 'N' : ' ',
+ icem_cand_print, cp->lcand,
+ icem_cand_print, cp->rcand);
+
+ if (cp->err)
+ err |= re_hprintf(pf, " (%m)", cp->err);
+
+ if (cp->scode)
+ err |= re_hprintf(pf, " [%u]", cp->scode);
+
+ return err;
+}
+
+
+int icem_candpairs_debug(struct re_printf *pf, const struct list *list)
+{
+ struct le *le;
+ int err;
+
+ if (!list)
+ return 0;
+
+ err = re_hprintf(pf, " (%u)\n", list_count(list));
+
+ for (le = list->head; le && !err; le = le->next) {
+
+ const struct ice_candpair *cp = le->data;
+ bool is_selected = (cp == cp->comp->cp_sel);
+
+ err = re_hprintf(pf, " %c %H\n",
+ is_selected ? '*' : ' ',
+ icem_candpair_debug, cp);
+ }
+
+ return err;
+}
diff --git a/src/ice/chklist.c b/src/ice/chklist.c
new file mode 100644
index 0000000..24e4f34
--- /dev/null
+++ b/src/ice/chklist.c
@@ -0,0 +1,341 @@
+/**
+ * @file chklist.c ICE Checklist
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_stun.h>
+#include <re_ice.h>
+#include "ice.h"
+
+
+#define DEBUG_MODULE "chklist"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/**
+ * Forming Candidate Pairs
+ */
+static int candpairs_form(struct icem *icem)
+{
+ struct le *le;
+ int err = 0;
+
+ if (list_isempty(&icem->lcandl))
+ return ENOENT;
+
+ if (list_isempty(&icem->rcandl)) {
+ DEBUG_WARNING("%s: no remote candidates\n", icem->name);
+ return ENOENT;
+ }
+
+ for (le = icem->lcandl.head; le; le = le->next) {
+
+ struct ice_cand *lcand = le->data;
+ struct le *rle;
+
+ for (rle = icem->rcandl.head; rle; rle = rle->next) {
+
+ struct ice_cand *rcand = rle->data;
+
+ if (lcand->compid != rcand->compid)
+ continue;
+
+ if (sa_af(&lcand->addr) != sa_af(&rcand->addr))
+ continue;
+
+ err = icem_candpair_alloc(NULL, icem, lcand, rcand);
+ if (err)
+ return err;
+ }
+ }
+
+ return err;
+}
+
+
+/* Replace server reflexive candidates by its base */
+static const struct sa *cand_srflx_addr(const struct ice_cand *c)
+{
+ return (ICE_CAND_TYPE_SRFLX == c->type) ? &c->base->addr : &c->addr;
+}
+
+
+/* return: NULL to keep, pointer to remove object */
+static void *unique_handler(struct le *le1, struct le *le2)
+{
+ struct ice_candpair *cp1 = le1->data, *cp2 = le2->data;
+
+ if (cp1->comp->id != cp2->comp->id)
+ return NULL;
+
+ if (!sa_cmp(cand_srflx_addr(cp1->lcand),
+ cand_srflx_addr(cp2->lcand), SA_ALL) ||
+ !sa_cmp(&cp1->rcand->addr, &cp2->rcand->addr, SA_ALL))
+ return NULL;
+
+ return cp1->pprio < cp2->pprio ? cp1 : cp2;
+}
+
+
+/**
+ * Pruning the Pairs
+ */
+static void candpair_prune(struct icem *icem)
+{
+ /* The agent MUST prune the list.
+ This is done by removing a pair if its local and remote
+ candidates are identical to the local and remote candidates
+ of a pair higher up on the priority list.
+
+ NOTE: This logic assumes the list is sorted by priority
+ */
+
+ uint32_t n = ice_list_unique(&icem->checkl, unique_handler);
+ if (n > 0) {
+ DEBUG_NOTICE("%s: pruned candidate pairs: %u\n",
+ icem->name, n);
+ }
+}
+
+
+/**
+ * Computing States
+ *
+ * @param icem ICE Media object
+ */
+void ice_candpair_set_states(struct icem *icem)
+{
+ struct le *le, *le2;
+
+ /*
+ For all pairs with the same foundation, it sets the state of
+ the pair with the lowest component ID to Waiting. If there is
+ more than one such pair, the one with the highest priority is
+ used.
+ */
+
+ for (le = icem->checkl.head; le; le = le->next) {
+
+ struct ice_candpair *cp = le->data;
+
+ for (le2 = icem->checkl.head; le2; le2 = le2->next) {
+
+ struct ice_candpair *cp2 = le2->data;
+
+ if (!icem_candpair_cmp_fnd(cp, cp2))
+ continue;
+
+ if (cp2->lcand->compid < cp->lcand->compid &&
+ cp2->pprio > cp->pprio)
+ cp = cp2;
+ }
+
+ icem_candpair_set_state(cp, ICE_CANDPAIR_WAITING);
+ }
+}
+
+
+/**
+ * Forming the Check Lists
+ *
+ * To form the check list for a media stream,
+ * the agent forms candidate pairs, computes a candidate pair priority,
+ * orders the pairs by priority, prunes them, and sets their states.
+ * These steps are described in this section.
+ *
+ * @param icem ICE Media object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int icem_checklist_form(struct icem *icem)
+{
+ int err;
+
+ if (!icem)
+ return EINVAL;
+
+ if (ICE_MODE_LITE == icem->lmode) {
+ DEBUG_WARNING("%s: Checklist: only valid for full-mode\n",
+ icem->name);
+ return EINVAL;
+ }
+
+ if (!list_isempty(&icem->checkl))
+ return EALREADY;
+
+ /* 1. form candidate pairs */
+ err = candpairs_form(icem);
+ if (err)
+ return err;
+
+ /* 2. compute a candidate pair priority */
+ /* 3. order the pairs by priority */
+ icem_candpair_prio_order(&icem->checkl);
+
+ /* 4. prune the pairs */
+ candpair_prune(icem);
+
+ return err;
+}
+
+
+/* If all of the pairs in the check list are now either in the Failed or
+ Succeeded state:
+ */
+static bool iscompleted(const struct icem *icem)
+{
+ struct le *le;
+
+ for (le = icem->checkl.head; le; le = le->next) {
+
+ const struct ice_candpair *cp = le->data;
+
+ if (!icem_candpair_iscompleted(cp))
+ return false;
+ }
+
+ return true;
+}
+
+
+/* 8. Concluding ICE Processing */
+static void concluding_ice(struct icem_comp *comp)
+{
+ struct ice_candpair *cp;
+
+ if (!comp || comp->concluded)
+ return;
+
+ /* pick the best candidate pair, highest priority */
+ cp = icem_candpair_find_st(&comp->icem->validl, comp->id,
+ ICE_CANDPAIR_SUCCEEDED);
+ if (!cp) {
+ DEBUG_WARNING("{%s.%u} conclude: no valid candpair found"
+ " (validlist=%u)\n",
+ comp->icem->name, comp->id,
+ list_count(&comp->icem->validl));
+ return;
+ }
+
+ icem_comp_set_selected(comp, cp);
+
+ if (comp->icem->conf.nom == ICE_NOMINATION_REGULAR) {
+
+ /* send STUN request with USE_CAND flag via triggered qeueue */
+ (void)icem_conncheck_send(cp, true, true);
+ icem_conncheck_schedule_check(comp->icem);
+ }
+
+ comp->concluded = true;
+}
+
+
+/**
+ * Check List and Timer State Updates
+ *
+ * @param icem ICE Media object
+ */
+void icem_checklist_update(struct icem *icem)
+{
+ struct le *le;
+ bool compl;
+ int err = 0;
+
+ compl = iscompleted(icem);
+ if (!compl)
+ return;
+
+ /*
+ * If there is not a pair in the valid list for each component of the
+ * media stream, the state of the check list is set to Failed.
+ */
+ for (le = icem->compl.head; le; le = le->next) {
+
+ struct icem_comp *comp = le->data;
+
+ if (!icem_candpair_find_compid(&icem->validl, comp->id)) {
+ DEBUG_WARNING("{%s.%u} no valid candidate pair"
+ " (validlist=%u)\n",
+ icem->name, comp->id,
+ list_count(&icem->validl));
+ err = ENOENT;
+ break;
+ }
+
+ concluding_ice(comp);
+
+ if (!comp->cp_sel)
+ continue;
+
+ icem_comp_keepalive(comp, true);
+ }
+
+ icem->state = err ? ICE_CHECKLIST_FAILED : ICE_CHECKLIST_COMPLETED;
+
+ if (icem->chkh) {
+ icem->chkh(err, icem->lrole == ICE_ROLE_CONTROLLING,
+ icem->arg);
+ }
+}
+
+
+/**
+ * Get the Local address of the Selected Candidate pair, if available
+ *
+ * @param icem ICE Media object
+ * @param compid Component ID
+ *
+ * @return Local address if available, otherwise NULL
+ */
+const struct sa *icem_selected_laddr(const struct icem *icem, unsigned compid)
+{
+ const struct ice_cand *cand = icem_selected_lcand(icem, compid);
+ return icem_lcand_addr(cand);
+}
+
+
+/**
+ * Get the Local candidate of the Selected Candidate pair, if available
+ *
+ * @param icem ICE Media object
+ * @param compid Component ID
+ *
+ * @return Local candidate if available, otherwise NULL
+ */
+const struct ice_cand *icem_selected_lcand(const struct icem *icem,
+ unsigned compid)
+{
+ const struct icem_comp *comp = icem_comp_find(icem, compid);
+ if (!comp || !comp->cp_sel)
+ return NULL;
+
+ return comp->cp_sel->lcand;
+}
+
+
+/**
+ * Get the Remote candidate of the Selected Candidate pair, if available
+ *
+ * @param icem ICE Media object
+ * @param compid Component ID
+ *
+ * @return Remote candidate if available, otherwise NULL
+ */
+const struct ice_cand *icem_selected_rcand(const struct icem *icem,
+ unsigned compid)
+{
+ const struct icem_comp *comp = icem_comp_find(icem, compid);
+ if (!comp || !comp->cp_sel)
+ return NULL;
+
+ return comp->cp_sel->rcand;
+}
diff --git a/src/ice/comp.c b/src/ice/comp.c
new file mode 100644
index 0000000..1e5c69f
--- /dev/null
+++ b/src/ice/comp.c
@@ -0,0 +1,305 @@
+/**
+ * @file comp.c ICE Media component
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sys.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_turn.h>
+#include <re_ice.h>
+#include "ice.h"
+
+
+#define DEBUG_MODULE "icecomp"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+enum {COMPID_MIN = 1, COMPID_MAX = 255};
+
+
+static bool helper_recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct icem_comp *comp = arg;
+ struct icem *icem = comp->icem;
+ struct stun_msg *msg = NULL;
+ struct stun_unknown_attr ua;
+ const size_t start = mb->pos;
+
+#if 0
+ re_printf("{%d} UDP recv_helper: %u bytes from %J\n",
+ comp->id, mbuf_get_left(mb), src);
+#endif
+
+ if (stun_msg_decode(&msg, mb, &ua))
+ return false;
+
+ if (STUN_METHOD_BINDING == stun_msg_method(msg)) {
+
+ switch (stun_msg_class(msg)) {
+
+ case STUN_CLASS_REQUEST:
+ (void)icem_stund_recv(comp, src, msg, start);
+ break;
+
+ default:
+ (void)stun_ctrans_recv(icem->stun, msg, &ua);
+ break;
+ }
+ }
+
+ mem_deref(msg);
+
+ return true; /* handled */
+}
+
+
+static void destructor(void *arg)
+{
+ struct icem_comp *comp = arg;
+
+ tmr_cancel(&comp->tmr_ka);
+ mem_deref(comp->turnc);
+ mem_deref(comp->cp_sel);
+ mem_deref(comp->def_lcand);
+ mem_deref(comp->def_rcand);
+ mem_deref(comp->uh);
+ mem_deref(comp->sock);
+}
+
+
+static struct ice_cand *cand_default(const struct list *lcandl,
+ unsigned compid)
+{
+ struct ice_cand *def = NULL;
+ struct le *le;
+
+ /* NOTE: list must be sorted by priority */
+ for (le = list_head(lcandl); le; le = le->next) {
+
+ struct ice_cand *cand = le->data;
+
+ if (cand->compid != compid)
+ continue;
+
+ switch (cand->type) {
+
+ case ICE_CAND_TYPE_RELAY:
+ return cand;
+
+ case ICE_CAND_TYPE_SRFLX:
+ if (!def || ICE_CAND_TYPE_SRFLX != def->type)
+ def = cand;
+ break;
+
+ case ICE_CAND_TYPE_HOST:
+ if (!def)
+ def = cand;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return def;
+}
+
+
+int icem_comp_alloc(struct icem_comp **cp, struct icem *icem, int id,
+ void *sock)
+{
+ struct icem_comp *comp;
+ struct sa local;
+ int err;
+
+ if (!cp || !icem || id<1 || id>255 || !sock)
+ return EINVAL;
+
+ comp = mem_zalloc(sizeof(*comp), destructor);
+ if (!comp)
+ return ENOMEM;
+
+ comp->id = id;
+ comp->sock = mem_ref(sock);
+ comp->icem = icem;
+
+ err = udp_register_helper(&comp->uh, sock, icem->layer,
+ NULL, helper_recv_handler, comp);
+ if (err)
+ goto out;
+
+ err = udp_local_get(comp->sock, &local);
+ if (err)
+ goto out;
+
+ comp->lport = sa_port(&local);
+
+ out:
+ if (err)
+ mem_deref(comp);
+ else
+ *cp = comp;
+
+ return err;
+}
+
+
+int icem_comp_set_default_cand(struct icem_comp *comp)
+{
+ struct ice_cand *cand;
+
+ if (!comp)
+ return EINVAL;
+
+ cand = cand_default(&comp->icem->lcandl, comp->id);
+ if (!cand)
+ return ENOENT;
+
+ mem_deref(comp->def_lcand);
+ comp->def_lcand = mem_ref(cand);
+
+ return 0;
+}
+
+
+void icem_comp_set_default_rcand(struct icem_comp *comp,
+ struct ice_cand *rcand)
+{
+ if (!comp)
+ return;
+
+ icecomp_printf(comp, "Set default remote candidate: %s:%J\n",
+ ice_cand_type2name(rcand->type), &rcand->addr);
+
+ mem_deref(comp->def_rcand);
+ comp->def_rcand = mem_ref(rcand);
+
+ if (comp->turnc) {
+ icecomp_printf(comp, "Add TURN Channel to peer %J\n",
+ &rcand->addr);
+
+ (void)turnc_add_chan(comp->turnc, &rcand->addr, NULL, NULL);
+ }
+}
+
+
+void icem_comp_set_selected(struct icem_comp *comp, struct ice_candpair *cp)
+{
+ if (!comp || !cp)
+ return;
+
+ if (cp->state != ICE_CANDPAIR_SUCCEEDED) {
+ DEBUG_WARNING("{%s.%u} set_selected: invalid state %s\n",
+ comp->icem->name, comp->id,
+ ice_candpair_state2name(cp->state));
+ }
+
+ mem_deref(comp->cp_sel);
+ comp->cp_sel = mem_ref(cp);
+}
+
+
+struct icem_comp *icem_comp_find(const struct icem *icem, unsigned compid)
+{
+ struct le *le;
+
+ if (!icem)
+ return NULL;
+
+ for (le = icem->compl.head; le; le = le->next) {
+
+ struct icem_comp *comp = le->data;
+
+ if (comp->id == compid)
+ return comp;
+ }
+
+ return NULL;
+}
+
+
+static void timeout(void *arg)
+{
+ struct icem_comp *comp = arg;
+ struct ice_candpair *cp;
+
+ tmr_start(&comp->tmr_ka, ICE_DEFAULT_Tr * 1000 + rand_u16() % 1000,
+ timeout, comp);
+
+ /* find selected candidate-pair */
+ cp = comp->cp_sel;
+ if (!cp)
+ return;
+
+ (void)stun_indication(comp->icem->proto, comp->sock, &cp->rcand->addr,
+ (cp->lcand->type == ICE_CAND_TYPE_RELAY) ? 4 : 0,
+ STUN_METHOD_BINDING, NULL, 0, true, 0);
+}
+
+
+void icem_comp_keepalive(struct icem_comp *comp, bool enable)
+{
+ if (!comp)
+ return;
+
+ if (enable) {
+ tmr_start(&comp->tmr_ka, ICE_DEFAULT_Tr * 1000, timeout, comp);
+ }
+ else {
+ tmr_cancel(&comp->tmr_ka);
+ }
+}
+
+
+void icecomp_printf(struct icem_comp *comp, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!comp || !comp->icem->conf.debug)
+ return;
+
+ va_start(ap, fmt);
+ (void)re_printf("{%11s.%u} %v", comp->icem->name, comp->id, fmt, &ap);
+ va_end(ap);
+}
+
+
+int icecomp_debug(struct re_printf *pf, const struct icem_comp *comp)
+{
+ if (!comp)
+ return 0;
+
+ return re_hprintf(pf, "id=%u ldef=%J rdef=%J concluded=%d",
+ comp->id,
+ comp->def_lcand ? &comp->def_lcand->addr : NULL,
+ comp->def_rcand ? &comp->def_rcand->addr : NULL,
+ comp->concluded);
+}
+
+
+int icem_set_turn_client(struct icem *icem, unsigned compid,
+ struct turnc *turnc)
+{
+ struct icem_comp *comp;
+
+ comp = icem_comp_find(icem, compid);
+ if (!comp)
+ return ENOENT;
+
+ comp->turnc = mem_deref(comp->turnc);
+
+ if (turnc)
+ comp->turnc = mem_ref(turnc);
+
+ return 0;
+}
diff --git a/src/ice/connchk.c b/src/ice/connchk.c
new file mode 100644
index 0000000..d101d5a
--- /dev/null
+++ b/src/ice/connchk.c
@@ -0,0 +1,450 @@
+/**
+ * @file connchk.c ICE Connectivity Checks
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_stun.h>
+#include <re_turn.h>
+#include <re_ice.h>
+#include "ice.h"
+
+
+#define DEBUG_MODULE "connchk"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static void pace_next(struct icem *icem)
+{
+ if (icem->state != ICE_CHECKLIST_RUNNING)
+ return;
+
+ icem_conncheck_schedule_check(icem);
+
+ if (icem->state == ICE_CHECKLIST_FAILED)
+ return;
+
+ icem_checklist_update(icem);
+}
+
+
+/**
+ * Constructing a Valid Pair
+ *
+ * @return The valid pair
+ */
+static struct ice_candpair *construct_valid_pair(struct icem *icem,
+ struct ice_candpair *cp,
+ const struct sa *mapped,
+ const struct sa *dest)
+{
+ struct ice_cand *lcand, *rcand;
+ struct ice_candpair *cp2;
+ int err;
+
+ lcand = icem_cand_find(&icem->lcandl, cp->lcand->compid, mapped);
+ rcand = icem_cand_find(&icem->rcandl, cp->rcand->compid, dest);
+
+ if (!lcand) {
+ DEBUG_WARNING("no such local candidate: %J\n", mapped);
+ return NULL;
+ }
+ if (!rcand) {
+ DEBUG_WARNING("no such remote candidate: %J\n", dest);
+ return NULL;
+ }
+
+ /* New candidate? -- implicit success */
+ if (lcand != cp->lcand || rcand != cp->rcand) {
+
+ if (lcand != cp->lcand) {
+ icecomp_printf(cp->comp,
+ "New local candidate for mapped %J\n",
+ mapped);
+ }
+ if (rcand != cp->rcand) {
+ icecomp_printf(cp->comp,
+ "New remote candidate for dest %J\n",
+ dest);
+ }
+
+ /* The original candidate pair is set to 'Failed' because
+ * the implicitly discovered pair is 'better'.
+ * This happens for UAs behind NAT where the original
+ * pair is of type 'host' and the implicit pair is 'srflx'
+ */
+
+ icem_candpair_make_valid(cp);
+
+ cp2 = icem_candpair_find(&icem->validl, lcand, rcand);
+ if (cp2)
+ return cp2;
+
+ err = icem_candpair_clone(&cp2, cp, lcand, rcand);
+ if (err)
+ return NULL;
+
+ icem_candpair_make_valid(cp2);
+ /*icem_candpair_failed(cp, EINTR, 0);*/
+
+ return cp2;
+ }
+ else {
+ /* Add to VALID LIST, the pair that generated the check */
+ icem_candpair_make_valid(cp);
+
+ return cp;
+ }
+}
+
+
+static void handle_success(struct icem *icem, struct ice_candpair *cp,
+ const struct sa *laddr)
+{
+ if (!icem_cand_find(&icem->lcandl, cp->lcand->compid, laddr)) {
+
+ int err;
+
+ icecomp_printf(cp->comp, "adding local PRFLX Candidate: %J\n",
+ laddr);
+
+ err = icem_lcand_add(icem, cp->lcand,
+ ICE_CAND_TYPE_PRFLX, laddr);
+ if (err) {
+ DEBUG_WARNING("failed to add PRFLX: %m\n", err);
+ }
+ }
+
+ cp = construct_valid_pair(icem, cp, laddr, &cp->rcand->addr);
+ if (!cp) {
+ DEBUG_WARNING("{%s} no valid candidate pair for %J\n",
+ icem->name, laddr);
+ return;
+ }
+
+ icem_candpair_make_valid(cp);
+ icem_comp_set_selected(cp->comp, cp);
+
+ cp->nominated = true;
+
+#if 0
+ /* stop conncheck now -- conclude */
+ icem_conncheck_stop(icem, 0);
+#endif
+}
+
+
+#if ICE_TRACE
+static int print_err(struct re_printf *pf, const int *err)
+{
+ if (err && *err)
+ return re_hprintf(pf, " (%m)", *err);
+
+ return 0;
+}
+#endif
+
+
+static void stunc_resp_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct ice_candpair *cp = arg;
+ struct icem *icem = cp->icem;
+ struct stun_attr *attr;
+
+ (void)reason;
+
+#if ICE_TRACE
+ icecomp_printf(cp->comp, "Rx %H <--- %H '%u %s'%H\n",
+ icem_cand_print, cp->lcand,
+ icem_cand_print, cp->rcand,
+ scode, reason, print_err, &err);
+#endif
+
+ if (err) {
+ icem_candpair_failed(cp, err, scode);
+ goto out;
+ }
+
+ switch (scode) {
+
+ case 0: /* Success case */
+ attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ if (!attr) {
+ DEBUG_WARNING("no XOR-MAPPED-ADDR in response\n");
+ icem_candpair_failed(cp, EBADMSG, 0);
+ break;
+ }
+
+ handle_success(icem, cp, &attr->v.sa);
+ break;
+
+ case 487: /* Role Conflict */
+ ice_switch_local_role(icem);
+ (void)icem_conncheck_send(cp, false, true);
+ break;
+
+ default:
+ DEBUG_WARNING("{%s.%u} STUN Response: %u %s\n",
+ icem->name, cp->comp->id, scode, reason);
+ icem_candpair_failed(cp, err, scode);
+ break;
+ }
+
+ out:
+ pace_next(icem);
+}
+
+
+int icem_conncheck_send(struct ice_candpair *cp, bool use_cand, bool trigged)
+{
+ struct ice_cand *lcand = cp->lcand;
+ struct icem *icem = cp->icem;
+ char username_buf[64];
+ size_t presz = 0;
+ uint32_t prio_prflx;
+ uint16_t ctrl_attr;
+ int err = 0;
+
+ icem_candpair_set_state(cp, ICE_CANDPAIR_INPROGRESS);
+
+ (void)re_snprintf(username_buf, sizeof(username_buf),
+ "%s:%s", icem->rufrag, icem->lufrag);
+
+ /* PRIORITY and USE-CANDIDATE */
+ prio_prflx = ice_cand_calc_prio(ICE_CAND_TYPE_PRFLX, 0, lcand->compid);
+
+ switch (icem->lrole) {
+
+ case ICE_ROLE_CONTROLLING:
+ ctrl_attr = STUN_ATTR_CONTROLLING;
+
+ if (icem->conf.nom == ICE_NOMINATION_AGGRESSIVE)
+ use_cand = true;
+ break;
+
+ case ICE_ROLE_CONTROLLED:
+ ctrl_attr = STUN_ATTR_CONTROLLED;
+ break;
+
+ default:
+ return EINVAL;
+ }
+
+#if ICE_TRACE
+ icecomp_printf(cp->comp, "Tx %H ---> %H (%s) %s %s\n",
+ icem_cand_print, cp->lcand, icem_cand_print, cp->rcand,
+ ice_candpair_state2name(cp->state),
+ use_cand ? "[USE]" : "",
+ trigged ? "[Trigged]" : "");
+#else
+ (void)trigged;
+#endif
+
+ /* A connectivity check MUST utilize the STUN short term credential
+ mechanism. */
+
+ /* The password is equal to the password provided by the peer */
+ if (!icem->rpwd) {
+ DEBUG_WARNING("no remote password!\n");
+ }
+
+ if (cp->ct_conn) {
+ DEBUG_WARNING("send_req: CONNCHECK already Pending!\n");
+ return EBUSY;
+ }
+
+ switch (lcand->type) {
+
+ case ICE_CAND_TYPE_RELAY:
+ /* Creating Permissions for Relayed Candidates */
+ err = turnc_add_chan(cp->comp->turnc, &cp->rcand->addr,
+ NULL, NULL);
+ if (err) {
+ DEBUG_WARNING("add channel: %m\n", err);
+ break;
+ }
+ presz = 4;
+ /*@fallthrough@*/
+
+ case ICE_CAND_TYPE_HOST:
+ case ICE_CAND_TYPE_SRFLX:
+ case ICE_CAND_TYPE_PRFLX:
+ cp->ct_conn = mem_deref(cp->ct_conn);
+ err = stun_request(&cp->ct_conn, icem->stun, icem->proto,
+ cp->comp->sock, &cp->rcand->addr, presz,
+ STUN_METHOD_BINDING,
+ (uint8_t *)icem->rpwd, str_len(icem->rpwd),
+ true, stunc_resp_handler, cp,
+ 4,
+ STUN_ATTR_USERNAME, username_buf,
+ STUN_ATTR_PRIORITY, &prio_prflx,
+ ctrl_attr, &icem->tiebrk,
+ STUN_ATTR_USE_CAND,
+ use_cand ? &use_cand : 0);
+ break;
+
+ default:
+ DEBUG_WARNING("unknown candidate type %d\n", lcand->type);
+ err = EINVAL;
+ break;
+ }
+
+ return err;
+}
+
+
+static void abort_ice(struct icem *icem, int err)
+{
+ icem->state = ICE_CHECKLIST_FAILED;
+ tmr_cancel(&icem->tmr_pace);
+
+ if (icem->chkh) {
+ icem->chkh(err, icem->lrole == ICE_ROLE_CONTROLLING,
+ icem->arg);
+ }
+
+ icem->chkh = NULL;
+}
+
+
+static void do_check(struct ice_candpair *cp)
+{
+ int err;
+
+ err = icem_conncheck_send(cp, false, false);
+ if (err) {
+ icem_candpair_failed(cp, err, 0);
+
+ if (err == ENOMEM) {
+ abort_ice(cp->icem, err);
+ }
+ else {
+ pace_next(cp->icem);
+ }
+ }
+}
+
+
+/**
+ * Scheduling Checks
+ *
+ * @param icem ICE Media object
+ */
+void icem_conncheck_schedule_check(struct icem *icem)
+{
+ struct ice_candpair *cp;
+
+ /* Find the highest priority pair in that check list that is in the
+ Waiting state. */
+ cp = icem_candpair_find_st(&icem->checkl, 0, ICE_CANDPAIR_WAITING);
+ if (cp) {
+ do_check(cp);
+ return;
+ }
+
+ /* If there is no such pair: */
+
+ /* Find the highest priority pair in that check list that is in
+ the Frozen state. */
+ cp = icem_candpair_find_st(&icem->checkl, 0, ICE_CANDPAIR_FROZEN);
+ if (cp) { /* If there is such a pair: */
+
+ /* Unfreeze the pair.
+ Perform a check for that pair, causing its state to
+ transition to In-Progress. */
+ do_check(cp);
+ return;
+ }
+
+ /* If there is no such pair: */
+
+ /* Terminate the timer for that check list. */
+
+#if 0
+ icem->state = ICE_CHECKLIST_COMPLETED;
+#endif
+}
+
+
+static void pace_timeout(void *arg)
+{
+ struct icem *icem = arg;
+
+ pace_next(icem);
+}
+
+
+/**
+ * Scheduling Checks
+ *
+ * @param icem ICE Media object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int icem_conncheck_start(struct icem *icem)
+{
+ int err;
+
+ if (!icem)
+ return EINVAL;
+
+ if (ICE_MODE_FULL != icem->lmode)
+ return EINVAL;
+
+ err = icem_checklist_form(icem);
+ if (err)
+ return err;
+
+ icem->state = ICE_CHECKLIST_RUNNING;
+
+ icem_printf(icem, "starting connectivity checks"
+ " with %u candidate pairs\n",
+ list_count(&icem->checkl));
+
+ /* add some delay, to wait for call to be 'established' */
+ tmr_start(&icem->tmr_pace, 10, pace_timeout, icem);
+
+ return 0;
+}
+
+
+void icem_conncheck_continue(struct icem *icem)
+{
+ if (!tmr_isrunning(&icem->tmr_pace))
+ tmr_start(&icem->tmr_pace, 1, pace_timeout, icem);
+}
+
+
+/**
+ * Stop checklist, cancel all connectivity checks
+ *
+ * @param icem ICE Media object
+ * @param err Error code
+ */
+void icem_conncheck_stop(struct icem *icem, int err)
+{
+ struct le *le;
+
+ icem->state = err ? ICE_CHECKLIST_FAILED : ICE_CHECKLIST_COMPLETED;
+
+ tmr_cancel(&icem->tmr_pace);
+
+ for (le = icem->checkl.head; le; le = le->next) {
+ struct ice_candpair *cp = le->data;
+
+ if (!icem_candpair_iscompleted(cp)) {
+ icem_candpair_cancel(cp);
+ icem_candpair_failed(cp, EINTR, 0);
+ }
+ }
+
+ icem_checklist_update(icem);
+}
diff --git a/src/ice/ice.c b/src/ice/ice.c
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/ice/ice.c
@@ -0,0 +1 @@
+
diff --git a/src/ice/ice.h b/src/ice/ice.h
new file mode 100644
index 0000000..b61d362
--- /dev/null
+++ b/src/ice/ice.h
@@ -0,0 +1,205 @@
+/**
+ * @file ice.h Internal Interface to ICE
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifndef RELEASE
+#define ICE_TRACE 1 /**< Trace connectivity checks */
+#endif
+
+
+enum ice_checkl_state {
+ ICE_CHECKLIST_NULL = -1,
+ ICE_CHECKLIST_RUNNING,
+ ICE_CHECKLIST_COMPLETED,
+ ICE_CHECKLIST_FAILED
+};
+
+enum ice_transp {
+ ICE_TRANSP_NONE = -1,
+ ICE_TRANSP_UDP = IPPROTO_UDP
+};
+
+/** ICE protocol values */
+enum {
+ ICE_DEFAULT_Tr = 15, /**< Keepalive interval [s] */
+ ICE_DEFAULT_Ta_RTP = 20, /**< Pacing interval RTP [ms] */
+ ICE_DEFAULT_Ta_NON_RTP = 500, /**< Pacing interval [ms] */
+ ICE_DEFAULT_RTO_RTP = 100, /**< Retransmission TimeOut RTP [ms] */
+ ICE_DEFAULT_RTO_NONRTP = 500, /**< Retransmission TimeOut [ms] */
+ ICE_DEFAULT_RC = 7 /**< Retransmission count */
+};
+
+
+/** Defines a media-stream component */
+struct icem_comp {
+ struct le le; /**< Linked-list element */
+ struct icem *icem; /**< Parent ICE media */
+ struct ice_cand *def_lcand; /**< Default local candidate */
+ struct ice_cand *def_rcand; /**< Default remote candidate */
+ struct ice_candpair *cp_sel; /**< Selected candidate-pair */
+ struct udp_helper *uh; /**< UDP helper */
+ void *sock; /**< Transport socket */
+ uint16_t lport; /**< Local port number */
+ unsigned id; /**< Component ID */
+ bool concluded; /**< Concluded flag */
+ struct turnc *turnc; /**< TURN Client */
+ struct tmr tmr_ka; /**< Keep-alive timer */
+};
+
+/** Defines an ICE media-stream */
+struct icem {
+ struct ice_conf conf; /**< ICE Configuration */
+ struct stun *stun; /**< STUN Transport */
+ struct sa stun_srv; /**< STUN Server IP address and port */
+ struct list lcandl; /**< List of local candidates */
+ struct list rcandl; /**< List of remote candidates */
+ struct list checkl; /**< Check List of cand pairs (sorted) */
+ struct list validl; /**< Valid List of cand pairs (sorted) */
+ uint64_t tiebrk; /**< Tie-break value for roleconflict */
+ bool mismatch; /**< ICE mismatch flag */
+ enum ice_mode lmode; /**< Local mode */
+ enum ice_mode rmode; /**< Remote mode */
+ enum ice_role lrole; /**< Local role */
+ struct tmr tmr_pace; /**< Timer for pacing STUN requests */
+ int proto; /**< Transport protocol */
+ int layer; /**< Protocol layer */
+ enum ice_checkl_state state; /**< State of the checklist */
+ struct list compl; /**< ICE media components */
+ char *lufrag; /**< Local Username fragment */
+ char *lpwd; /**< Local Password */
+ char *rufrag; /**< Remote Username fragment */
+ char *rpwd; /**< Remote Password */
+ ice_connchk_h *chkh; /**< Connectivity check handler */
+ void *arg; /**< Handler argument */
+ char name[32]; /**< Name of the media stream */
+};
+
+/** Defines a candidate */
+struct ice_cand {
+ struct le le; /**< List element */
+ enum ice_cand_type type; /**< Candidate type */
+ uint32_t prio; /**< Priority of this candidate */
+ char *foundation; /**< Foundation */
+ unsigned compid; /**< Component ID (1-256) */
+ struct sa rel; /**< Related IP address and port number */
+ struct sa addr; /**< Transport address */
+ enum ice_transp transp; /**< Transport protocol */
+
+ /* extra for local */
+ struct ice_cand *base; /**< Links to base candidate, if any */
+ char *ifname; /**< Network interface, for diagnostics */
+};
+
+/** Defines a candidate pair */
+struct ice_candpair {
+ struct le le; /**< List element */
+ struct icem *icem; /**< Pointer to parent ICE media */
+ struct icem_comp *comp; /**< Pointer to media-stream component */
+ struct ice_cand *lcand; /**< Local candidate */
+ struct ice_cand *rcand; /**< Remote candidate */
+ bool def; /**< Default flag */
+ bool valid; /**< Valid flag */
+ bool nominated; /**< Nominated flag */
+ enum ice_candpair_state state;/**< Candidate pair state */
+ uint64_t pprio; /**< Pair priority */
+ struct stun_ctrans *ct_conn; /**< STUN Transaction for conncheck */
+ int err; /**< Saved error code, if failed */
+ uint16_t scode; /**< Saved STUN code, if failed */
+};
+
+
+/* cand */
+int icem_lcand_add_base(struct icem *icem, unsigned compid, uint16_t lprio,
+ const char *ifname, enum ice_transp transp,
+ const struct sa *addr);
+int icem_rcand_add(struct icem *icem, enum ice_cand_type type, unsigned compid,
+ uint32_t prio, const struct sa *addr,
+ const struct sa *rel_addr, const struct pl *foundation);
+int icem_rcand_add_prflx(struct ice_cand **rcp, struct icem *icem,
+ unsigned compid, uint32_t prio,
+ const struct sa *addr);
+struct ice_cand *icem_lcand_find_checklist(const struct icem *icem,
+ unsigned compid);
+int icem_cands_debug(struct re_printf *pf, const struct list *lst);
+int icem_cand_print(struct re_printf *pf, const struct ice_cand *cand);
+
+
+/* candpair */
+int icem_candpair_alloc(struct ice_candpair **cpp, struct icem *icem,
+ struct ice_cand *lcand, struct ice_cand *rcand);
+int icem_candpair_clone(struct ice_candpair **cpp, struct ice_candpair *cp0,
+ struct ice_cand *lcand, struct ice_cand *rcand);
+void icem_candpair_prio_order(struct list *lst);
+void icem_candpair_cancel(struct ice_candpair *cp);
+void icem_candpair_make_valid(struct ice_candpair *cp);
+void icem_candpair_failed(struct ice_candpair *cp, int err, uint16_t scode);
+void icem_candpair_set_state(struct ice_candpair *cp,
+ enum ice_candpair_state state);
+void icem_candpairs_flush(struct list *lst, enum ice_cand_type type,
+ unsigned compid);
+bool icem_candpair_iscompleted(const struct ice_candpair *cp);
+bool icem_candpair_cmp(const struct ice_candpair *cp1,
+ const struct ice_candpair *cp2);
+bool icem_candpair_cmp_fnd(const struct ice_candpair *cp1,
+ const struct ice_candpair *cp2);
+struct ice_candpair *icem_candpair_find(const struct list *lst,
+ const struct ice_cand *lcand,
+ const struct ice_cand *rcand);
+struct ice_candpair *icem_candpair_find_st(const struct list *lst,
+ unsigned compid,
+ enum ice_candpair_state state);
+struct ice_candpair *icem_candpair_find_compid(const struct list *lst,
+ unsigned compid);
+struct ice_candpair *icem_candpair_find_rcand(struct icem *icem,
+ const struct ice_cand *rcand);
+int icem_candpair_debug(struct re_printf *pf, const struct ice_candpair *cp);
+int icem_candpairs_debug(struct re_printf *pf, const struct list *list);
+
+
+/* stun server */
+int icem_stund_recv(struct icem_comp *comp, const struct sa *src,
+ struct stun_msg *req, size_t presz);
+
+
+/* ICE media */
+void icem_printf(struct icem *icem, const char *fmt, ...);
+
+
+/* Checklist */
+int icem_checklist_form(struct icem *icem);
+void icem_checklist_update(struct icem *icem);
+
+
+/* component */
+int icem_comp_alloc(struct icem_comp **cp, struct icem *icem, int id,
+ void *sock);
+int icem_comp_set_default_cand(struct icem_comp *comp);
+void icem_comp_set_default_rcand(struct icem_comp *comp,
+ struct ice_cand *rcand);
+void icem_comp_set_selected(struct icem_comp *comp, struct ice_candpair *cp);
+struct icem_comp *icem_comp_find(const struct icem *icem, unsigned compid);
+void icem_comp_keepalive(struct icem_comp *comp, bool enable);
+void icecomp_printf(struct icem_comp *comp, const char *fmt, ...);
+int icecomp_debug(struct re_printf *pf, const struct icem_comp *comp);
+
+
+/* conncheck */
+void icem_conncheck_schedule_check(struct icem *icem);
+void icem_conncheck_continue(struct icem *icem);
+int icem_conncheck_send(struct ice_candpair *cp, bool use_cand, bool trigged);
+
+
+/* icestr */
+const char *ice_mode2name(enum ice_mode mode);
+const char *ice_checkl_state2name(enum ice_checkl_state cst);
+
+
+/* util */
+typedef void * (list_unique_h)(struct le *le1, struct le *le2);
+
+uint64_t ice_calc_pair_prio(uint32_t g, uint32_t d);
+void ice_switch_local_role(struct icem *icem);
+uint32_t ice_list_unique(struct list *list, list_unique_h *uh);
diff --git a/src/ice/icem.c b/src/ice/icem.c
new file mode 100644
index 0000000..940c858
--- /dev/null
+++ b/src/ice/icem.c
@@ -0,0 +1,604 @@
+/**
+ * @file icem.c ICE Media stream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_stun.h>
+#include <re_turn.h>
+#include <re_ice.h>
+#include "ice.h"
+
+
+#define DEBUG_MODULE "icem"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/*
+ * ICE Implementation as of RFC 5245
+ */
+
+
+static const struct ice_conf conf_default = {
+ ICE_NOMINATION_REGULAR,
+ ICE_DEFAULT_RTO_RTP,
+ ICE_DEFAULT_RC,
+ false
+};
+
+
+/** Determining Role */
+static void ice_determine_role(struct icem *icem, enum ice_role role)
+{
+ if (!icem)
+ return;
+
+ if (icem->lmode == icem->rmode)
+ icem->lrole = role;
+ else if (icem->lmode == ICE_MODE_FULL)
+ icem->lrole = ICE_ROLE_CONTROLLING;
+ else
+ icem->lrole = ICE_ROLE_CONTROLLED;
+}
+
+
+static void icem_destructor(void *data)
+{
+ struct icem *icem = data;
+
+ tmr_cancel(&icem->tmr_pace);
+ list_flush(&icem->compl);
+ list_flush(&icem->validl);
+ list_flush(&icem->checkl);
+ list_flush(&icem->lcandl);
+ list_flush(&icem->rcandl);
+ mem_deref(icem->lufrag);
+ mem_deref(icem->lpwd);
+ mem_deref(icem->rufrag);
+ mem_deref(icem->rpwd);
+ mem_deref(icem->stun);
+}
+
+
+/**
+ * Add a new ICE Media object to the ICE Session
+ *
+ * @param icemp Pointer to allocated ICE Media object
+ * @param mode ICE mode
+ * @param role Local ICE role
+ * @param proto Transport protocol
+ * @param layer Protocol stack layer
+ * @param tiebrk Tie-breaker value, must be same for all media streams
+ * @param lufrag Local username fragment
+ * @param lpwd Local password
+ * @param chkh Connectivity check handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int icem_alloc(struct icem **icemp,
+ enum ice_mode mode, enum ice_role role,
+ int proto, int layer,
+ uint64_t tiebrk, const char *lufrag, const char *lpwd,
+ ice_connchk_h *chkh, void *arg)
+{
+ struct icem *icem;
+ int err = 0;
+
+ if (!icemp || !tiebrk || !lufrag || !lpwd)
+ return EINVAL;
+
+ if (str_len(lufrag) < 4 || str_len(lpwd) < 22) {
+ DEBUG_WARNING("alloc: lufrag/lpwd is too short\n");
+ return EINVAL;
+ }
+
+ if (proto != IPPROTO_UDP)
+ return EPROTONOSUPPORT;
+
+ icem = mem_zalloc(sizeof(*icem), icem_destructor);
+ if (!icem)
+ return ENOMEM;
+
+ icem->conf = conf_default;
+
+ tmr_init(&icem->tmr_pace);
+ list_init(&icem->lcandl);
+ list_init(&icem->rcandl);
+ list_init(&icem->checkl);
+ list_init(&icem->validl);
+
+ icem->layer = layer;
+ icem->proto = proto;
+ icem->state = ICE_CHECKLIST_NULL;
+ icem->chkh = chkh;
+ icem->arg = arg;
+
+ if (err)
+ goto out;
+
+ icem->lmode = mode;
+ icem->tiebrk = tiebrk;
+
+ err |= str_dup(&icem->lufrag, lufrag);
+ err |= str_dup(&icem->lpwd, lpwd);
+ if (err)
+ goto out;
+
+ ice_determine_role(icem, role);
+
+ if (ICE_MODE_FULL == icem->lmode) {
+
+ err = stun_alloc(&icem->stun, NULL, NULL, NULL);
+ if (err)
+ goto out;
+
+ /* Update STUN Transport */
+ stun_conf(icem->stun)->rto = icem->conf.rto;
+ stun_conf(icem->stun)->rc = icem->conf.rc;
+ }
+
+ out:
+ if (err)
+ mem_deref(icem);
+ else if (icemp)
+ *icemp = icem;
+
+ return err;
+}
+
+
+/**
+ * Get the ICE Configuration
+ *
+ * @param icem ICE Media object
+ *
+ * @return ICE Configuration
+ */
+struct ice_conf *icem_conf(struct icem *icem)
+{
+ return icem ? &icem->conf : NULL;
+}
+
+
+enum ice_role icem_local_role(const struct icem *icem)
+{
+ return icem ? icem->lrole : ICE_ROLE_UNKNOWN;
+}
+
+
+void icem_set_conf(struct icem *icem, const struct ice_conf *conf)
+{
+ if (!icem || !conf)
+ return;
+
+ icem->conf = *conf;
+
+ if (icem->stun) {
+
+ /* Update STUN Transport */
+ stun_conf(icem->stun)->rto = icem->conf.rto;
+ stun_conf(icem->stun)->rc = icem->conf.rc;
+ }
+}
+
+
+/**
+ * Set the local role on the ICE Session
+ *
+ * @param icem ICE Media object
+ * @param role Local ICE role
+ */
+void icem_set_role(struct icem *icem, enum ice_role role)
+{
+ if (!icem)
+ return;
+
+ ice_determine_role(icem, role);
+}
+
+
+/**
+ * Set the name of the ICE Media object, used for debugging
+ *
+ * @param icem ICE Media object
+ * @param name Media name
+ */
+void icem_set_name(struct icem *icem, const char *name)
+{
+ if (!icem)
+ return;
+
+ str_ncpy(icem->name, name, sizeof(icem->name));
+}
+
+
+/**
+ * Add a new component to the ICE Media object
+ *
+ * @param icem ICE Media object
+ * @param compid Component ID
+ * @param sock Application protocol socket
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int icem_comp_add(struct icem *icem, unsigned compid, void *sock)
+{
+ struct icem_comp *comp;
+ int err;
+
+ if (!icem)
+ return EINVAL;
+
+ if (icem_comp_find(icem, compid))
+ return EALREADY;
+
+ err = icem_comp_alloc(&comp, icem, compid, sock);
+ if (err)
+ return err;
+
+ list_append(&icem->compl, &comp->le, comp);
+
+ return 0;
+}
+
+
+/**
+ * Add a new candidate to the ICE Media object
+ *
+ * @param icem ICE Media object
+ * @param compid Component ID
+ * @param lprio Local priority
+ * @param ifname Name of the network interface
+ * @param addr Local network address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int icem_cand_add(struct icem *icem, unsigned compid, uint16_t lprio,
+ const char *ifname, const struct sa *addr)
+{
+ if (!icem_comp_find(icem, compid))
+ return ENOENT;
+
+ return icem_lcand_add_base(icem, compid, lprio, ifname,
+ ICE_TRANSP_UDP, addr);
+}
+
+
+static void *unique_handler(struct le *le1, struct le *le2)
+{
+ struct ice_cand *c1 = le1->data, *c2 = le2->data;
+
+ if (c1->base != c2->base || !sa_cmp(&c1->addr, &c2->addr, SA_ALL))
+ return NULL;
+
+ /* remove candidate with lower priority */
+ return c1->prio < c2->prio ? c1 : c2;
+}
+
+
+/**
+ * Eliminating Redundant Candidates
+ *
+ * @param icem ICE Media object
+ */
+void icem_cand_redund_elim(struct icem *icem)
+{
+ uint32_t n;
+
+ n = ice_list_unique(&icem->lcandl, unique_handler);
+ if (n > 0) {
+ icem_printf(icem, "redundant candidates eliminated: %u\n", n);
+ }
+}
+
+
+/**
+ * Get the Default Local Candidate
+ *
+ * @param icem ICE Media object
+ * @param compid Component ID
+ *
+ * @return Default Local Candidate address if set, otherwise NULL
+ */
+const struct sa *icem_cand_default(struct icem *icem, unsigned compid)
+{
+ const struct icem_comp *comp = icem_comp_find(icem, compid);
+
+ if (!comp || !comp->def_lcand)
+ return NULL;
+
+ return &comp->def_lcand->addr;
+}
+
+
+/**
+ * Verifying ICE Support and set default remote candidate
+ *
+ * @param icem ICE Media
+ * @param compid Component ID
+ * @param raddr Address of default remote candidate
+ *
+ * @return True if ICE is supported, otherwise false
+ */
+bool icem_verify_support(struct icem *icem, unsigned compid,
+ const struct sa *raddr)
+{
+ struct ice_cand *rcand;
+ bool match;
+
+ if (!icem)
+ return false;
+
+ rcand = icem_cand_find(&icem->rcandl, compid, raddr);
+ match = rcand != NULL;
+
+ if (!match)
+ icem->mismatch = true;
+
+ if (rcand) {
+ icem_comp_set_default_rcand(icem_comp_find(icem, compid),
+ rcand);
+ }
+
+ return match;
+}
+
+
+/**
+ * Add a TURN Channel for the selected remote address
+ *
+ * @param icem ICE Media object
+ * @param compid Component ID
+ * @param raddr Remote network address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int icem_add_chan(struct icem *icem, unsigned compid, const struct sa *raddr)
+{
+ struct icem_comp *comp;
+
+ if (!icem)
+ return EINVAL;
+
+ comp = icem_comp_find(icem, compid);
+ if (!comp)
+ return ENOENT;
+
+ if (comp->turnc) {
+ DEBUG_NOTICE("{%s.%u} Add TURN Channel to peer %J\n",
+ comp->icem->name, comp->id, raddr);
+
+ return turnc_add_chan(comp->turnc, raddr, NULL, NULL);
+ }
+
+ return 0;
+}
+
+
+static void purge_relayed(struct icem *icem, struct icem_comp *comp)
+{
+ if (comp->turnc) {
+ DEBUG_NOTICE("{%s.%u} purge local RELAY candidates\n",
+ icem->name, comp->id);
+ }
+
+ /*
+ * Purge all Candidate-Pairs where the Local candidate
+ * is of type "Relay"
+ */
+ icem_candpairs_flush(&icem->checkl, ICE_CAND_TYPE_RELAY, comp->id);
+ icem_candpairs_flush(&icem->validl, ICE_CAND_TYPE_RELAY, comp->id);
+
+ comp->turnc = mem_deref(comp->turnc);
+}
+
+
+/**
+ * Update the ICE Media object
+ *
+ * @param icem ICE Media object
+ */
+void icem_update(struct icem *icem)
+{
+ struct le *le;
+
+ if (!icem)
+ return;
+
+ for (le = icem->compl.head; le; le = le->next) {
+
+ struct icem_comp *comp = le->data;
+
+ /* remove TURN client if not used by local "Selected" */
+ if (comp->cp_sel) {
+
+ if (comp->cp_sel->lcand->type != ICE_CAND_TYPE_RELAY)
+ purge_relayed(icem, comp);
+ }
+ }
+}
+
+
+/**
+ * Get the ICE Mismatch flag of the ICE Media object
+ *
+ * @param icem ICE Media object
+ *
+ * @return True if ICE mismatch, otherwise false
+ */
+bool icem_mismatch(const struct icem *icem)
+{
+ return icem ? icem->mismatch : true;
+}
+
+
+/**
+ * Print debug information for the ICE Media
+ *
+ * @param pf Print function for debug output
+ * @param icem ICE Media object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int icem_debug(struct re_printf *pf, const struct icem *icem)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!icem)
+ return 0;
+
+ err |= re_hprintf(pf, "----- ICE Media <%s> -----\n", icem->name);
+
+ err |= re_hprintf(pf, " local_mode=%s, remote_mode=%s",
+ ice_mode2name(icem->lmode),
+ ice_mode2name(icem->rmode));
+ err |= re_hprintf(pf, ", local_role=%s\n", ice_role2name(icem->lrole));
+ err |= re_hprintf(pf, " local_ufrag=\"%s\" local_pwd=\"%s\"\n",
+ icem->lufrag, icem->lpwd);
+
+ err |= re_hprintf(pf, " Components: (%u)\n", list_count(&icem->compl));
+ for (le = icem->compl.head; le; le = le->next) {
+ struct icem_comp *comp = le->data;
+
+ err |= re_hprintf(pf, " %H\n", icecomp_debug, comp);
+ }
+
+ err |= re_hprintf(pf, " Local Candidates: %H",
+ icem_cands_debug, &icem->lcandl);
+ err |= re_hprintf(pf, " Remote Candidates: %H",
+ icem_cands_debug, &icem->rcandl);
+ err |= re_hprintf(pf, " Check list: [state=%s]%H",
+ ice_checkl_state2name(icem->state),
+ icem_candpairs_debug, &icem->checkl);
+ err |= re_hprintf(pf, " Valid list: %H",
+ icem_candpairs_debug, &icem->validl);
+
+ err |= stun_debug(pf, icem->stun);
+
+ return err;
+}
+
+
+/**
+ * Get the list of Local Candidates (struct cand)
+ *
+ * @param icem ICE Media object
+ *
+ * @return List of Local Candidates
+ */
+struct list *icem_lcandl(const struct icem *icem)
+{
+ return icem ? (struct list *)&icem->lcandl : NULL;
+}
+
+
+/**
+ * Get the list of Remote Candidates (struct cand)
+ *
+ * @param icem ICE Media object
+ *
+ * @return List of Remote Candidates
+ */
+struct list *icem_rcandl(const struct icem *icem)
+{
+ return icem ? (struct list *)&icem->rcandl : NULL;
+}
+
+
+/**
+ * Get the checklist of Candidate Pairs
+ *
+ * @param icem ICE Media object
+ *
+ * @return Checklist (struct ice_candpair)
+ */
+struct list *icem_checkl(const struct icem *icem)
+{
+ return icem ? (struct list *)&icem->checkl : NULL;
+}
+
+
+/**
+ * Get the list of valid Candidate Pairs
+ *
+ * @param icem ICE Media object
+ *
+ * @return Validlist (struct ice_candpair)
+ */
+struct list *icem_validl(const struct icem *icem)
+{
+ return icem ? (struct list *)&icem->validl : NULL;
+}
+
+
+/**
+ * Set the default local candidates, for ICE-lite mode only
+ *
+ * @param icem ICE Media object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int icem_lite_set_default_candidates(struct icem *icem)
+{
+ struct le *le;
+ int err = 0;
+
+ if (icem->lmode != ICE_MODE_LITE)
+ return EINVAL;
+
+ for (le = icem->compl.head; le; le = le->next) {
+
+ struct icem_comp *comp = le->data;
+
+ err |= icem_comp_set_default_cand(comp);
+ }
+
+ return err;
+}
+
+
+int icem_comps_set_default_cand(struct icem *icem)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!icem)
+ return EINVAL;
+
+ for (le = icem->compl.head; le; le = le->next) {
+
+ struct icem_comp *comp = le->data;
+
+ err |= icem_comp_set_default_cand(comp);
+ }
+
+ return err;
+}
+
+
+struct stun *icem_stun(struct icem *icem)
+{
+ return icem ? icem->stun : NULL;
+}
+
+
+void icem_printf(struct icem *icem, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!icem || !icem->conf.debug)
+ return;
+
+ va_start(ap, fmt);
+ (void)re_printf("{%11s. } %v", icem->name, fmt, &ap);
+ va_end(ap);
+}
diff --git a/src/ice/icesdp.c b/src/ice/icesdp.c
new file mode 100644
index 0000000..f05f935
--- /dev/null
+++ b/src/ice/icesdp.c
@@ -0,0 +1,428 @@
+/**
+ * @file icesdp.c SDP Attributes for ICE
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_net.h>
+#include <re_stun.h>
+#include <re_ice.h>
+#include "ice.h"
+
+
+#define DEBUG_MODULE "icesdp"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+const char ice_attr_cand[] = "candidate";
+const char ice_attr_remote_cand[] = "remote-candidates";
+const char ice_attr_lite[] = "ice-lite";
+const char ice_attr_ufrag[] = "ice-ufrag";
+const char ice_attr_pwd[] = "ice-pwd";
+const char ice_attr_mismatch[] = "ice-mismatch";
+
+
+static const char rel_addr_str[] = "raddr";
+static const char rel_port_str[] = "rport";
+
+
+/* Encode SDP Attributes */
+
+
+static const char *transp_name(enum ice_transp transp)
+{
+ switch (transp) {
+
+ case ICE_TRANSP_UDP: return "UDP";
+ default: return "???";
+ }
+}
+
+
+static enum ice_transp transp_resolve(const struct pl *transp)
+{
+ if (!pl_strcasecmp(transp, "UDP"))
+ return ICE_TRANSP_UDP;
+
+ return ICE_TRANSP_NONE;
+}
+
+
+/**
+ * Encode SDP candidate attribute
+ *
+ * @param pf Print function
+ * @param cand Candidate to encode
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ice_cand_encode(struct re_printf *pf, const struct ice_cand *cand)
+{
+ int err;
+
+ err = re_hprintf(pf, "%s %u %s %u %j %u typ %s",
+ cand->foundation, cand->compid,
+ transp_name(cand->transp), cand->prio,
+ &cand->addr, sa_port(&cand->addr),
+ ice_cand_type2name(cand->type));
+
+ if (sa_isset(&cand->rel, SA_ADDR))
+ err |= re_hprintf(pf, " raddr %j", &cand->rel);
+
+ if (sa_isset(&cand->rel, SA_PORT))
+ err |= re_hprintf(pf, " rport %u", sa_port(&cand->rel));
+
+ return err;
+}
+
+
+/**
+ * Check if remote candidates are available
+ *
+ * @param icem ICE Media object
+ *
+ * @return True if available, otherwise false
+ */
+bool ice_remotecands_avail(const struct icem *icem)
+{
+ if (!icem)
+ return false;
+
+ return icem->lrole == ICE_ROLE_CONTROLLING &&
+ icem->state == ICE_CHECKLIST_COMPLETED;
+}
+
+
+/**
+ * Encode the SDP "remote-candidates" Attribute
+ *
+ * @param pf Print function
+ * @param icem ICE Media object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ice_remotecands_encode(struct re_printf *pf, const struct icem *icem)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!icem)
+ return EINVAL;
+
+ for (le = icem->rcandl.head; le && !err; le = le->next) {
+
+ const struct ice_cand *rcand = le->data;
+
+ err = re_hprintf(pf, "%s%d %j %u",
+ icem->rcandl.head==le ? "" : " ",
+ rcand->compid,
+ &rcand->addr, sa_port(&rcand->addr));
+ }
+
+ return err;
+}
+
+
+/* Decode SDP Attributes */
+
+
+static int ufrag_decode(struct icem *icem, const char *value)
+{
+ char *ufrag = NULL;
+ int err;
+
+ err = str_dup(&ufrag, value);
+ if (err)
+ return err;
+
+ mem_deref(icem->rufrag);
+ icem->rufrag = mem_ref(ufrag);
+
+ mem_deref(ufrag);
+
+ return 0;
+}
+
+
+static int pwd_decode(struct icem *icem, const char *value)
+{
+ char *pwd = NULL;
+ int err;
+
+ err = str_dup(&pwd, value);
+ if (err)
+ return err;
+
+ mem_deref(icem->rpwd);
+ icem->rpwd = mem_ref(pwd);
+
+ mem_deref(pwd);
+
+ return 0;
+}
+
+
+static int media_ufrag_decode(struct icem *icem, const char *value)
+{
+ icem->rufrag = mem_deref(icem->rufrag);
+
+ return str_dup(&icem->rufrag, value);
+}
+
+
+static int media_pwd_decode(struct icem *icem, const char *value)
+{
+ icem->rpwd = mem_deref(icem->rpwd);
+
+ return str_dup(&icem->rpwd, value);
+}
+
+
+static int cand_decode(struct icem *icem, const char *val)
+{
+ struct pl foundation, compid, transp, prio, addr, port, cand_type;
+ struct pl extra = pl_null;
+ struct sa caddr, rel_addr;
+ char type[8];
+ uint8_t cid;
+ int err;
+
+ sa_init(&rel_addr, AF_INET);
+
+ err = re_regex(val, strlen(val),
+ "[^ ]+ [0-9]+ [^ ]+ [0-9]+ [^ ]+ [0-9]+ typ [a-z]+[^]*",
+ &foundation, &compid, &transp, &prio,
+ &addr, &port, &cand_type, &extra);
+ if (err)
+ return err;
+
+ if (ICE_TRANSP_NONE == transp_resolve(&transp)) {
+ DEBUG_NOTICE("<%s> ignoring candidate with"
+ " unknown transport=%r (%r:%r)\n",
+ icem->name, &transp, &cand_type, &addr);
+ return 0;
+ }
+
+ if (pl_isset(&extra)) {
+
+ struct pl name, value;
+
+ /* Loop through " SP attr SP value" pairs */
+ while (!re_regex(extra.p, extra.l, " [^ ]+ [^ ]+",
+ &name, &value)) {
+
+ pl_advance(&extra, value.p + value.l - extra.p);
+
+ if (0 == pl_strcasecmp(&name, rel_addr_str)) {
+ err = sa_set(&rel_addr, &value,
+ sa_port(&rel_addr));
+ if (err)
+ break;
+ }
+ else if (0 == pl_strcasecmp(&name, rel_port_str)) {
+ sa_set_port(&rel_addr, pl_u32(&value));
+ }
+ }
+ }
+
+ err = sa_set(&caddr, &addr, pl_u32(&port));
+ if (err)
+ return err;
+
+ cid = pl_u32(&compid);
+
+ /* add only if not exist */
+ if (icem_cand_find(&icem->rcandl, cid, &caddr))
+ return 0;
+
+ (void)pl_strcpy(&cand_type, type, sizeof(type));
+
+ return icem_rcand_add(icem, ice_cand_name2type(type), cid,
+ pl_u32(&prio), &caddr, &rel_addr, &foundation);
+}
+
+
+/**
+ * Decode SDP session attributes
+ *
+ * @param icem ICE Media object
+ * @param name Name of the SDP attribute
+ * @param value Value of the SDP attribute (optional)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int ice_sdp_decode(struct icem *icem, const char *name, const char *value)
+{
+ if (!icem)
+ return EINVAL;
+
+ if (0 == str_casecmp(name, ice_attr_lite)) {
+ if (ICE_MODE_LITE == icem->lmode) {
+ DEBUG_WARNING("we are lite, peer is also lite!\n");
+ return EPROTO;
+ }
+ icem->rmode = ICE_MODE_LITE;
+ icem->lrole = ICE_ROLE_CONTROLLING;
+ }
+ else if (0 == str_casecmp(name, ice_attr_ufrag))
+ return ufrag_decode(icem, value);
+ else if (0 == str_casecmp(name, ice_attr_pwd))
+ return pwd_decode(icem, value);
+
+ return 0;
+}
+
+
+/**
+ * Decode SDP media attributes
+ *
+ * @param icem ICE Media object
+ * @param name Name of the SDP attribute
+ * @param value Value of the SDP attribute (optional)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int icem_sdp_decode(struct icem *icem, const char *name, const char *value)
+{
+ if (!icem)
+ return EINVAL;
+
+ if (0 == str_casecmp(name, ice_attr_cand))
+ return cand_decode(icem, value);
+ else if (0 == str_casecmp(name, ice_attr_mismatch))
+ icem->mismatch = true;
+ else if (0 == str_casecmp(name, ice_attr_ufrag))
+ return media_ufrag_decode(icem, value);
+ else if (0 == str_casecmp(name, ice_attr_pwd))
+ return media_pwd_decode(icem, value);
+
+ return 0;
+}
+
+
+static const char *ice_tcptype_name(enum ice_tcptype tcptype)
+{
+ switch (tcptype) {
+
+ case ICE_TCP_ACTIVE: return "active";
+ case ICE_TCP_PASSIVE: return "passive";
+ case ICE_TCP_SO: return "so";
+ default: return "???";
+ }
+}
+
+
+static enum ice_tcptype ice_tcptype_resolve(const struct pl *pl)
+{
+ if (0 == pl_strcasecmp(pl, "active")) return ICE_TCP_ACTIVE;
+ if (0 == pl_strcasecmp(pl, "passive")) return ICE_TCP_PASSIVE;
+ if (0 == pl_strcasecmp(pl, "so")) return ICE_TCP_SO;
+
+ return (enum ice_tcptype)-1;
+}
+
+
+int ice_cand_attr_encode(struct re_printf *pf,
+ const struct ice_cand_attr *cand)
+{
+ int err = 0;
+
+ if (!cand)
+ return 0;
+
+ err |= re_hprintf(pf, "%s %u %s %u %j %u typ %s",
+ cand->foundation, cand->compid,
+ net_proto2name(cand->proto), cand->prio,
+ &cand->addr, sa_port(&cand->addr),
+ ice_cand_type2name(cand->type));
+
+ if (sa_isset(&cand->rel_addr, SA_ADDR))
+ err |= re_hprintf(pf, " raddr %j", &cand->rel_addr);
+
+ if (sa_isset(&cand->rel_addr, SA_PORT))
+ err |= re_hprintf(pf, " rport %u", sa_port(&cand->rel_addr));
+
+ if (cand->proto == IPPROTO_TCP) {
+ err |= re_hprintf(pf, " tcptype %s",
+ ice_tcptype_name(cand->tcptype));
+ }
+
+ return err;
+}
+
+
+int ice_cand_attr_decode(struct ice_cand_attr *cand, const char *val)
+{
+ struct pl pl_fnd, pl_compid, pl_transp, pl_prio, pl_addr, pl_port;
+ struct pl pl_type, pl_raddr, pl_rport, pl_opt = PL_INIT;
+ size_t len;
+ char type[8];
+ int err;
+
+ if (!cand || !val)
+ return EINVAL;
+
+ memset(cand, 0, sizeof(*cand));
+
+ len = str_len(val);
+
+ err = re_regex(val, len,
+ "[^ ]+ [0-9]+ [a-z]+ [0-9]+ [^ ]+ [0-9]+ typ [a-z]+"
+ "[^]*",
+ &pl_fnd, &pl_compid, &pl_transp, &pl_prio,
+ &pl_addr, &pl_port, &pl_type, &pl_opt);
+ if (err)
+ return err;
+
+ (void)pl_strcpy(&pl_fnd, cand->foundation, sizeof(cand->foundation));
+
+ if (0 == pl_strcasecmp(&pl_transp, "UDP"))
+ cand->proto = IPPROTO_UDP;
+ else if (0 == pl_strcasecmp(&pl_transp, "TCP"))
+ cand->proto = IPPROTO_TCP;
+ else
+ cand->proto = 0;
+
+ err = sa_set(&cand->addr, &pl_addr, pl_u32(&pl_port));
+ if (err)
+ return err;
+
+ cand->compid = pl_u32(&pl_compid);
+ cand->prio = pl_u32(&pl_prio);
+
+ (void)pl_strcpy(&pl_type, type, sizeof(type));
+
+ cand->type = ice_cand_name2type(type);
+
+ /* optional */
+
+ if (0 == re_regex(pl_opt.p, pl_opt.l, "raddr [^ ]+ rport [0-9]+",
+ &pl_raddr, &pl_rport)) {
+
+ err = sa_set(&cand->rel_addr, &pl_raddr, pl_u32(&pl_rport));
+ if (err)
+ return err;
+ }
+
+ if (cand->proto == IPPROTO_TCP) {
+
+ struct pl tcptype;
+
+ err = re_regex(pl_opt.p, pl_opt.l, "tcptype [^ ]+",
+ &tcptype);
+ if (err)
+ return err;
+
+ cand->tcptype = ice_tcptype_resolve(&tcptype);
+ }
+
+ return 0;
+}
diff --git a/src/ice/icestr.c b/src/ice/icestr.c
new file mode 100644
index 0000000..2d39d29
--- /dev/null
+++ b/src/ice/icestr.c
@@ -0,0 +1,88 @@
+/**
+ * @file icestr.c ICE Strings
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_stun.h>
+#include <re_ice.h>
+#include "ice.h"
+
+
+const char *ice_cand_type2name(enum ice_cand_type type)
+{
+ switch (type) {
+
+ case ICE_CAND_TYPE_HOST: return "host";
+ case ICE_CAND_TYPE_SRFLX: return "srflx";
+ case ICE_CAND_TYPE_PRFLX: return "prflx";
+ case ICE_CAND_TYPE_RELAY: return "relay";
+ default: return "???";
+ }
+}
+
+
+enum ice_cand_type ice_cand_name2type(const char *name)
+{
+ if (0 == str_casecmp(name, "host")) return ICE_CAND_TYPE_HOST;
+ if (0 == str_casecmp(name, "srflx")) return ICE_CAND_TYPE_SRFLX;
+ if (0 == str_casecmp(name, "prflx")) return ICE_CAND_TYPE_PRFLX;
+ if (0 == str_casecmp(name, "relay")) return ICE_CAND_TYPE_RELAY;
+
+ return (enum ice_cand_type)-1;
+}
+
+
+const char *ice_mode2name(enum ice_mode mode)
+{
+ switch (mode) {
+
+ case ICE_MODE_FULL: return "Full";
+ case ICE_MODE_LITE: return "Lite";
+ default: return "???";
+ }
+}
+
+
+const char *ice_role2name(enum ice_role role)
+{
+ switch (role) {
+
+ case ICE_ROLE_UNKNOWN: return "Unknown";
+ case ICE_ROLE_CONTROLLING: return "Controlling";
+ case ICE_ROLE_CONTROLLED: return "Controlled";
+ default: return "???";
+ }
+}
+
+
+const char *ice_candpair_state2name(enum ice_candpair_state st)
+{
+ switch (st) {
+
+ case ICE_CANDPAIR_FROZEN: return "Frozen";
+ case ICE_CANDPAIR_WAITING: return "Waiting";
+ case ICE_CANDPAIR_INPROGRESS: return "InProgress";
+ case ICE_CANDPAIR_SUCCEEDED: return "Succeeded";
+ case ICE_CANDPAIR_FAILED: return "Failed";
+ default: return "???";
+ }
+}
+
+
+const char *ice_checkl_state2name(enum ice_checkl_state cst)
+{
+ switch (cst) {
+
+ case ICE_CHECKLIST_NULL: return "(NULL)";
+ case ICE_CHECKLIST_RUNNING: return "Running";
+ case ICE_CHECKLIST_COMPLETED: return "Completed";
+ case ICE_CHECKLIST_FAILED: return "Failed";
+ default: return "???";
+ }
+}
diff --git a/src/ice/mod.mk b/src/ice/mod.mk
new file mode 100644
index 0000000..54aefbe
--- /dev/null
+++ b/src/ice/mod.mk
@@ -0,0 +1,16 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += ice/cand.c
+SRCS += ice/candpair.c
+SRCS += ice/chklist.c
+SRCS += ice/comp.c
+SRCS += ice/connchk.c
+SRCS += ice/icem.c
+SRCS += ice/icesdp.c
+SRCS += ice/icestr.c
+SRCS += ice/stunsrv.c
+SRCS += ice/util.c
diff --git a/src/ice/stunsrv.c b/src/ice/stunsrv.c
new file mode 100644
index 0000000..5f73f1d
--- /dev/null
+++ b/src/ice/stunsrv.c
@@ -0,0 +1,324 @@
+/**
+ * @file stunsrv.c Basic STUN Server for Connectivity checks
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_stun.h>
+#include <re_ice.h>
+#include <re_sys.h>
+#include "ice.h"
+
+
+#define DEBUG_MODULE "stunsrv"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static const char *sw = "ice stunsrv v" VERSION " (" ARCH "/" OS ")";
+
+
+static void triggered_check(struct icem *icem, struct ice_cand *lcand,
+ struct ice_cand *rcand)
+{
+ struct ice_candpair *cp = NULL;
+ int err;
+
+ if (lcand && rcand)
+ cp = icem_candpair_find(&icem->checkl, lcand, rcand);
+
+ if (cp) {
+
+ switch (cp->state) {
+
+#if 0
+ /* TODO: I am not sure why we should cancel the
+ * pending Connectivity check here. this
+ * can lead to a deadlock situation where
+ * both agents are stuck on sending
+ * triggered checks on the same candidate pair
+ */
+ case ICE_CANDPAIR_INPROGRESS:
+ icem_candpair_cancel(cp);
+ /*@fallthrough@*/
+#endif
+
+ case ICE_CANDPAIR_FAILED:
+ icem_candpair_set_state(cp, ICE_CANDPAIR_WAITING);
+ /*@fallthrough@*/
+
+ case ICE_CANDPAIR_FROZEN:
+ case ICE_CANDPAIR_WAITING:
+ err = icem_conncheck_send(cp, false, true);
+ if (err) {
+ DEBUG_WARNING("triggered check failed\n");
+ }
+ break;
+
+ case ICE_CANDPAIR_SUCCEEDED:
+ default:
+ break;
+ }
+ }
+ else {
+
+#if 0
+ err = icem_candpair_alloc(&cp, icem, lcand, rcand);
+ if (err) {
+ DEBUG_WARNING("failed to allocate candpair:"
+ " lcand=%p rcand=%p (%m)\n",
+ lcand, rcand, err);
+ return;
+ }
+
+ icem_candpair_prio_order(&icem->checkl);
+
+ icem_candpair_set_state(cp, ICE_CANDPAIR_WAITING);
+
+ (void)icem_conncheck_send(cp, false, true);
+#endif
+
+ }
+}
+
+
+/*
+ * 7.2.1. Additional Procedures for Full Implementations
+ */
+static int handle_stun_full(struct icem *icem,
+ struct icem_comp *comp, const struct sa *src,
+ uint32_t prio, bool use_cand, bool tunnel)
+{
+ struct ice_cand *lcand = NULL, *rcand;
+ struct ice_candpair *cp = NULL;
+ int err;
+
+ rcand = icem_cand_find(&icem->rcandl, comp->id, src);
+ if (!rcand) {
+ err = icem_rcand_add_prflx(&rcand, icem, comp->id, prio, src);
+ if (err)
+ return err;
+ }
+
+ cp = icem_candpair_find_rcand(icem, rcand);
+ if (cp)
+ lcand = cp->lcand;
+ else
+ lcand = icem_lcand_find_checklist(icem, comp->id);
+
+ if (!lcand) {
+ DEBUG_WARNING("{%s.%u} local candidate not found"
+ " (checklist=%u) (src=%J)\n",
+ icem->name, comp->id,
+ list_count(&icem->checkl), src);
+ return 0;
+ }
+
+ triggered_check(icem, lcand, rcand);
+
+ if (!cp) {
+ cp = icem_candpair_find_rcand(icem, rcand);
+ if (!cp) {
+ DEBUG_WARNING("{%s.%u} candidate pair not found:"
+ " source=%J\n",
+ icem->name, comp->id, src);
+ return 0;
+ }
+ }
+
+#if ICE_TRACE
+ icecomp_printf(comp, "Rx Binding Request from %J via %s"
+ " (candpair=%s) %s\n",
+ src, tunnel ? "Tunnel" : "Socket",
+ cp ? ice_candpair_state2name(cp->state) : "n/a",
+ use_cand ? "[USE]" : "");
+#else
+ (void)tunnel;
+#endif
+
+ /* 7.2.1.5. Updating the Nominated Flag */
+ if (use_cand) {
+ if (icem->lrole == ICE_ROLE_CONTROLLED &&
+ cp->state == ICE_CANDPAIR_SUCCEEDED) {
+
+ if (!cp->nominated) {
+ icecomp_printf(comp, "setting NOMINATED"
+ " flag on candpair [%H]\n",
+ icem_candpair_debug, cp);
+ }
+
+ cp->nominated = true;
+ }
+
+ /* Cancel conncheck. Choose Selected Pair */
+ icem_candpair_make_valid(cp);
+
+ if (icem->conf.nom == ICE_NOMINATION_REGULAR) {
+ icem_candpair_cancel(cp);
+ icem_comp_set_selected(comp, cp);
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * 7.2.2. Additional Procedures for Lite Implementations
+ */
+static int handle_stun_lite(struct icem *icem,
+ struct icem_comp *comp, const struct sa *src,
+ bool use_cand)
+{
+ struct ice_cand *lcand, *rcand;
+ struct ice_candpair *cp;
+ int err;
+
+ if (!use_cand)
+ return 0;
+
+ rcand = icem_cand_find(&icem->rcandl, comp->id, src);
+ if (!rcand) {
+ DEBUG_WARNING("lite: could not find remote candidate\n");
+ return 0;
+ }
+
+ /* find the local host candidate with the same component */
+ lcand = icem_cand_find(&icem->lcandl, comp->id, NULL);
+ if (!lcand) {
+ DEBUG_WARNING("lite: could not find local candidate\n");
+ return 0;
+ }
+
+ /* search validlist for existing candpair's */
+ if (icem_candpair_find(&icem->validl, lcand, rcand))
+ return 0;
+
+ err = icem_candpair_alloc(&cp, icem, lcand, rcand);
+ if (err) {
+ DEBUG_WARNING("lite: failed to created candidate pair\n");
+ return err;
+ }
+
+ icem_candpair_make_valid(cp);
+ cp->nominated = true;
+
+ return 0;
+}
+
+
+static int stunsrv_ereply(struct icem_comp *comp, const struct sa *src,
+ size_t presz, const struct stun_msg *req,
+ uint16_t scode, const char *reason)
+{
+ struct icem *icem = comp->icem;
+
+ return stun_ereply(icem->proto, comp->sock, src, presz, req,
+ scode, reason,
+ (uint8_t *)icem->lpwd, strlen(icem->lpwd), true, 1,
+ STUN_ATTR_SOFTWARE, sw);
+}
+
+
+int icem_stund_recv(struct icem_comp *comp, const struct sa *src,
+ struct stun_msg *req, size_t presz)
+{
+ struct icem *icem = comp->icem;
+ struct stun_attr *attr;
+ struct pl lu, ru;
+ enum ice_role rrole = ICE_ROLE_UNKNOWN;
+ uint64_t tiebrk = 0;
+ uint32_t prio_prflx;
+ bool use_cand = false;
+ int err;
+
+ /* RFC 5389: Fingerprint errors are silently discarded */
+ err = stun_msg_chk_fingerprint(req);
+ if (err)
+ return err;
+
+ err = stun_msg_chk_mi(req, (uint8_t *)icem->lpwd, strlen(icem->lpwd));
+ if (err) {
+ if (err == EBADMSG)
+ goto unauth;
+ else
+ goto badmsg;
+ }
+
+ attr = stun_msg_attr(req, STUN_ATTR_USERNAME);
+ if (!attr)
+ goto badmsg;
+
+ err = re_regex(attr->v.username, strlen(attr->v.username),
+ "[^:]+:[^]+", &lu, &ru);
+ if (err) {
+ DEBUG_WARNING("could not parse USERNAME attribute (%s)\n",
+ attr->v.username);
+ goto unauth;
+ }
+ if (pl_strcmp(&lu, icem->lufrag))
+ goto unauth;
+ if (str_isset(icem->rufrag) && pl_strcmp(&ru, icem->rufrag))
+ goto unauth;
+
+ attr = stun_msg_attr(req, STUN_ATTR_CONTROLLED);
+ if (attr) {
+ rrole = ICE_ROLE_CONTROLLED;
+ tiebrk = attr->v.uint64;
+ }
+
+ attr = stun_msg_attr(req, STUN_ATTR_CONTROLLING);
+ if (attr) {
+ rrole = ICE_ROLE_CONTROLLING;
+ tiebrk = attr->v.uint64;
+ }
+
+ if (rrole == icem->lrole) {
+ if (icem->tiebrk >= tiebrk)
+ ice_switch_local_role(icem);
+ else
+ goto conflict;
+ }
+
+ attr = stun_msg_attr(req, STUN_ATTR_PRIORITY);
+ if (attr)
+ prio_prflx = attr->v.uint32;
+ else
+ goto badmsg;
+
+ attr = stun_msg_attr(req, STUN_ATTR_USE_CAND);
+ if (attr)
+ use_cand = true;
+
+ if (icem->lmode == ICE_MODE_FULL) {
+ err = handle_stun_full(icem, comp, src, prio_prflx,
+ use_cand, presz > 0);
+ }
+ else {
+ err = handle_stun_lite(icem, comp, src, use_cand);
+ }
+
+ if (err)
+ goto badmsg;
+
+ return stun_reply(icem->proto, comp->sock, src, presz, req,
+ (uint8_t *)icem->lpwd, strlen(icem->lpwd), true, 2,
+ STUN_ATTR_XOR_MAPPED_ADDR, src,
+ STUN_ATTR_SOFTWARE, sw);
+
+ badmsg:
+ return stunsrv_ereply(comp, src, presz, req, 400, "Bad Request");
+
+ unauth:
+ return stunsrv_ereply(comp, src, presz, req, 401, "Unauthorized");
+
+ conflict:
+ return stunsrv_ereply(comp, src, presz, req, 487, "Role Conflict");
+}
diff --git a/src/ice/util.c b/src/ice/util.c
new file mode 100644
index 0000000..d6376fb
--- /dev/null
+++ b/src/ice/util.c
@@ -0,0 +1,145 @@
+/**
+ * @file ice/util.c ICE Utilities
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#ifndef WIN32
+#include <time.h>
+#endif
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_stun.h>
+#include <re_sys.h>
+#include <re_ice.h>
+#include "ice.h"
+
+
+#define DEBUG_MODULE "iceutil"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+enum {
+ CAND_PRIO_RELAY = 0,
+ CAND_PRIO_SRFLX = 100,
+ CAND_PRIO_PRFLX = 110,
+ CAND_PRIO_HOST = 126
+};
+
+
+static uint32_t type_prio(enum ice_cand_type type)
+{
+ switch (type) {
+
+ case ICE_CAND_TYPE_HOST: return CAND_PRIO_HOST;
+ case ICE_CAND_TYPE_SRFLX: return CAND_PRIO_SRFLX;
+ case ICE_CAND_TYPE_PRFLX: return CAND_PRIO_PRFLX;
+ case ICE_CAND_TYPE_RELAY: return CAND_PRIO_RELAY;
+ default: return 0;
+ }
+}
+
+
+uint32_t ice_cand_calc_prio(enum ice_cand_type type, uint16_t local,
+ unsigned compid)
+{
+ return type_prio(type)<<24 | (uint32_t)local<<8 | (256 - compid);
+}
+
+
+/*
+ * g = controlling agent
+ * d = controlled agent
+
+ pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
+
+ */
+uint64_t ice_calc_pair_prio(uint32_t g, uint32_t d)
+{
+ const uint64_t m = min(g, d);
+ const uint64_t x = max(g, d);
+
+ return (m<<32) + 2*x + (g>d?1:0);
+}
+
+
+void ice_switch_local_role(struct icem *icem)
+{
+ enum ice_role new_role;
+
+ if (ICE_ROLE_CONTROLLING == icem->lrole)
+ new_role = ICE_ROLE_CONTROLLED;
+ else
+ new_role = ICE_ROLE_CONTROLLING;
+
+ DEBUG_NOTICE("Switch local role from %s to %s\n",
+ ice_role2name(icem->lrole), ice_role2name(new_role));
+
+ icem->lrole = new_role;
+
+#if 0
+ /* recompute pair priorities for all media streams */
+ for (le = icem->le.list->head; le; le = le->next) {
+ icem = le->data;
+ icem_candpair_prio_order(&icem->checkl);
+ }
+#endif
+}
+
+
+/**
+ * Remove duplicate elements from list, preserving order
+ *
+ * @param list Linked list
+ * @param uh Unique handler (return object to remove)
+ *
+ * @return Number of elements removed
+ *
+ * @note: O (n ^ 2)
+ */
+uint32_t ice_list_unique(struct list *list, list_unique_h *uh)
+{
+ struct le *le1 = list_head(list);
+ uint32_t n = 0;
+
+ while (le1 && le1 != list->tail) {
+
+ struct le *le2 = le1->next;
+ void *data = NULL;
+
+ while (le2) {
+
+ data = uh(le1, le2);
+
+ le2 = le2->next;
+
+ if (!data)
+ continue;
+
+ if (le1->data == data)
+ break;
+ else {
+ data = mem_deref(data);
+ ++n;
+ }
+ }
+
+ le1 = le1->next;
+
+ if (data) {
+ mem_deref(data);
+ ++n;
+ }
+ }
+
+ return n;
+}
diff --git a/src/jbuf/jbuf.c b/src/jbuf/jbuf.c
new file mode 100644
index 0000000..47a1764
--- /dev/null
+++ b/src/jbuf/jbuf.c
@@ -0,0 +1,437 @@
+/**
+ * @file jbuf.c Jitter Buffer implementation
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_mbuf.h>
+#include <re_mem.h>
+#include <re_rtp.h>
+#include <re_jbuf.h>
+
+
+#define DEBUG_MODULE "jbuf"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+#ifndef RELEASE
+#define JBUF_STAT 1 /**< Jitter buffer statistics */
+#endif
+
+
+#if JBUF_STAT
+#define STAT_ADD(var, value) (jb->stat.var) += (value) /**< Stats add */
+#define STAT_INC(var) ++(jb->stat.var) /**< Stats inc */
+#else
+#define STAT_ADD(var, value)
+#define STAT_INC(var)
+#endif
+
+
+/** Defines a packet frame */
+struct frame {
+ struct le le; /**< Linked list element */
+ struct rtp_header hdr; /**< RTP Header */
+ void *mem; /**< Reference counted pointer */
+};
+
+
+/**
+ * Defines a jitter buffer
+ *
+ * The jitter buffer is for incoming RTP packets, which are sorted by
+ * sequence number.
+ */
+struct jbuf {
+ struct list pooll; /**< List of free frames in pool */
+ struct list framel; /**< List of buffered frames */
+ uint32_t n; /**< [# frames] Current # of frames in buffer */
+ uint32_t min; /**< [# frames] Minimum # of frames to buffer */
+ uint32_t max; /**< [# frames] Maximum # of frames to buffer */
+ uint16_t seq_put; /**< Sequence number for last jbuf_put() */
+ bool running; /**< Jitter buffer is running */
+
+#if JBUF_STAT
+ uint16_t seq_get; /**< Timestamp of last played frame */
+ struct jbuf_stat stat; /**< Jitter buffer Statistics */
+#endif
+};
+
+
+/** Is x less than y? */
+static inline bool seq_less(uint16_t x, uint16_t y)
+{
+ return ((int16_t)(x - y)) < 0;
+}
+
+
+/**
+ * Get a frame from the pool
+ */
+static void frame_alloc(struct jbuf *jb, struct frame **f)
+{
+ struct le *le;
+
+ le = jb->pooll.head;
+ if (le) {
+ list_unlink(le);
+ ++jb->n;
+ }
+ else {
+ struct frame *f0;
+
+ /* Steal an old frame */
+ le = jb->framel.head;
+ f0 = le->data;
+
+ STAT_INC(n_overflow);
+ DEBUG_INFO("drop 1 old frame seq=%u (total dropped %u)\n",
+ f0->hdr.seq, jb->stat.n_overflow);
+
+ f0->mem = mem_deref(f0->mem);
+ list_unlink(le);
+ }
+
+ *f = le->data;
+}
+
+
+/**
+ * Release a frame, put it back in the pool
+ */
+static void frame_deref(struct jbuf *jb, struct frame *f)
+{
+ f->mem = mem_deref(f->mem);
+ list_unlink(&f->le);
+ list_append(&jb->pooll, &f->le, f);
+ --jb->n;
+}
+
+
+static void jbuf_destructor(void *data)
+{
+ struct jbuf *jb = data;
+
+ jbuf_flush(jb);
+
+ /* Free all frames in the pool list */
+ list_flush(&jb->pooll);
+}
+
+
+/**
+ * Allocate a new jitter buffer
+ *
+ * @param jbp Pointer to returned jitter buffer
+ * @param min Minimum delay in [frames]
+ * @param max Maximum delay in [frames]
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int jbuf_alloc(struct jbuf **jbp, uint32_t min, uint32_t max)
+{
+ struct jbuf *jb;
+ uint32_t i;
+ int err = 0;
+
+ if (!jbp || ( min > max))
+ return EINVAL;
+
+ DEBUG_INFO("alloc: delay=%u-%u frames\n", min, max);
+
+ /* self-test: x < y (also handle wrap around) */
+ if (!seq_less(10, 20) || seq_less(20, 10) || !seq_less(65535, 0)) {
+ DEBUG_WARNING("seq_less() is broken\n");
+ return ENOSYS;
+ }
+
+ jb = mem_zalloc(sizeof(*jb), jbuf_destructor);
+ if (!jb)
+ return ENOMEM;
+
+ list_init(&jb->pooll);
+ list_init(&jb->framel);
+
+ jb->min = min;
+ jb->max = max;
+
+ /* Allocate all frames now */
+ for (i=0; i<jb->max; i++) {
+ struct frame *f = mem_zalloc(sizeof(*f), NULL);
+ if (!f) {
+ err = ENOMEM;
+ break;
+ }
+
+ list_append(&jb->pooll, &f->le, f);
+ DEBUG_INFO("alloc: adding to pool list %u\n", i);
+ }
+
+ if (err)
+ mem_deref(jb);
+ else
+ *jbp = jb;
+
+ return err;
+}
+
+
+/**
+ * Put one frame into the jitter buffer
+ *
+ * @param jb Jitter buffer
+ * @param hdr RTP Header
+ * @param mem Memory pointer - will be referenced
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int jbuf_put(struct jbuf *jb, const struct rtp_header *hdr, void *mem)
+{
+ struct frame *f;
+ struct le *le, *tail;
+ uint16_t seq;
+ int err = 0;
+
+ if (!jb || !hdr)
+ return EINVAL;
+
+ seq = hdr->seq;
+
+ STAT_INC(n_put);
+
+ if (jb->running) {
+
+ /* Packet arrived too late to be put into buffer */
+ if (seq_less((seq + jb->n), jb->seq_put)) {
+ STAT_INC(n_late);
+ DEBUG_INFO("packet too late: seq=%u (seq_put=%u)\n",
+ seq, jb->seq_put);
+ return ETIMEDOUT;
+ }
+ }
+
+ frame_alloc(jb, &f);
+
+ tail = jb->framel.tail;
+
+ /* If buffer is empty -> append to tail
+ Frame is later than tail -> append to tail
+ */
+ if (!tail || seq_less(((struct frame *)tail->data)->hdr.seq, seq)) {
+ list_append(&jb->framel, &f->le, f);
+ goto out;
+ }
+
+ /* Out-of-sequence, find right position */
+ for (le = tail; le; le = le->prev) {
+ const uint16_t seq_le = ((struct frame *)le->data)->hdr.seq;
+
+ if (seq_less(seq_le, seq)) { /* most likely */
+ DEBUG_INFO("put: out-of-sequence"
+ " - inserting after seq=%u (seq=%u)\n",
+ seq_le, seq);
+ list_insert_after(&jb->framel, le, &f->le, f);
+ break;
+ }
+ else if (seq == seq_le) { /* less likely */
+ /* Detect duplicates */
+ DEBUG_INFO("duplicate: seq=%u\n", seq);
+ STAT_INC(n_dups);
+ list_insert_after(&jb->framel, le, &f->le, f);
+ frame_deref(jb, f);
+ return EALREADY;
+ }
+
+ /* sequence number less than current seq, continue */
+ }
+
+ /* no earlier timestamps found, put in head */
+ if (!le) {
+ DEBUG_INFO("put: out-of-sequence"
+ " - put in head (seq=%u)\n", seq);
+ list_prepend(&jb->framel, &f->le, f);
+ }
+
+ STAT_INC(n_oos);
+
+ out:
+ /* Update last timestamp */
+ jb->running = true;
+ jb->seq_put = seq;
+
+ /* Success */
+ f->hdr = *hdr;
+ f->mem = mem_ref(mem);
+
+ return err;
+}
+
+
+/**
+ * Get one frame from the jitter buffer
+ *
+ * @param jb Jitter buffer
+ * @param hdr Returned RTP Header
+ * @param mem Pointer to memory object storage - referenced on success
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int jbuf_get(struct jbuf *jb, struct rtp_header *hdr, void **mem)
+{
+ struct frame *f;
+
+ if (!jb || !hdr || !mem)
+ return EINVAL;
+
+ STAT_INC(n_get);
+
+ if (jb->n <= jb->min || !jb->framel.head) {
+ DEBUG_INFO("not enough buffer frames - wait.. (n=%u min=%u)\n",
+ jb->n, jb->min);
+ STAT_INC(n_underflow);
+ return ENOENT;
+ }
+
+ /* When we get one frame F[i], check that the next frame F[i+1]
+ is present and have a seq no. of seq[i] + 1 !
+ if not, we should consider that packet lost */
+
+ f = jb->framel.head->data;
+
+#if JBUF_STAT
+ /* Check timestamp of previously played frame */
+ if (jb->seq_get) {
+ const int16_t seq_diff = f->hdr.seq - jb->seq_get;
+ if (seq_less(f->hdr.seq, jb->seq_get)) {
+ DEBUG_WARNING("get: seq=%u too late\n", f->hdr.seq);
+ }
+ else if (seq_diff > 1) {
+ STAT_ADD(n_lost, 1);
+ DEBUG_INFO("get: n_lost: diff=%d,seq=%u,seq_get=%u\n",
+ seq_diff, f->hdr.seq, jb->seq_get);
+ }
+ }
+
+ /* Update sequence number for 'get' */
+ jb->seq_get = f->hdr.seq;
+#endif
+
+ *hdr = f->hdr;
+ *mem = mem_ref(f->mem);
+
+ frame_deref(jb, f);
+
+ return 0;
+}
+
+
+/**
+ * Flush all frames in the jitter buffer
+ *
+ * @param jb Jitter buffer
+ */
+void jbuf_flush(struct jbuf *jb)
+{
+ struct le *le;
+#if JBUF_STAT
+ uint32_t n_flush;
+#endif
+
+ if (!jb)
+ return;
+
+ if (jb->framel.head) {
+ DEBUG_INFO("flush: %u frames\n", jb->n);
+ }
+
+ /* put all buffered frames back in free list */
+ for (le = jb->framel.head; le; le = jb->framel.head) {
+ DEBUG_INFO(" flush frame: seq=%u\n",
+ ((struct frame *)(le->data))->hdr.seq);
+
+ frame_deref(jb, le->data);
+ }
+
+ jb->n = 0;
+ jb->running = false;
+
+#if JBUF_STAT
+ n_flush = STAT_INC(n_flush);
+ jb->seq_get = 0;
+ memset(&jb->stat, 0, sizeof(jb->stat));
+ jb->stat.n_flush = n_flush;
+#endif
+
+}
+
+
+/**
+ * Get jitter buffer statistics
+ *
+ * @param jb Jitter buffer
+ * @param jstat Pointer to statistics storage
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int jbuf_stats(const struct jbuf *jb, struct jbuf_stat *jstat)
+{
+ if (!jb || !jstat)
+ return EINVAL;
+
+#if JBUF_STAT
+ *jstat = jb->stat;
+
+ return 0;
+#else
+ return ENOSYS;
+#endif
+}
+
+
+/**
+ * Debug the jitter buffer
+ *
+ * @param pf Print handler
+ * @param jb Jitter buffer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int jbuf_debug(struct re_printf *pf, const struct jbuf *jb)
+{
+ int err = 0;
+
+ if (!jb)
+ return 0;
+
+ err |= re_hprintf(pf, "--- jitter buffer debug---\n");
+
+ err |= re_hprintf(pf, " running=%d", jb->running);
+ err |= re_hprintf(pf, " min=%u cur=%u max=%u [frames]\n",
+ jb->min, jb->n, jb->max);
+ err |= re_hprintf(pf, " seq_put=%u\n", jb->seq_put);
+
+#if JBUF_STAT
+ err |= re_hprintf(pf, " Stat: put=%u", jb->stat.n_put);
+ err |= re_hprintf(pf, " get=%u", jb->stat.n_get);
+ err |= re_hprintf(pf, " oos=%u", jb->stat.n_oos);
+ err |= re_hprintf(pf, " dup=%u", jb->stat.n_dups);
+ err |= re_hprintf(pf, " late=%u", jb->stat.n_late);
+ err |= re_hprintf(pf, " or=%u", jb->stat.n_overflow);
+ err |= re_hprintf(pf, " ur=%u", jb->stat.n_underflow);
+ err |= re_hprintf(pf, " flush=%u", jb->stat.n_flush);
+ err |= re_hprintf(pf, " put/get_ratio=%u%%", jb->stat.n_get ?
+ 100*jb->stat.n_put/jb->stat.n_get : 0);
+ err |= re_hprintf(pf, " lost=%u (%u.%02u%%)\n",
+ jb->stat.n_lost,
+ jb->stat.n_put ?
+ 100*jb->stat.n_lost/jb->stat.n_put : 0,
+ jb->stat.n_put ?
+ 10000*jb->stat.n_lost/jb->stat.n_put%100 : 0);
+#endif
+
+ return err;
+}
diff --git a/src/jbuf/mod.mk b/src/jbuf/mod.mk
new file mode 100644
index 0000000..7c7c0fd
--- /dev/null
+++ b/src/jbuf/mod.mk
@@ -0,0 +1,7 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += jbuf/jbuf.c
diff --git a/src/json/decode.c b/src/json/decode.c
new file mode 100644
index 0000000..067ecff
--- /dev/null
+++ b/src/json/decode.c
@@ -0,0 +1,469 @@
+/**
+ * @file json/decode.c JSON decoder
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_odict.h>
+#include <re_json.h>
+
+
+static inline long double mypower10(uint64_t e)
+{
+ long double p = 10, n = 1;
+
+ while (e > 0) {
+ if (e & 1)
+ n *= p;
+
+ p *= p;
+ e >>= 1;
+ }
+
+ return n;
+}
+
+
+static bool is_string(struct pl *c, const struct pl *pl)
+{
+ if (pl->l < 2)
+ return false;
+
+ if (pl->p[0] != '"'|| pl->p[pl->l-1] != '"')
+ return false;
+
+ c->p = pl->p + 1;
+ c->l = pl->l - 2;
+
+ return true;
+}
+
+
+static bool is_number(long double *d, bool *isfloat, const struct pl *pl)
+{
+ bool neg = false, pos = false, frac = false, exp = false;
+ long double v = 0, mul = 1;
+ const char *p;
+ int64_t e = 0;
+
+ if (!pl->l)
+ return false;
+
+ p = &pl->p[pl->l];
+
+ while (p > pl->p) {
+
+ const char ch = *--p;
+
+ if (ch == 'e' || ch == 'E') {
+
+ if (exp || frac)
+ return false;
+
+ exp = true;
+ e = neg ? -v : v;
+ v = 0;
+ mul = 1;
+ neg = false;
+ pos = false;
+ }
+ else if (pos || neg) {
+ return false;
+ }
+ else if (ch == '.') {
+
+ if (frac)
+ return false;
+
+ frac = true;
+ v /= mul;
+ mul = 1;
+ }
+ else if ('0' <= ch && ch <= '9') {
+ v += mul * (ch - '0');
+ mul *= 10;
+ }
+ else if (ch == '-') {
+ neg = true;
+ }
+ else if (ch == '+') {
+ pos = true;
+ }
+ else {
+ return false;
+ }
+ }
+
+ *isfloat = (frac || exp);
+
+ if (exp) {
+ if (e < 0)
+ v /= mypower10(-e);
+ else
+ v *= mypower10(e);
+ }
+
+ if (neg)
+ v = -v;
+
+ *d = v;
+
+ return true;
+}
+
+
+static int decode_name(char **str, const struct pl *pl)
+{
+ struct pl pls;
+
+ if (!pl->p)
+ return EBADMSG;
+
+ if (!is_string(&pls, pl))
+ return EBADMSG;
+
+ return re_sdprintf(str, "%H", utf8_decode, &pls);
+}
+
+
+static int decode_value(struct json_value *val, const struct pl *pl)
+{
+ long double dbl;
+ struct pl pls;
+ bool isfloat;
+ int err = 0;
+
+ if (!pl->p)
+ return EBADMSG;
+
+ if (is_string(&pls, pl)) {
+
+ err = re_sdprintf(&val->v.str, "%H", utf8_decode, &pls);
+ val->type = JSON_STRING;
+ }
+ else if (is_number(&dbl, &isfloat, pl)) {
+
+ if (isfloat) {
+ val->type = JSON_DOUBLE;
+ val->v.dbl = dbl;
+ }
+ else {
+ val->type = JSON_INT;
+ val->v.integer = dbl;
+ }
+ }
+ else if (!pl_strcasecmp(pl, "false")) {
+
+ val->v.boolean = false;
+ val->type = JSON_BOOL;
+ }
+ else if (!pl_strcasecmp(pl, "true")) {
+
+ val->v.boolean = true;
+ val->type = JSON_BOOL;
+ }
+ else if (!pl_strcasecmp(pl, "null")) {
+
+ val->type = JSON_NULL;
+ }
+ else {
+ re_printf("json: value of unknown type: <%r>\n", pl);
+ err = EBADMSG;
+ }
+
+ return err;
+}
+
+
+static int object_entry(const struct pl *pl_name, const struct pl *pl_val,
+ json_object_entry_h *oeh, void *arg)
+{
+ struct json_value val;
+ char *name;
+ int err;
+
+ err = decode_name(&name, pl_name);
+ if (err)
+ return err;
+
+ err = decode_value(&val, pl_val);
+ if (err)
+ goto out;
+
+ if (oeh)
+ err = oeh(name, &val, arg);
+
+ if (val.type == JSON_STRING)
+ mem_deref(val.v.str);
+
+ out:
+ mem_deref(name);
+
+ return err;
+}
+
+
+static int array_entry(unsigned idx, const struct pl *pl_val,
+ json_array_entry_h *aeh, void *arg)
+{
+ struct json_value val;
+ int err;
+
+ err = decode_value(&val, pl_val);
+ if (err)
+ return err;
+
+ if (aeh)
+ err = aeh(idx, &val, arg);
+
+ if (val.type == JSON_STRING)
+ mem_deref(val.v.str);
+
+ return err;
+}
+
+
+static int object_start(const struct pl *pl_name, unsigned idx,
+ struct json_handlers *h)
+{
+ char *name = NULL;
+ int err = 0;
+
+ if (pl_name->p) {
+
+ err = decode_name(&name, pl_name);
+ if (err)
+ return err;
+ }
+
+ if (h->oh)
+ err = h->oh(name, idx, h);
+
+ mem_deref(name);
+
+ return err;
+}
+
+
+static int array_start(const struct pl *pl_name, unsigned idx,
+ struct json_handlers *h)
+{
+ char *name = NULL;
+ int err = 0;
+
+ if (pl_name->p) {
+
+ err = decode_name(&name, pl_name);
+ if (err)
+ return err;
+ }
+
+ if (h->ah)
+ err = h->ah(name, idx, h);
+
+ mem_deref(name);
+
+ return err;
+}
+
+
+static inline int chkval(struct pl *val, const char *p)
+{
+ if (!val->p || p<val->p)
+ return EINVAL;
+
+ val->l = p - val->p;
+
+ return 0;
+}
+
+
+static int _json_decode(const char **str, size_t *len,
+ unsigned depth, unsigned maxdepth,
+ json_object_h *oh, json_array_h *ah,
+ json_object_entry_h *oeh, json_array_entry_h *aeh,
+ void *arg)
+{
+ bool esc = false, inquot = false, inobj = false, inarray = false;
+ struct pl name = PL_INIT, val = PL_INIT;
+ size_t ws = 0;
+ unsigned idx = 0;
+ int err;
+
+ for (; *len>0; ++(*str), --(*len)) {
+
+ if (inquot) {
+ if (esc)
+ esc = false;
+ else if (**str == '\"')
+ inquot = false;
+ else if (**str == '\\')
+ esc = true;
+
+ continue;
+ }
+
+ switch (**str) {
+
+ case ':':
+ if (!inobj || name.p || chkval(&val, *str - ws))
+ return EBADMSG;
+
+ name = val;
+ val = pl_null;
+ break;
+
+ case ',':
+ if (chkval(&val, *str - ws))
+ break;
+
+ if (inobj) {
+
+ if (!name.p)
+ return EBADMSG;
+
+ err = object_entry(&name, &val, oeh, arg);
+ if (err)
+ return err;
+ }
+ else if (inarray) {
+
+ err = array_entry(idx, &val, aeh, arg);
+ if (err)
+ return err;
+
+ ++idx;
+ }
+ else
+ return EBADMSG;
+
+ name = pl_null;
+ val = pl_null;
+ break;
+
+ case '{':
+ if (inobj || inarray) {
+
+ struct json_handlers h = {oh,ah,oeh,aeh,arg};
+
+ if (depth >= maxdepth)
+ return EOVERFLOW;
+
+ if (inobj && !name.p)
+ return EBADMSG;
+
+ err = object_start(&name, idx, &h);
+ if (err)
+ return err;
+
+ name = pl_null;
+
+ err = _json_decode(str, len, depth + 1,
+ maxdepth, h.oh, h.ah,
+ h.oeh, h.aeh, h.arg);
+ if (err)
+ return err;
+
+ if (inarray)
+ ++idx;
+ }
+ else {
+ inobj = true;
+ }
+ break;
+
+ case '[':
+ if (inobj || inarray) {
+
+ struct json_handlers h = {oh,ah,oeh,aeh,arg};
+
+ if (depth >= maxdepth)
+ return EOVERFLOW;
+
+ if (inobj && !name.p)
+ return EBADMSG;
+
+ err = array_start(&name, idx, &h);
+ if (err)
+ return err;
+
+ name = pl_null;
+
+ err = _json_decode(str, len, depth + 1,
+ maxdepth, h.oh, h.ah,
+ h.oeh, h.aeh, h.arg);
+ if (err)
+ return err;
+
+ if (inarray)
+ ++idx;
+ }
+ else {
+ inarray = true;
+ idx = 0;
+ }
+ break;
+
+ case '}':
+ if (!inobj)
+ return EBADMSG;
+
+ if (chkval(&val, *str - ws))
+ return 0;
+
+ if (!name.p)
+ return EBADMSG;
+
+ return object_entry(&name, &val, oeh, arg);
+
+ case ']':
+ if (!inarray)
+ return EBADMSG;
+
+ if (chkval(&val, *str - ws))
+ return 0;
+
+ return array_entry(idx, &val, aeh, arg);
+
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ ++ws;
+ break;
+
+ default:
+ if (val.p)
+ break;
+
+ if (**str == '\"')
+ inquot = true;
+
+ val.p = *str;
+ val.l = 0;
+ ws = 0;
+ break;
+ }
+ }
+
+ if (inobj || inarray)
+ return EBADMSG;
+
+ return 0;
+}
+
+
+int json_decode(const char *str, size_t len, unsigned maxdepth,
+ json_object_h *oh, json_array_h *ah,
+ json_object_entry_h *oeh, json_array_entry_h *aeh, void *arg)
+{
+ if (!str)
+ return EINVAL;
+
+ return _json_decode(&str, &len, 0, maxdepth, oh, ah, oeh, aeh, arg);
+}
diff --git a/src/json/decode_odict.c b/src/json/decode_odict.c
new file mode 100644
index 0000000..cd64aff
--- /dev/null
+++ b/src/json/decode_odict.c
@@ -0,0 +1,125 @@
+/**
+ * @file json/decode_odict.c JSON odict decode
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_odict.h>
+#include <re_json.h>
+
+
+static int container_add(const char *name, unsigned idx,
+ enum odict_type type, struct json_handlers *h)
+{
+ struct odict *o = h->arg, *oc;
+ char index[64];
+ int err;
+
+ if (!name) {
+ if (re_snprintf(index, sizeof(index), "%u", idx) < 0)
+ return ENOMEM;
+
+ name = index;
+ }
+
+ err = odict_alloc(&oc, hash_bsize(o->ht));
+ if (err)
+ return err;
+
+ err = odict_entry_add(o, name, type, oc);
+ mem_deref(oc);
+ h->arg = oc;
+
+ return err;
+}
+
+
+static int object_handler(const char *name, unsigned idx,
+ struct json_handlers *h)
+{
+ return container_add(name, idx, ODICT_OBJECT, h);
+}
+
+
+static int array_handler(const char *name, unsigned idx,
+ struct json_handlers *h)
+{
+ return container_add(name, idx, ODICT_ARRAY, h);
+}
+
+
+static int entry_add(struct odict *o, const char *name,
+ const struct json_value *val)
+{
+ switch (val->type) {
+
+ case JSON_STRING:
+ return odict_entry_add(o, name, ODICT_STRING, val->v.str);
+
+ case JSON_INT:
+ return odict_entry_add(o, name, ODICT_INT, val->v.integer);
+
+ case JSON_DOUBLE:
+ return odict_entry_add(o, name, ODICT_DOUBLE, val->v.dbl);
+
+ case JSON_BOOL:
+ return odict_entry_add(o, name, ODICT_BOOL, val->v.boolean);
+
+ case JSON_NULL:
+ return odict_entry_add(o, name, ODICT_NULL);
+
+ default:
+ return ENOSYS;
+ }
+}
+
+
+static int object_entry_handler(const char *name, const struct json_value *val,
+ void *arg)
+{
+ struct odict *o = arg;
+
+ return entry_add(o, name, val);
+}
+
+
+static int array_entry_handler(unsigned idx, const struct json_value *val,
+ void *arg)
+{
+ struct odict *o = arg;
+ char index[64];
+
+ if (re_snprintf(index, sizeof(index), "%u", idx) < 0)
+ return ENOMEM;
+
+ return entry_add(o, index, val);
+}
+
+
+int json_decode_odict(struct odict **op, uint32_t hash_size, const char *str,
+ size_t len, unsigned maxdepth)
+{
+ struct odict *o;
+ int err;
+
+ if (!op || !str)
+ return EINVAL;
+
+ err = odict_alloc(&o, hash_size);
+ if (err)
+ return err;
+
+ err = json_decode(str, len, maxdepth, object_handler, array_handler,
+ object_entry_handler, array_entry_handler, o);
+ if (err)
+ mem_deref(o);
+ else
+ *op = o;
+
+ return err;
+}
diff --git a/src/json/encode.c b/src/json/encode.c
new file mode 100644
index 0000000..fa56de3
--- /dev/null
+++ b/src/json/encode.c
@@ -0,0 +1,99 @@
+/**
+ * @file json/encode.c JSON encoder
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_odict.h>
+#include <re_json.h>
+
+
+static int encode_entry(struct re_printf *pf, const struct odict_entry *e)
+{
+ struct odict *array;
+ struct le *le;
+ int err;
+
+ if (!e)
+ return 0;
+
+ switch (e->type) {
+
+ case ODICT_OBJECT:
+ err = json_encode_odict(pf, e->u.odict);
+ break;
+
+ case ODICT_ARRAY:
+ array = e->u.odict;
+ if (!array)
+ return 0;
+
+ err = re_hprintf(pf, "[");
+
+ for (le=array->lst.head; le; le=le->next) {
+
+ const struct odict_entry *ae = le->data;
+
+ err |= re_hprintf(pf, "%H%s",
+ encode_entry, ae,
+ le->next ? "," : "");
+ }
+
+ err |= re_hprintf(pf, "]");
+ break;
+
+ case ODICT_INT:
+ err = re_hprintf(pf, "%lld", e->u.integer);
+ break;
+
+ case ODICT_DOUBLE:
+ err = re_hprintf(pf, "%f", e->u.dbl);
+ break;
+
+ case ODICT_STRING:
+ err = re_hprintf(pf, "\"%H\"", utf8_encode, e->u.str);
+ break;
+
+ case ODICT_BOOL:
+ err = re_hprintf(pf, "%s", e->u.boolean ? "true" : "false");
+ break;
+
+ case ODICT_NULL:
+ err = re_hprintf(pf, "null");
+ break;
+
+ default:
+ re_fprintf(stderr, "json: unsupported type %d\n", e->type);
+ err = EINVAL;
+ }
+
+ return err;
+}
+
+
+int json_encode_odict(struct re_printf *pf, const struct odict *o)
+{
+ struct le *le;
+ int err;
+
+ if (!o)
+ return 0;
+
+ err = re_hprintf(pf, "{");
+
+ for (le=o->lst.head; le; le=le->next) {
+
+ const struct odict_entry *e = le->data;
+
+ err |= re_hprintf(pf, "\"%H\":%H%s",
+ utf8_encode, e->key,
+ encode_entry, e,
+ le->next ? "," : "");
+ }
+
+ err |= re_hprintf(pf, "}");
+
+ return err;
+}
diff --git a/src/json/mod.mk b/src/json/mod.mk
new file mode 100644
index 0000000..731ddc5
--- /dev/null
+++ b/src/json/mod.mk
@@ -0,0 +1,9 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 - 2015 Creytiv.com
+#
+
+SRCS += json/decode.c
+SRCS += json/decode_odict.c
+SRCS += json/encode.c
diff --git a/src/list/list.c b/src/list/list.c
new file mode 100644
index 0000000..5be764a
--- /dev/null
+++ b/src/list/list.c
@@ -0,0 +1,361 @@
+/**
+ * @file list.c Linked List implementation
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_list.h>
+#include <re_mem.h>
+
+
+#define DEBUG_MODULE "list"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/**
+ * Initialise a linked list
+ *
+ * @param list Linked list
+ */
+void list_init(struct list *list)
+{
+ if (!list)
+ return;
+
+ list->head = NULL;
+ list->tail = NULL;
+}
+
+
+/**
+ * Flush a linked list and free all elements
+ *
+ * @param list Linked list
+ */
+void list_flush(struct list *list)
+{
+ struct le *le;
+
+ if (!list)
+ return;
+
+ le = list->head;
+ while (le) {
+ struct le *next = le->next;
+ void *data = le->data;
+ le->list = NULL;
+ le->prev = le->next = NULL;
+ le->data = NULL;
+ le = next;
+ mem_deref(data);
+ }
+
+ list_init(list);
+}
+
+
+/**
+ * Clear a linked list without dereferencing the elements
+ *
+ * @param list Linked list
+ */
+void list_clear(struct list *list)
+{
+ struct le *le;
+
+ if (!list)
+ return;
+
+ le = list->head;
+ while (le) {
+ struct le *next = le->next;
+ le->list = NULL;
+ le->prev = le->next = NULL;
+ le->data = NULL;
+ le = next;
+ }
+
+ list_init(list);
+}
+
+
+/**
+ * Append a list element to a linked list
+ *
+ * @param list Linked list
+ * @param le List element
+ * @param data Element data
+ */
+void list_append(struct list *list, struct le *le, void *data)
+{
+ if (!list || !le)
+ return;
+
+ if (le->list) {
+ DEBUG_WARNING("append: le linked to %p\n", le->list);
+ return;
+ }
+
+ le->prev = list->tail;
+ le->next = NULL;
+ le->list = list;
+ le->data = data;
+
+ if (!list->head)
+ list->head = le;
+
+ if (list->tail)
+ list->tail->next = le;
+
+ list->tail = le;
+}
+
+
+/**
+ * Prepend a list element to a linked list
+ *
+ * @param list Linked list
+ * @param le List element
+ * @param data Element data
+ */
+void list_prepend(struct list *list, struct le *le, void *data)
+{
+ if (!list || !le)
+ return;
+
+ if (le->list) {
+ DEBUG_WARNING("prepend: le linked to %p\n", le->list);
+ return;
+ }
+
+ le->prev = NULL;
+ le->next = list->head;
+ le->list = list;
+ le->data = data;
+
+ if (list->head)
+ list->head->prev = le;
+
+ if (!list->tail)
+ list->tail = le;
+
+ list->head = le;
+}
+
+
+/**
+ * Insert a list element before a given list element
+ *
+ * @param list Linked list
+ * @param le Given list element
+ * @param ile List element to insert
+ * @param data Element data
+ */
+void list_insert_before(struct list *list, struct le *le, struct le *ile,
+ void *data)
+{
+ if (!list || !le || !ile)
+ return;
+
+ if (ile->list) {
+ DEBUG_WARNING("insert_before: le linked to %p\n", le->list);
+ return;
+ }
+
+ if (le->prev)
+ le->prev->next = ile;
+ else if (list->head == le)
+ list->head = ile;
+
+ ile->prev = le->prev;
+ ile->next = le;
+ ile->list = list;
+ ile->data = data;
+
+ le->prev = ile;
+}
+
+
+/**
+ * Insert a list element after a given list element
+ *
+ * @param list Linked list
+ * @param le Given list element
+ * @param ile List element to insert
+ * @param data Element data
+ */
+void list_insert_after(struct list *list, struct le *le, struct le *ile,
+ void *data)
+{
+ if (!list || !le || !ile)
+ return;
+
+ if (ile->list) {
+ DEBUG_WARNING("insert_after: le linked to %p\n", le->list);
+ return;
+ }
+
+ if (le->next)
+ le->next->prev = ile;
+ else if (list->tail == le)
+ list->tail = ile;
+
+ ile->prev = le;
+ ile->next = le->next;
+ ile->list = list;
+ ile->data = data;
+
+ le->next = ile;
+}
+
+
+/**
+ * Remove a list element from a linked list
+ *
+ * @param le List element to remove
+ */
+void list_unlink(struct le *le)
+{
+ struct list *list;
+
+ if (!le || !le->list)
+ return;
+
+ list = le->list;
+
+ if (le->prev)
+ le->prev->next = le->next;
+ else
+ list->head = le->next;
+
+ if (le->next)
+ le->next->prev = le->prev;
+ else
+ list->tail = le->prev;
+
+ le->next = NULL;
+ le->prev = NULL;
+ le->list = NULL;
+}
+
+
+/**
+ * Sort a linked list in an order defined by the sort handler
+ *
+ * @param list Linked list
+ * @param sh Sort handler
+ * @param arg Handler argument
+ */
+void list_sort(struct list *list, list_sort_h *sh, void *arg)
+{
+ struct le *le;
+ bool sort;
+
+ if (!list || !sh)
+ return;
+
+ retry:
+ le = list->head;
+ sort = false;
+
+ while (le && le->next) {
+
+ if (sh(le, le->next, arg)) {
+
+ le = le->next;
+ }
+ else {
+ struct le *tle = le->next;
+
+ list_unlink(le);
+ list_insert_after(list, tle, le, le->data);
+ sort = true;
+ }
+ }
+
+ if (sort) {
+ goto retry;
+ }
+}
+
+
+/**
+ * Call the apply handler for each element in a linked list
+ *
+ * @param list Linked list
+ * @param fwd true to traverse from head to tail, false for reverse
+ * @param ah Apply handler
+ * @param arg Handler argument
+ *
+ * @return Current list element if handler returned true
+ */
+struct le *list_apply(const struct list *list, bool fwd,
+ list_apply_h *ah, void *arg)
+{
+ struct le *le;
+
+ if (!list || !ah)
+ return NULL;
+
+ le = fwd ? list->head : list->tail;
+
+ while (le) {
+ struct le *cur = le;
+
+ le = fwd ? le->next : le->prev;
+
+ if (ah(cur, arg))
+ return cur;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get the first element in a linked list
+ *
+ * @param list Linked list
+ *
+ * @return First list element (NULL if empty)
+ */
+struct le *list_head(const struct list *list)
+{
+ return list ? list->head : NULL;
+}
+
+
+/**
+ * Get the last element in a linked list
+ *
+ * @param list Linked list
+ *
+ * @return Last list element (NULL if empty)
+ */
+struct le *list_tail(const struct list *list)
+{
+ return list ? list->tail : NULL;
+}
+
+
+/**
+ * Get the number of elements in a linked list
+ *
+ * @param list Linked list
+ *
+ * @return Number of list elements
+ */
+uint32_t list_count(const struct list *list)
+{
+ uint32_t n = 0;
+ struct le *le;
+
+ if (!list)
+ return 0;
+
+ for (le = list->head; le; le = le->next)
+ ++n;
+
+ return n;
+}
diff --git a/src/list/mod.mk b/src/list/mod.mk
new file mode 100644
index 0000000..fcec450
--- /dev/null
+++ b/src/list/mod.mk
@@ -0,0 +1,7 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += list/list.c
diff --git a/src/lock/lock.c b/src/lock/lock.c
new file mode 100644
index 0000000..1f5f2de
--- /dev/null
+++ b/src/lock/lock.c
@@ -0,0 +1,142 @@
+/**
+ * @file lock/lock.c Pthread mutex locking
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _DEFAULT_SOURCE 1
+#define __USE_UNIX98 1
+#include <pthread.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_lock.h>
+
+
+#define DEBUG_MODULE "lock"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+#ifndef RELEASE
+#define LOCK_DEBUG 0
+#endif
+
+
+/** Defines a lock */
+struct lock {
+ pthread_mutex_t m;
+};
+
+
+static void lock_destructor(void *data)
+{
+ struct lock *l = data;
+
+ int err = pthread_mutex_destroy(&l->m);
+ if (err) {
+ DEBUG_WARNING("pthread_mutex_destroy: %m\n", err);
+ }
+}
+
+
+/**
+ * Allocate a new lock
+ *
+ * @param lp Pointer to allocated lock object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int lock_alloc(struct lock **lp)
+{
+ pthread_mutexattr_t attr;
+ struct lock *l;
+
+ if (!lp)
+ return EINVAL;
+
+ l = mem_zalloc(sizeof(*l), lock_destructor);
+ if (!l)
+ return ENOMEM;
+
+ (void)pthread_mutex_init(&l->m, NULL);
+
+ pthread_mutexattr_init(&attr);
+
+#if LOCK_DEBUG
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
+ DEBUG_NOTICE("init debug lock\n");
+#else
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
+#endif
+ pthread_mutex_init(&l->m, &attr);
+
+ *lp = l;
+ return 0;
+}
+
+
+/**
+ * Get the lock for reading
+ *
+ * @param l Lock object
+ */
+void lock_read_get(struct lock *l)
+{
+ const int err = pthread_mutex_lock(&l->m);
+ if (err) {
+ DEBUG_WARNING("lock_read_get: %m\n", err);
+ }
+}
+
+
+/**
+ * Get the lock for writing
+ *
+ * @param l Lock object
+ */
+void lock_write_get(struct lock *l)
+{
+ const int err = pthread_mutex_lock(&l->m);
+ if (err) {
+ DEBUG_WARNING("lock_write_get: %m\n", err);
+ }
+}
+
+
+/**
+ * Attempt to get a lock for reading
+ *
+ * @param l Lock object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int lock_read_try(struct lock *l)
+{
+ return pthread_mutex_trylock(&l->m);
+}
+
+
+/**
+ * Attempt to get a lock for writing
+ *
+ * @param l Lock object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int lock_write_try(struct lock *l)
+{
+ return pthread_mutex_trylock(&l->m);
+}
+
+
+/**
+ * Release a lock
+ *
+ * @param l Lock object
+ */
+void lock_rel(struct lock *l)
+{
+ const int err = pthread_mutex_unlock(&l->m);
+ if (err) {
+ DEBUG_WARNING("lock_rel: %m\n", err);
+ }
+}
diff --git a/src/lock/mod.mk b/src/lock/mod.mk
new file mode 100644
index 0000000..ffe0e55
--- /dev/null
+++ b/src/lock/mod.mk
@@ -0,0 +1,17 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+ifdef HAVE_PTHREAD_RWLOCK
+SRCS += lock/rwlock.c
+else
+ifdef HAVE_PTHREAD
+SRCS += lock/lock.c
+endif
+endif
+
+ifeq ($(OS),win32)
+SRCS += lock/win32/lock.c
+endif
diff --git a/src/lock/rwlock.c b/src/lock/rwlock.c
new file mode 100644
index 0000000..0f6605f
--- /dev/null
+++ b/src/lock/rwlock.c
@@ -0,0 +1,114 @@
+/**
+ * @file rwlock.c Pthread read/write locking
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _GNU_SOURCE 1
+#include <pthread.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_lock.h>
+
+
+#define DEBUG_MODULE "rwlock"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+struct lock {
+ pthread_rwlock_t lock;
+};
+
+
+static void lock_destructor(void *data)
+{
+ struct lock *l = data;
+
+ int err = pthread_rwlock_destroy(&l->lock);
+ if (err) {
+ DEBUG_WARNING("pthread_rwlock_destroy: %m\n", err);
+ }
+}
+
+
+int lock_alloc(struct lock **lp)
+{
+ struct lock *l;
+ int err;
+
+ if (!lp)
+ return EINVAL;
+
+ l = mem_zalloc(sizeof(*l), lock_destructor);
+ if (!l)
+ return ENOMEM;
+
+ err = pthread_rwlock_init(&l->lock, NULL);
+ if (err)
+ goto out;
+
+ *lp = l;
+
+ out:
+ if (err)
+ mem_deref(l);
+ return err;
+}
+
+
+void lock_read_get(struct lock *l)
+{
+ int err;
+
+ if (!l)
+ return;
+
+ err = pthread_rwlock_rdlock(&l->lock);
+ if (err) {
+ DEBUG_WARNING("lock_read_get: %m\n", err);
+ }
+}
+
+
+void lock_write_get(struct lock *l)
+{
+ int err;
+
+ if (!l)
+ return;
+
+ err = pthread_rwlock_wrlock(&l->lock);
+ if (err) {
+ DEBUG_WARNING("lock_write_get: %m\n", err);
+ }
+}
+
+
+int lock_read_try(struct lock *l)
+{
+ if (!l)
+ return EINVAL;
+ return pthread_rwlock_tryrdlock(&l->lock);
+}
+
+
+int lock_write_try(struct lock *l)
+{
+ if (!l)
+ return EINVAL;
+ return pthread_rwlock_trywrlock(&l->lock);
+}
+
+
+void lock_rel(struct lock *l)
+{
+ int err;
+
+ if (!l)
+ return;
+
+ err = pthread_rwlock_unlock(&l->lock);
+ if (err) {
+ DEBUG_WARNING("lock_rel: %m\n", err);
+ }
+}
diff --git a/src/lock/win32/lock.c b/src/lock/win32/lock.c
new file mode 100644
index 0000000..8fb4362
--- /dev/null
+++ b/src/lock/win32/lock.c
@@ -0,0 +1,72 @@
+/**
+ * @file win32/lock.c Locking for Windows
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0400
+#include <windows.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_lock.h>
+
+
+struct lock {
+ CRITICAL_SECTION c;
+};
+
+
+static void lock_destructor(void *data)
+{
+ struct lock *l = data;
+
+ DeleteCriticalSection(&l->c);
+}
+
+
+int lock_alloc(struct lock **lp)
+{
+ struct lock *l;
+
+ if (!lp)
+ return EINVAL;
+
+ l = mem_alloc(sizeof(*l), lock_destructor);
+ if (!l)
+ return ENOMEM;
+
+ InitializeCriticalSection(&l->c);
+
+ *lp = l;
+ return 0;
+}
+
+
+void lock_read_get(struct lock *l)
+{
+ EnterCriticalSection(&l->c);
+}
+
+
+void lock_write_get(struct lock *l)
+{
+ EnterCriticalSection(&l->c);
+}
+
+
+int lock_read_try(struct lock *l)
+{
+ return TryEnterCriticalSection(&l->c) ? 0 : ENODEV;
+}
+
+
+int lock_write_try(struct lock *l)
+{
+ return TryEnterCriticalSection(&l->c) ? 0 : ENODEV;
+}
+
+
+void lock_rel(struct lock *l)
+{
+ LeaveCriticalSection(&l->c);
+}
diff --git a/src/main/epoll.c b/src/main/epoll.c
new file mode 100644
index 0000000..dfe91fe
--- /dev/null
+++ b/src/main/epoll.c
@@ -0,0 +1,55 @@
+/**
+ * @file epoll.c epoll specific routines
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_sys.h>
+#include "main.h"
+
+
+#define DEBUG_MODULE "epoll"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/**
+ * Check for working epoll() kernel support
+ *
+ * @return true if support, false if not
+ */
+bool epoll_check(void)
+{
+ uint32_t osrel;
+ int err, epfd;
+
+ err = sys_rel_get(&osrel, NULL, NULL, NULL);
+ if (err)
+ return false;
+
+ if (osrel < 0x020542) {
+ DEBUG_INFO("epoll not supported in osrel=0x%08x\n", osrel);
+ return false;
+ }
+
+#ifdef OPENWRT
+ /* epoll is working again with 2.6.25.7 */
+ if (osrel < 0x020619) {
+ DEBUG_NOTICE("epoll is broken in osrel=0x%08x\n", osrel);
+ return false;
+ }
+#endif
+
+ epfd = epoll_create(64);
+ if (-1 == epfd) {
+ DEBUG_NOTICE("epoll_create: %m\n", errno);
+ return false;
+ }
+
+ (void)close(epfd);
+
+ return true;
+}
diff --git a/src/main/init.c b/src/main/init.c
new file mode 100644
index 0000000..3d5290b
--- /dev/null
+++ b/src/main/init.c
@@ -0,0 +1,58 @@
+/**
+ * @file init.c Main initialisation routine
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_net.h>
+#include <re_sys.h>
+#include <re_main.h>
+#include "main.h"
+
+
+/**
+ * Initialise main library
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int libre_init(void)
+{
+ int err;
+
+ rand_init();
+
+#ifdef USE_OPENSSL
+ err = openssl_init();
+ if (err)
+ goto out;
+#endif
+
+ err = net_sock_init();
+ if (err)
+ goto out;
+
+ out:
+ if (err) {
+ net_sock_close();
+#ifdef USE_OPENSSL
+ openssl_close();
+#endif
+ }
+
+ return err;
+}
+
+
+/**
+ * Close library and free up all resources
+ */
+void libre_close(void)
+{
+ (void)fd_setsize(0);
+ net_sock_close();
+#ifdef USE_OPENSSL
+ openssl_close();
+#endif
+}
diff --git a/src/main/main.c b/src/main/main.c
new file mode 100644
index 0000000..b90c139
--- /dev/null
+++ b/src/main/main.c
@@ -0,0 +1,1214 @@
+/**
+ * @file main.c Main polling routine
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <sys/types.h>
+#undef _STRICT_ANSI
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef WIN32
+#include <winsock.h>
+#endif
+#ifdef HAVE_SIGNAL
+#include <signal.h>
+#endif
+#ifdef HAVE_SELECT_H
+#include <sys/select.h>
+#endif
+#ifdef HAVE_POLL
+#include <poll.h>
+#endif
+#ifdef HAVE_EPOLL
+#include <sys/epoll.h>
+#endif
+#ifdef HAVE_KQUEUE
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#undef LIST_INIT
+#undef LIST_FOREACH
+#endif
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_main.h>
+#include "main.h"
+#ifdef HAVE_PTHREAD
+#define __USE_GNU 1
+#include <stdlib.h>
+#include <pthread.h>
+#endif
+
+
+#define DEBUG_MODULE "main"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+/*
+ epoll() has been tested successfully on the following kernels:
+
+ - Linux 2.6.16.29-xen (Debian 4.0 etch)
+ - Linux 2.6.18-4-amd64 (Debian 4.0 etch)
+
+
+ TODO clean this up
+
+ - The polling method is selectable both in compile-time and run-time
+ - The polling method can be changed in run time. this is cool!
+ - Maximum number of fds can be set from application, but only once!
+ - Look at howto optimise main loop
+ */
+
+#if !defined (RELEASE) && !defined (MAIN_DEBUG)
+#define MAIN_DEBUG 1 /**< Enable main loop debugging */
+#endif
+
+
+/** Main loop values */
+enum {
+ MAX_BLOCKING = 100, /**< Maximum time spent in handler in [ms] */
+#if defined (FD_SETSIZE)
+ DEFAULT_MAXFDS = FD_SETSIZE
+#else
+ DEFAULT_MAXFDS = 128
+#endif
+};
+
+
+/** Polling loop data */
+struct re {
+ /** File descriptor handler set */
+ struct {
+ int flags; /**< Polling flags (Read, Write, etc.) */
+ fd_h *fh; /**< Event handler */
+ void *arg; /**< Handler argument */
+ } *fhs;
+ int maxfds; /**< Maximum number of polling fds */
+ int nfds; /**< Number of active file descriptors */
+ enum poll_method method; /**< The current polling method */
+ bool update; /**< File descriptor set need updating */
+ bool polling; /**< Is polling flag */
+ int sig; /**< Last caught signal */
+ struct list tmrl; /**< List of timers */
+
+#ifdef HAVE_POLL
+ struct pollfd *fds; /**< Event set for poll() */
+#endif
+
+#ifdef HAVE_EPOLL
+ struct epoll_event *events; /**< Event set for epoll() */
+ int epfd; /**< epoll control file descriptor */
+#endif
+
+#ifdef HAVE_KQUEUE
+ struct kevent *evlist;
+ int kqfd;
+#endif
+
+#ifdef HAVE_PTHREAD
+ pthread_mutex_t mutex; /**< Mutex for thread synchronization */
+ pthread_mutex_t *mutexp; /**< Pointer to active mutex */
+#endif
+};
+
+static struct re global_re = {
+ NULL,
+ 0,
+ 0,
+ METHOD_NULL,
+ false,
+ false,
+ 0,
+ LIST_INIT,
+#ifdef HAVE_POLL
+ NULL,
+#endif
+#ifdef HAVE_EPOLL
+ NULL,
+ -1,
+#endif
+#ifdef HAVE_KQUEUE
+ NULL,
+ -1,
+#endif
+#ifdef HAVE_PTHREAD
+#if MAIN_DEBUG && defined (PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP)
+ PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP,
+#else
+ PTHREAD_MUTEX_INITIALIZER,
+#endif
+ &global_re.mutex,
+#endif
+};
+
+
+#ifdef HAVE_PTHREAD
+
+static void poll_close(struct re *re);
+
+static pthread_once_t pt_once = PTHREAD_ONCE_INIT;
+static pthread_key_t pt_key;
+
+
+static void thread_destructor(void *arg)
+{
+ poll_close(arg);
+ free(arg);
+}
+
+
+static void re_once(void)
+{
+ pthread_key_create(&pt_key, thread_destructor);
+}
+
+
+static struct re *re_get(void)
+{
+ struct re *re;
+
+ pthread_once(&pt_once, re_once);
+
+ re = pthread_getspecific(pt_key);
+ if (!re) {
+ re = &global_re;
+ }
+
+ return re;
+}
+
+
+static inline void re_lock(struct re *re)
+{
+ int err;
+
+ err = pthread_mutex_lock(re->mutexp);
+ if (err) {
+ DEBUG_WARNING("re_lock: %m\n", err);
+ }
+}
+
+
+static inline void re_unlock(struct re *re)
+{
+ int err;
+
+ err = pthread_mutex_unlock(re->mutexp);
+ if (err) {
+ DEBUG_WARNING("re_unlock: %m\n", err);
+ }
+}
+
+
+#else
+
+static struct re *re_get(void)
+{
+ return &global_re;
+}
+
+#define re_lock(x) /**< Stub */
+#define re_unlock(x) /**< Stub */
+
+#endif
+
+
+#if MAIN_DEBUG
+/**
+ * Call the application event handler
+ *
+ * @param re Poll state
+ * @param fd File descriptor
+ * @param flags Event flags
+ */
+static void fd_handler(struct re *re, int fd, int flags)
+{
+ const uint64_t tick = tmr_jiffies();
+ uint32_t diff;
+
+ DEBUG_INFO("event on fd=%d (flags=0x%02x)...\n", fd, flags);
+
+ re->fhs[fd].fh(flags, re->fhs[fd].arg);
+
+ diff = (uint32_t)(tmr_jiffies() - tick);
+
+ if (diff > MAX_BLOCKING) {
+ DEBUG_WARNING("long async blocking: %u>%u ms (h=%p arg=%p)\n",
+ diff, MAX_BLOCKING,
+ re->fhs[fd].fh, re->fhs[fd].arg);
+ }
+}
+#endif
+
+
+#ifdef HAVE_POLL
+static int set_poll_fds(struct re *re, int fd, int flags)
+{
+ if (!re->fds)
+ return 0;
+
+ if (flags)
+ re->fds[fd].fd = fd;
+ else
+ re->fds[fd].fd = -1;
+
+ re->fds[fd].events = 0;
+ if (flags & FD_READ)
+ re->fds[fd].events |= POLLIN;
+ if (flags & FD_WRITE)
+ re->fds[fd].events |= POLLOUT;
+ if (flags & FD_EXCEPT)
+ re->fds[fd].events |= POLLERR;
+
+ return 0;
+}
+#endif
+
+
+#ifdef HAVE_EPOLL
+static int set_epoll_fds(struct re *re, int fd, int flags)
+{
+ struct epoll_event event;
+ int err = 0;
+
+ if (re->epfd < 0)
+ return EBADFD;
+
+ memset(&event, 0, sizeof(event));
+
+ DEBUG_INFO("set_epoll_fds: fd=%d flags=0x%02x\n", fd, flags);
+
+ if (flags) {
+ event.data.fd = fd;
+
+ if (flags & FD_READ)
+ event.events |= EPOLLIN;
+ if (flags & FD_WRITE)
+ event.events |= EPOLLOUT;
+ if (flags & FD_EXCEPT)
+ event.events |= EPOLLERR;
+
+ /* Try to add it first */
+ if (-1 == epoll_ctl(re->epfd, EPOLL_CTL_ADD, fd, &event)) {
+
+ /* If already exist then modify it */
+ if (EEXIST == errno) {
+
+ if (-1 == epoll_ctl(re->epfd, EPOLL_CTL_MOD,
+ fd, &event)) {
+ err = errno;
+ DEBUG_WARNING("epoll_ctl:"
+ " EPOLL_CTL_MOD:"
+ " fd=%d (%m)\n",
+ fd, err);
+ }
+ }
+ else {
+ err = errno;
+ DEBUG_WARNING("epoll_ctl: EPOLL_CTL_ADD:"
+ " fd=%d (%m)\n",
+ fd, err);
+ }
+ }
+ }
+ else {
+ if (-1 == epoll_ctl(re->epfd, EPOLL_CTL_DEL, fd, &event)) {
+ err = errno;
+ DEBUG_INFO("epoll_ctl: EPOLL_CTL_DEL: fd=%d (%m)\n",
+ fd, err);
+ }
+ }
+
+ return err;
+}
+#endif
+
+
+#ifdef HAVE_KQUEUE
+static int set_kqueue_fds(struct re *re, int fd, int flags)
+{
+ struct kevent kev[2];
+ int r, n = 0;
+
+ memset(kev, 0, sizeof(kev));
+
+ /* always delete the events */
+ EV_SET(&kev[0], fd, EVFILT_READ, EV_DELETE, 0, 0, 0);
+ EV_SET(&kev[1], fd, EVFILT_WRITE, EV_DELETE, 0, 0, 0);
+ kevent(re->kqfd, kev, 2, NULL, 0, NULL);
+
+ memset(kev, 0, sizeof(kev));
+
+ if (flags & FD_WRITE) {
+ EV_SET(&kev[n], fd, EVFILT_WRITE, EV_ADD, 0, 0, 0);
+ ++n;
+ }
+ if (flags & FD_READ) {
+ EV_SET(&kev[n], fd, EVFILT_READ, EV_ADD, 0, 0, 0);
+ ++n;
+ }
+
+ if (n) {
+ r = kevent(re->kqfd, kev, n, NULL, 0, NULL);
+ if (r < 0) {
+ int err = errno;
+
+ DEBUG_WARNING("set: [fd=%d, flags=%x] kevent: %m\n",
+ fd, flags, err);
+ return err;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+
+/**
+ * Rebuild the file descriptor mapping table. This must be done whenever
+ * the polling method is changed.
+ */
+static int rebuild_fds(struct re *re)
+{
+ int i, err = 0;
+
+ DEBUG_INFO("rebuilding fds (nfds=%d)\n", re->nfds);
+
+ /* Update fd sets */
+ for (i=0; i<re->nfds; i++) {
+ if (!re->fhs[i].fh)
+ continue;
+
+ switch (re->method) {
+
+#ifdef HAVE_POLL
+ case METHOD_POLL:
+ err = set_poll_fds(re, i, re->fhs[i].flags);
+ break;
+#endif
+#ifdef HAVE_EPOLL
+ case METHOD_EPOLL:
+ err = set_epoll_fds(re, i, re->fhs[i].flags);
+ break;
+#endif
+
+#ifdef HAVE_KQUEUE
+ case METHOD_KQUEUE:
+ err = set_kqueue_fds(re, i, re->fhs[i].flags);
+ break;
+#endif
+
+ default:
+ break;
+ }
+
+ if (err)
+ break;
+ }
+
+ return err;
+}
+
+
+static int poll_init(struct re *re)
+{
+ DEBUG_INFO("poll init (maxfds=%d)\n", re->maxfds);
+
+ if (!re->maxfds) {
+ DEBUG_WARNING("poll init: maxfds is 0\n");
+ return EINVAL;
+ }
+
+ switch (re->method) {
+
+#ifdef HAVE_POLL
+ case METHOD_POLL:
+ if (!re->fds) {
+ re->fds = mem_zalloc(re->maxfds * sizeof(*re->fds),
+ NULL);
+ if (!re->fds)
+ return ENOMEM;
+ }
+ break;
+#endif
+#ifdef HAVE_EPOLL
+ case METHOD_EPOLL:
+ if (!re->events) {
+ DEBUG_INFO("allocate %u bytes for epoll set\n",
+ re->maxfds * sizeof(*re->events));
+ re->events = mem_zalloc(re->maxfds*sizeof(*re->events),
+ NULL);
+ if (!re->events)
+ return ENOMEM;
+ }
+
+ if (re->epfd < 0
+ && -1 == (re->epfd = epoll_create(re->maxfds))) {
+
+ int err = errno;
+
+ DEBUG_WARNING("epoll_create: %m (maxfds=%d)\n",
+ err, re->maxfds);
+ return err;
+ }
+ DEBUG_INFO("init: epoll_create() epfd=%d\n", re->epfd);
+ break;
+#endif
+
+#ifdef HAVE_KQUEUE
+ case METHOD_KQUEUE:
+
+ if (!re->evlist) {
+ size_t sz = re->maxfds * sizeof(*re->evlist);
+ re->evlist = mem_zalloc(sz, NULL);
+ if (!re->evlist)
+ return ENOMEM;
+ }
+
+ if (re->kqfd < 0) {
+ re->kqfd = kqueue();
+ if (re->kqfd < 0)
+ return errno;
+ DEBUG_INFO("kqueue: fd=%d\n", re->kqfd);
+ }
+
+ break;
+#endif
+
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+/** Free all resources */
+static void poll_close(struct re *re)
+{
+ DEBUG_INFO("poll close\n");
+
+ re->fhs = mem_deref(re->fhs);
+ re->maxfds = 0;
+
+#ifdef HAVE_POLL
+ re->fds = mem_deref(re->fds);
+#endif
+#ifdef HAVE_EPOLL
+ DEBUG_INFO("poll_close: epfd=%d\n", re->epfd);
+
+ if (re->epfd >= 0) {
+ (void)close(re->epfd);
+ re->epfd = -1;
+ }
+
+ re->events = mem_deref(re->events);
+#endif
+
+#ifdef HAVE_KQUEUE
+ if (re->kqfd >= 0) {
+ close(re->kqfd);
+ re->kqfd = -1;
+ }
+
+ re->evlist = mem_deref(re->evlist);
+#endif
+}
+
+
+static int poll_setup(struct re *re)
+{
+ int err;
+
+ err = fd_setsize(DEFAULT_MAXFDS);
+ if (err)
+ goto out;
+
+ if (METHOD_NULL == re->method) {
+ err = poll_method_set(poll_method_best());
+ if (err)
+ goto out;
+
+ DEBUG_INFO("poll setup: poll method not set - set to `%s'\n",
+ poll_method_name(re->method));
+ }
+
+ err = poll_init(re);
+
+ out:
+ if (err)
+ poll_close(re);
+
+ return err;
+}
+
+
+/**
+ * Listen for events on a file descriptor
+ *
+ * @param fd File descriptor
+ * @param flags Wanted event flags
+ * @param fh Event handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int fd_listen(int fd, int flags, fd_h *fh, void *arg)
+{
+ struct re *re = re_get();
+ int err = 0;
+
+ DEBUG_INFO("fd_listen: fd=%d flags=0x%02x\n", fd, flags);
+
+ if (fd < 0) {
+ DEBUG_WARNING("fd_listen: corrupt fd %d\n", fd);
+ return EBADF;
+ }
+
+ if (flags || fh) {
+ err = poll_setup(re);
+ if (err)
+ return err;
+ }
+
+ if (fd >= re->maxfds) {
+ if (flags) {
+ DEBUG_WARNING("fd_listen: fd=%d flags=0x%02x"
+ " - Max %d fds\n",
+ fd, flags, re->maxfds);
+ }
+ return EMFILE;
+ }
+
+ /* Update fh set */
+ if (re->fhs) {
+ re->fhs[fd].flags = flags;
+ re->fhs[fd].fh = fh;
+ re->fhs[fd].arg = arg;
+ }
+
+ re->nfds = max(re->nfds, fd+1);
+
+ switch (re->method) {
+
+#ifdef HAVE_POLL
+ case METHOD_POLL:
+ err = set_poll_fds(re, fd, flags);
+ break;
+#endif
+
+#ifdef HAVE_EPOLL
+ case METHOD_EPOLL:
+ if (re->epfd < 0)
+ return EBADFD;
+ err = set_epoll_fds(re, fd, flags);
+ break;
+#endif
+
+#ifdef HAVE_KQUEUE
+ case METHOD_KQUEUE:
+ err = set_kqueue_fds(re, fd, flags);
+ break;
+#endif
+
+ default:
+ break;
+ }
+
+ if (err) {
+ if (flags && fh) {
+ fd_close(fd);
+ DEBUG_WARNING("fd_listen: fd=%d flags=0x%02x (%m)\n",
+ fd, flags, err);
+ }
+ }
+
+ return err;
+}
+
+
+/**
+ * Stop listening for events on a file descriptor
+ *
+ * @param fd File descriptor
+ */
+void fd_close(int fd)
+{
+ (void)fd_listen(fd, 0, NULL, NULL);
+}
+
+
+/**
+ * Polling loop
+ *
+ * @param re Poll state.
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+static int fd_poll(struct re *re)
+{
+ const uint64_t to = tmr_next_timeout(&re->tmrl);
+ int i, n;
+#ifdef HAVE_SELECT
+ fd_set rfds, wfds, efds;
+#endif
+
+ DEBUG_INFO("next timer: %llu ms\n", to);
+
+ /* Wait for I/O */
+ switch (re->method) {
+
+#ifdef HAVE_POLL
+ case METHOD_POLL:
+ re_unlock(re);
+ n = poll(re->fds, re->nfds, to ? (int)to : -1);
+ re_lock(re);
+ break;
+#endif
+#ifdef HAVE_SELECT
+ case METHOD_SELECT: {
+ struct timeval tv;
+
+ /* Clear and update fd sets */
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ FD_ZERO(&efds);
+
+ for (i=0; i<re->nfds; i++) {
+ if (!re->fhs[i].fh)
+ continue;
+
+ if (re->fhs[i].flags & FD_READ)
+ FD_SET(i, &rfds);
+ if (re->fhs[i].flags & FD_WRITE)
+ FD_SET(i, &wfds);
+ if (re->fhs[i].flags & FD_EXCEPT)
+ FD_SET(i, &efds);
+ }
+
+#ifdef WIN32
+ tv.tv_sec = (long) to / 1000;
+#else
+ tv.tv_sec = (time_t) to / 1000;
+#endif
+ tv.tv_usec = (uint32_t) (to % 1000) * 1000;
+ re_unlock(re);
+ n = select(re->nfds, &rfds, &wfds, &efds, to ? &tv : NULL);
+ re_lock(re);
+ }
+ break;
+#endif
+#ifdef HAVE_EPOLL
+ case METHOD_EPOLL:
+ re_unlock(re);
+ n = epoll_wait(re->epfd, re->events, re->maxfds,
+ to ? (int)to : -1);
+ re_lock(re);
+ break;
+#endif
+
+#ifdef HAVE_KQUEUE
+ case METHOD_KQUEUE: {
+ struct timespec timeout;
+
+ timeout.tv_sec = (time_t) (to / 1000);
+ timeout.tv_nsec = (to % 1000) * 1000000;
+
+ re_unlock(re);
+ n = kevent(re->kqfd, NULL, 0, re->evlist, re->maxfds,
+ to ? &timeout : NULL);
+ re_lock(re);
+ }
+ break;
+#endif
+
+ default:
+ (void)to;
+ DEBUG_WARNING("no polling method set\n");
+ return EINVAL;
+ }
+
+ if (n < 0)
+ return errno;
+
+ /* Check for events */
+ for (i=0; (n > 0) && (i < re->nfds); i++) {
+ int fd, flags = 0;
+
+ switch (re->method) {
+
+#ifdef HAVE_POLL
+ case METHOD_POLL:
+ fd = i;
+ if (re->fds[fd].revents & POLLIN)
+ flags |= FD_READ;
+ if (re->fds[fd].revents & POLLOUT)
+ flags |= FD_WRITE;
+ if (re->fds[fd].revents & (POLLERR|POLLHUP|POLLNVAL))
+ flags |= FD_EXCEPT;
+ if (re->fds[fd].revents & POLLNVAL) {
+ DEBUG_WARNING("event: fd=%d POLLNVAL"
+ " (fds.fd=%d,"
+ " fds.events=0x%02x)\n",
+ fd, re->fds[fd].fd,
+ re->fds[fd].events);
+ }
+ /* Clear events */
+ re->fds[fd].revents = 0;
+ break;
+#endif
+#ifdef HAVE_SELECT
+ case METHOD_SELECT:
+ fd = i;
+ if (FD_ISSET(fd, &rfds))
+ flags |= FD_READ;
+ if (FD_ISSET(fd, &wfds))
+ flags |= FD_WRITE;
+ if (FD_ISSET(fd, &efds))
+ flags |= FD_EXCEPT;
+ break;
+#endif
+#ifdef HAVE_EPOLL
+ case METHOD_EPOLL:
+ fd = re->events[i].data.fd;
+
+ if (re->events[i].events & EPOLLIN)
+ flags |= FD_READ;
+ if (re->events[i].events & EPOLLOUT)
+ flags |= FD_WRITE;
+ if (re->events[i].events & (EPOLLERR|EPOLLHUP))
+ flags |= FD_EXCEPT;
+
+ if (!flags) {
+ DEBUG_WARNING("epoll: no flags fd=%d\n", fd);
+ }
+
+ break;
+#endif
+
+#ifdef HAVE_KQUEUE
+ case METHOD_KQUEUE: {
+
+ struct kevent *kev = &re->evlist[i];
+
+ fd = (int)kev->ident;
+
+ if (fd >= re->maxfds) {
+ DEBUG_WARNING("large fd=%d\n", fd);
+ break;
+ }
+
+ if (kev->filter == EVFILT_READ)
+ flags |= FD_READ;
+ else if (kev->filter == EVFILT_WRITE)
+ flags |= FD_WRITE;
+ else {
+ DEBUG_WARNING("kqueue: unhandled "
+ "filter %x\n",
+ kev->filter);
+ }
+
+ if (kev->flags & EV_EOF) {
+ flags |= FD_EXCEPT;
+ }
+ if (kev->flags & EV_ERROR) {
+ DEBUG_WARNING("kqueue: EV_ERROR on fd %d\n",
+ fd);
+ }
+
+ if (!flags) {
+ DEBUG_WARNING("kqueue: no flags fd=%d\n", fd);
+ }
+ }
+ break;
+#endif
+
+ default:
+ return EINVAL;
+ }
+
+ if (!flags)
+ continue;
+
+ if (re->fhs[fd].fh) {
+#if MAIN_DEBUG
+ fd_handler(re, fd, flags);
+#else
+ re->fhs[fd].fh(flags, re->fhs[fd].arg);
+#endif
+ }
+
+ /* Check if polling method was changed */
+ if (re->update) {
+ re->update = false;
+ return 0;
+ }
+
+ --n;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Set the maximum number of file descriptors
+ *
+ * @param maxfds Max FDs. 0 to free.
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int fd_setsize(int maxfds)
+{
+ struct re *re = re_get();
+
+ if (!maxfds) {
+ fd_debug();
+ poll_close(re);
+ return 0;
+ }
+
+ if (!re->maxfds)
+ re->maxfds = maxfds;
+
+ if (!re->fhs) {
+ DEBUG_INFO("fd_setsize: maxfds=%d, allocating %u bytes\n",
+ re->maxfds, re->maxfds * sizeof(*re->fhs));
+
+ re->fhs = mem_zalloc(re->maxfds * sizeof(*re->fhs), NULL);
+ if (!re->fhs)
+ return ENOMEM;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Print all file descriptors in-use
+ */
+void fd_debug(void)
+{
+ const struct re *re = re_get();
+ int i;
+
+ if (!re->fhs)
+ return;
+
+ for (i=0; i<re->nfds; i++) {
+
+ if (!re->fhs[i].flags)
+ continue;
+
+ (void)re_fprintf(stderr,
+ "fd %d in use: flags=%x fh=%p arg=%p\n",
+ i, re->fhs[i].flags, re->fhs[i].fh,
+ re->fhs[i].arg);
+ }
+}
+
+
+#ifdef HAVE_SIGNAL
+/* Thread-safe signal handling */
+static void signal_handler(int sig)
+{
+ (void)signal(sig, signal_handler);
+ re_get()->sig = sig;
+}
+#endif
+
+
+/**
+ * Main polling loop for async I/O events. This function will only return when
+ * re_cancel() is called or an error occured.
+ *
+ * @param signalh Optional Signal handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int re_main(re_signal_h *signalh)
+{
+ struct re *re = re_get();
+ int err;
+
+#ifdef HAVE_SIGNAL
+ if (signalh) {
+ (void)signal(SIGINT, signal_handler);
+ (void)signal(SIGALRM, signal_handler);
+ (void)signal(SIGTERM, signal_handler);
+ }
+#endif
+
+ if (re->polling) {
+ DEBUG_WARNING("main loop already polling\n");
+ return EALREADY;
+ }
+
+ err = poll_setup(re);
+ if (err)
+ goto out;
+
+ DEBUG_INFO("Using async I/O polling method: `%s'\n",
+ poll_method_name(re->method));
+
+ re->polling = true;
+
+ re_lock(re);
+ for (;;) {
+
+ if (re->sig) {
+ if (signalh)
+ signalh(re->sig);
+
+ re->sig = 0;
+ }
+
+ if (!re->polling) {
+ err = 0;
+ break;
+ }
+
+ err = fd_poll(re);
+ if (err) {
+ if (EINTR == err)
+ continue;
+
+#ifdef DARWIN
+ /* NOTE: workaround for Darwin */
+ if (EBADF == err)
+ continue;
+#endif
+
+ break;
+ }
+
+ tmr_poll(&re->tmrl);
+ }
+ re_unlock(re);
+
+ out:
+ re->polling = false;
+
+ return err;
+}
+
+
+/**
+ * Cancel the main polling loop
+ */
+void re_cancel(void)
+{
+ struct re *re = re_get();
+
+ re->polling = false;
+}
+
+
+/**
+ * Debug the main polling loop
+ *
+ * @param pf Print handler where debug output is printed to
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int re_debug(struct re_printf *pf, void *unused)
+{
+ struct re *re = re_get();
+ int err = 0;
+
+ (void)unused;
+
+ err |= re_hprintf(pf, "re main loop:\n");
+ err |= re_hprintf(pf, " maxfds: %d\n", re->maxfds);
+ err |= re_hprintf(pf, " nfds: %d\n", re->nfds);
+ err |= re_hprintf(pf, " method: %d (%s)\n", re->method,
+ poll_method_name(re->method));
+
+ return err;
+}
+
+
+/**
+ * Set async I/O polling method. This function can also be called while the
+ * program is running.
+ *
+ * @param method New polling method
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int poll_method_set(enum poll_method method)
+{
+ struct re *re = re_get();
+ int err;
+
+ err = fd_setsize(DEFAULT_MAXFDS);
+ if (err)
+ return err;
+
+ switch (method) {
+
+#ifdef HAVE_POLL
+ case METHOD_POLL:
+ break;
+#endif
+#ifdef HAVE_SELECT
+ case METHOD_SELECT:
+ if (re->maxfds > (int)FD_SETSIZE) {
+ DEBUG_WARNING("SELECT: maxfds > FD_SETSIZE\n");
+ return EMFILE;
+ }
+ break;
+#endif
+#ifdef HAVE_EPOLL
+ case METHOD_EPOLL:
+ if (!epoll_check())
+ return EINVAL;
+ break;
+#endif
+#ifdef HAVE_KQUEUE
+ case METHOD_KQUEUE:
+ break;
+#endif
+ default:
+ DEBUG_WARNING("poll method not supported: '%s'\n",
+ poll_method_name(method));
+ return EINVAL;
+ }
+
+ re->method = method;
+ re->update = true;
+
+ DEBUG_INFO("Setting async I/O polling method to `%s'\n",
+ poll_method_name(re->method));
+
+ err = poll_init(re);
+ if (err)
+ return err;
+
+ return rebuild_fds(re);
+}
+
+
+/**
+ * Add a worker thread for this thread
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int re_thread_init(void)
+{
+#ifdef HAVE_PTHREAD
+ struct re *re;
+
+ pthread_once(&pt_once, re_once);
+
+ re = pthread_getspecific(pt_key);
+ if (re) {
+ DEBUG_WARNING("thread_init: already added for thread %d\n",
+ pthread_self());
+ return EALREADY;
+ }
+
+ re = malloc(sizeof(*re));
+ if (!re)
+ return ENOMEM;
+
+ memset(re, 0, sizeof(*re));
+ pthread_mutex_init(&re->mutex, NULL);
+ re->mutexp = &re->mutex;
+
+#ifdef HAVE_EPOLL
+ re->epfd = -1;
+#endif
+
+#ifdef HAVE_KQUEUE
+ re->kqfd = -1;
+#endif
+
+ pthread_setspecific(pt_key, re);
+ return 0;
+#else
+ return ENOSYS;
+#endif
+}
+
+
+/**
+ * Remove the worker thread for this thread
+ */
+void re_thread_close(void)
+{
+#ifdef HAVE_PTHREAD
+ struct re *re;
+
+ pthread_once(&pt_once, re_once);
+
+ re = pthread_getspecific(pt_key);
+ if (re) {
+ poll_close(re);
+ free(re);
+ pthread_setspecific(pt_key, NULL);
+ }
+#endif
+}
+
+
+/**
+ * Enter an 're' thread
+ *
+ * @note Must only be called from a non-re thread
+ */
+void re_thread_enter(void)
+{
+ re_lock(re_get());
+}
+
+
+/**
+ * Leave an 're' thread
+ *
+ * @note Must only be called from a non-re thread
+ */
+void re_thread_leave(void)
+{
+ re_unlock(re_get());
+}
+
+
+/**
+ * Set an external mutex for this thread
+ *
+ * @param mutexp Pointer to external mutex, NULL to use internal
+ */
+void re_set_mutex(void *mutexp)
+{
+#ifdef HAVE_PTHREAD
+ struct re *re = re_get();
+
+ re->mutexp = mutexp ? mutexp : &re->mutex;
+#else
+ (void)mutexp;
+#endif
+}
+
+
+/**
+ * Get the timer-list for this thread
+ *
+ * @return Timer list
+ *
+ * @note only used by tmr module
+ */
+struct list *tmrl_get(void);
+struct list *tmrl_get(void)
+{
+ return &re_get()->tmrl;
+}
diff --git a/src/main/main.h b/src/main/main.h
new file mode 100644
index 0000000..a934c63
--- /dev/null
+++ b/src/main/main.h
@@ -0,0 +1,24 @@
+/**
+ * @file main.h Internal interface to main polling loop
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifdef HAVE_EPOLL
+bool epoll_check(void);
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef USE_OPENSSL
+int openssl_init(void);
+void openssl_close(void);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/main/method.c b/src/main/method.c
new file mode 100644
index 0000000..d4fc1fb
--- /dev/null
+++ b/src/main/method.c
@@ -0,0 +1,102 @@
+/**
+ * @file method.c Polling methods
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_main.h>
+#include "main.h"
+
+
+static const char str_poll[] = "poll"; /**< POSIX.1-2001 poll */
+static const char str_select[] = "select"; /**< POSIX.1-2001 select */
+static const char str_epoll[] = "epoll"; /**< Linux epoll */
+static const char str_kqueue[] = "kqueue";
+
+
+/**
+ * Choose the best async I/O polling method
+ *
+ * @return Polling method
+ */
+enum poll_method poll_method_best(void)
+{
+ enum poll_method m = METHOD_NULL;
+
+#ifdef HAVE_EPOLL
+ /* Supported from Linux 2.5.66 */
+ if (METHOD_NULL == m) {
+ if (epoll_check())
+ m = METHOD_EPOLL;
+ }
+#endif
+
+#ifdef HAVE_KQUEUE
+ if (METHOD_NULL == m) {
+ m = METHOD_KQUEUE;
+ }
+#endif
+
+#ifdef HAVE_POLL
+ if (METHOD_NULL == m) {
+ m = METHOD_POLL;
+ }
+#endif
+#ifdef HAVE_SELECT
+ if (METHOD_NULL == m) {
+ m = METHOD_SELECT;
+ }
+#endif
+
+ return m;
+}
+
+
+/**
+ * Get the name of the polling method
+ *
+ * @param method Polling method
+ *
+ * @return Polling name string
+ */
+const char *poll_method_name(enum poll_method method)
+{
+ switch (method) {
+
+ case METHOD_POLL: return str_poll;
+ case METHOD_SELECT: return str_select;
+ case METHOD_EPOLL: return str_epoll;
+ case METHOD_KQUEUE: return str_kqueue;
+ default: return "???";
+ }
+}
+
+
+/**
+ * Get the polling method type from a string
+ *
+ * @param method Returned polling method
+ * @param name Polling method name string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int poll_method_type(enum poll_method *method, const struct pl *name)
+{
+ if (!method || !name)
+ return EINVAL;
+
+ if (0 == pl_strcasecmp(name, str_poll))
+ *method = METHOD_POLL;
+ else if (0 == pl_strcasecmp(name, str_select))
+ *method = METHOD_SELECT;
+ else if (0 == pl_strcasecmp(name, str_epoll))
+ *method = METHOD_EPOLL;
+ else if (0 == pl_strcasecmp(name, str_kqueue))
+ *method = METHOD_KQUEUE;
+ else
+ return ENOENT;
+
+ return 0;
+}
diff --git a/src/main/mod.mk b/src/main/mod.mk
new file mode 100644
index 0000000..7adffae
--- /dev/null
+++ b/src/main/mod.mk
@@ -0,0 +1,17 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += main/init.c
+SRCS += main/main.c
+SRCS += main/method.c
+
+ifneq ($(HAVE_EPOLL),)
+SRCS += main/epoll.c
+endif
+
+ifneq ($(USE_OPENSSL),)
+SRCS += main/openssl.c
+endif
diff --git a/src/main/openssl.c b/src/main/openssl.c
new file mode 100644
index 0000000..dfeb91e
--- /dev/null
+++ b/src/main/openssl.c
@@ -0,0 +1,165 @@
+/**
+ * @file openssl.c OpenSSL initialisation and multi-threading routines
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#ifdef HAVE_SIGNAL
+#include <signal.h>
+#endif
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+#endif
+#include <openssl/crypto.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <re_types.h>
+#include <re_lock.h>
+#include <re_mem.h>
+#include "main.h"
+
+
+#if defined (HAVE_PTHREAD) && (OPENSSL_VERSION_NUMBER < 0x10100000L)
+
+
+static pthread_mutex_t *lockv;
+
+
+static inline unsigned long threadid(void)
+{
+#if defined (DARWIN) || defined (FREEBSD) || defined (OPENBSD) || \
+ defined (NETBSD) || defined (DRAGONFLY)
+ return (unsigned long)(void *)pthread_self();
+#else
+ return (unsigned long)pthread_self();
+#endif
+}
+
+
+#if OPENSSL_VERSION_NUMBER >= 0x10000000
+static void threadid_handler(CRYPTO_THREADID *id)
+{
+ CRYPTO_THREADID_set_numeric(id, threadid());
+}
+#else
+static unsigned long threadid_handler(void)
+{
+ return threadid();
+}
+#endif
+
+
+static void locking_handler(int mode, int type, const char *file, int line)
+{
+ (void)file;
+ (void)line;
+
+ if (mode & CRYPTO_LOCK)
+ (void)pthread_mutex_lock(&lockv[type]);
+ else
+ (void)pthread_mutex_unlock(&lockv[type]);
+}
+
+
+#endif
+
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+static struct CRYPTO_dynlock_value *dynlock_create_handler(const char *file,
+ int line)
+{
+ struct lock *lock;
+ (void)file;
+ (void)line;
+
+ if (lock_alloc(&lock))
+ return NULL;
+
+ return (struct CRYPTO_dynlock_value *)lock;
+}
+
+
+static void dynlock_lock_handler(int mode, struct CRYPTO_dynlock_value *l,
+ const char *file, int line)
+{
+ struct lock *lock = (struct lock *)l;
+ (void)file;
+ (void)line;
+
+ if (mode & CRYPTO_LOCK)
+ lock_write_get(lock);
+ else
+ lock_rel(lock);
+}
+
+
+static void dynlock_destroy_handler(struct CRYPTO_dynlock_value *l,
+ const char *file, int line)
+{
+ (void)file;
+ (void)line;
+
+ mem_deref(l);
+}
+#endif
+
+
+#ifdef SIGPIPE
+static void sigpipe_handler(int x)
+{
+ (void)x;
+ (void)signal(SIGPIPE, sigpipe_handler);
+}
+#endif
+
+
+int openssl_init(void)
+{
+#if defined (HAVE_PTHREAD) && (OPENSSL_VERSION_NUMBER < 0x10100000L)
+ int err, i;
+
+ lockv = mem_zalloc(sizeof(pthread_mutex_t) * CRYPTO_num_locks(), NULL);
+ if (!lockv)
+ return ENOMEM;
+
+ for (i=0; i<CRYPTO_num_locks(); i++) {
+
+ err = pthread_mutex_init(&lockv[i], NULL);
+ if (err) {
+ lockv = mem_deref(lockv);
+ return err;
+ }
+ }
+
+#if OPENSSL_VERSION_NUMBER >= 0x10000000
+ CRYPTO_THREADID_set_callback(threadid_handler);
+#else
+ CRYPTO_set_id_callback(threadid_handler);
+#endif
+
+ CRYPTO_set_locking_callback(locking_handler);
+#endif
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ CRYPTO_set_dynlock_create_callback(dynlock_create_handler);
+ CRYPTO_set_dynlock_lock_callback(dynlock_lock_handler);
+ CRYPTO_set_dynlock_destroy_callback(dynlock_destroy_handler);
+#endif
+
+#ifdef SIGPIPE
+ (void)signal(SIGPIPE, sigpipe_handler);
+#endif
+
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ return 0;
+}
+
+
+void openssl_close(void)
+{
+ ERR_free_strings();
+#if defined (HAVE_PTHREAD) && (OPENSSL_VERSION_NUMBER < 0x10100000L)
+ lockv = mem_deref(lockv);
+#endif
+}
diff --git a/src/mbuf/mbuf.c b/src/mbuf/mbuf.c
new file mode 100644
index 0000000..6fe47bd
--- /dev/null
+++ b/src/mbuf/mbuf.c
@@ -0,0 +1,608 @@
+/**
+ * @file mbuf.c Memory buffers
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+
+
+#define DEBUG_MODULE "mbuf"
+#define DEBUG_LEVEL 4
+#include <re_dbg.h>
+
+
+enum {DEFAULT_SIZE=512};
+
+
+static void mbuf_destructor(void *data)
+{
+ struct mbuf *mb = data;
+
+ mem_deref(mb->buf);
+}
+
+
+/**
+ * Allocate a new memory buffer
+ *
+ * @param size Initial buffer size
+ *
+ * @return New memory buffer, NULL if no memory
+ */
+struct mbuf *mbuf_alloc(size_t size)
+{
+ struct mbuf *mb;
+
+ mb = mem_zalloc(sizeof(*mb), mbuf_destructor);
+ if (!mb)
+ return NULL;
+
+ if (mbuf_resize(mb, size ? size : DEFAULT_SIZE))
+ return mem_deref(mb);
+
+ return mb;
+}
+
+
+/**
+ * Allocate a new memory buffer with a reference to another mbuf
+ *
+ * @param mbr Memory buffer to reference
+ *
+ * @return New memory buffer, NULL if no memory
+ */
+struct mbuf *mbuf_alloc_ref(struct mbuf *mbr)
+{
+ struct mbuf *mb;
+
+ if (!mbr)
+ return NULL;
+
+ mb = mem_zalloc(sizeof(*mb), mbuf_destructor);
+ if (!mb)
+ return NULL;
+
+ mb->buf = mem_ref(mbr->buf);
+ mb->size = mbr->size;
+ mb->pos = mbr->pos;
+ mb->end = mbr->end;
+
+ return mb;
+}
+
+
+/**
+ * Initialize a memory buffer
+ *
+ * @param mb Memory buffer to initialize
+ */
+void mbuf_init(struct mbuf *mb)
+{
+ if (!mb)
+ return;
+
+ mb->buf = NULL;
+ mb->size = 0;
+ mb->pos = 0;
+ mb->end = 0;
+}
+
+
+/**
+ * Reset a memory buffer
+ *
+ * @param mb Memory buffer to reset
+ */
+void mbuf_reset(struct mbuf *mb)
+{
+ if (!mb)
+ return;
+
+ mb->buf = mem_deref(mb->buf);
+ mbuf_init(mb);
+}
+
+
+/**
+ * Resize a memory buffer
+ *
+ * @param mb Memory buffer to resize
+ * @param size New buffer size
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_resize(struct mbuf *mb, size_t size)
+{
+ uint8_t *buf;
+
+ if (!mb)
+ return EINVAL;
+
+ buf = mb->buf ? mem_realloc(mb->buf, size) : mem_alloc(size, NULL);
+ if (!buf)
+ return ENOMEM;
+
+ mb->buf = buf;
+ mb->size = size;
+
+ return 0;
+}
+
+
+/**
+ * Trim unused trailing bytes in a memory buffer, resize if necessary
+ *
+ * @param mb Memory buffer to trim
+ */
+void mbuf_trim(struct mbuf *mb)
+{
+ int err;
+
+ if (!mb || !mb->end || mb->end == mb->size)
+ return;
+
+ /* We shrink - this cannot fail */
+ err = mbuf_resize(mb, mb->end);
+ if (err) {
+ DEBUG_WARNING("trim: resize failed (%m)\n", err);
+ }
+}
+
+
+/**
+ * Shift mbuf content position
+ *
+ * @param mb Memory buffer to shift
+ * @param shift Shift offset count
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_shift(struct mbuf *mb, ssize_t shift)
+{
+ size_t rsize;
+ uint8_t *p;
+
+ if (!mb)
+ return EINVAL;
+
+ if (((ssize_t)mb->pos + shift) < 0 ||
+ ((ssize_t)mb->end + shift) < 0)
+ return ERANGE;
+
+ rsize = mb->end + shift;
+
+ if (rsize > mb->size) {
+
+ int err;
+
+ err = mbuf_resize(mb, rsize);
+ if (err)
+ return err;
+ }
+
+ p = mbuf_buf(mb);
+
+ memmove(p + shift, p, mbuf_get_left(mb));
+
+ mb->pos += shift;
+ mb->end += shift;
+
+ return 0;
+}
+
+
+/**
+ * Write a block of memory to a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param buf Memory block to write
+ * @param size Number of bytes to write
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_write_mem(struct mbuf *mb, const uint8_t *buf, size_t size)
+{
+ size_t rsize;
+
+ if (!mb || !buf)
+ return EINVAL;
+
+ rsize = mb->pos + size;
+
+ if (rsize > mb->size) {
+ const size_t dsize = mb->size ? (mb->size * 2)
+ : DEFAULT_SIZE;
+ int err;
+
+ err = mbuf_resize(mb, MAX(rsize, dsize));
+ if (err)
+ return err;
+ }
+
+ memcpy(mb->buf + mb->pos, buf, size);
+
+ mb->pos += size;
+ mb->end = MAX(mb->end, mb->pos);
+
+ return 0;
+}
+
+
+/**
+ * Write an 8-bit value to a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param v 8-bit value to write
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_write_u8(struct mbuf *mb, uint8_t v)
+{
+ return mbuf_write_mem(mb, (uint8_t *)&v, sizeof(v));
+}
+
+
+/**
+ * Write a 16-bit value to a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param v 16-bit value to write
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_write_u16(struct mbuf *mb, uint16_t v)
+{
+ return mbuf_write_mem(mb, (uint8_t *)&v, sizeof(v));
+}
+
+
+/**
+ * Write a 32-bit value to a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param v 32-bit value to write
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_write_u32(struct mbuf *mb, uint32_t v)
+{
+ return mbuf_write_mem(mb, (uint8_t *)&v, sizeof(v));
+}
+
+
+/**
+ * Write a 64-bit value to a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param v 64-bit value to write
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_write_u64(struct mbuf *mb, uint64_t v)
+{
+ return mbuf_write_mem(mb, (uint8_t *)&v, sizeof(v));
+}
+
+
+/**
+ * Write a null-terminated string to a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param str Null terminated string to write
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_write_str(struct mbuf *mb, const char *str)
+{
+ if (!str)
+ return EINVAL;
+
+ return mbuf_write_mem(mb, (const uint8_t *)str, strlen(str));
+}
+
+
+/**
+ * Write a pointer-length string to a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param pl Pointer-length string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_write_pl(struct mbuf *mb, const struct pl *pl)
+{
+ if (!pl)
+ return EINVAL;
+
+ return mbuf_write_mem(mb, (const uint8_t *)pl->p, pl->l);
+}
+
+
+/**
+ * Read a block of memory from a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param buf Buffer to read data to
+ * @param size Size of buffer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_read_mem(struct mbuf *mb, uint8_t *buf, size_t size)
+{
+ if (!mb || !buf)
+ return EINVAL;
+
+ if (size > mbuf_get_left(mb)) {
+ DEBUG_WARNING("tried to read beyond mbuf end (%u > %u)\n",
+ size, mbuf_get_left(mb));
+ return EOVERFLOW;
+ }
+
+ memcpy(buf, mb->buf + mb->pos, size);
+
+ mb->pos += size;
+
+ return 0;
+}
+
+
+/**
+ * Read an 8-bit value from a memory buffer
+ *
+ * @param mb Memory buffer
+ *
+ * @return 8-bit value
+ */
+uint8_t mbuf_read_u8(struct mbuf *mb)
+{
+ uint8_t v;
+
+ return (0 == mbuf_read_mem(mb, &v, sizeof(v))) ? v : 0;
+}
+
+
+/**
+ * Read a 16-bit value from a memory buffer
+ *
+ * @param mb Memory buffer
+ *
+ * @return 16-bit value
+ */
+uint16_t mbuf_read_u16(struct mbuf *mb)
+{
+ uint16_t v;
+
+ return (0 == mbuf_read_mem(mb, (uint8_t *)&v, sizeof(v))) ? v : 0;
+}
+
+
+/**
+ * Read a 32-bit value from a memory buffer
+ *
+ * @param mb Memory buffer
+ *
+ * @return 32-bit value
+ */
+uint32_t mbuf_read_u32(struct mbuf *mb)
+{
+ uint32_t v;
+
+ return (0 == mbuf_read_mem(mb, (uint8_t *)&v, sizeof(v))) ? v : 0;
+}
+
+
+/**
+ * Read a 64-bit value from a memory buffer
+ *
+ * @param mb Memory buffer
+ *
+ * @return 64-bit value
+ */
+uint64_t mbuf_read_u64(struct mbuf *mb)
+{
+ uint64_t v;
+
+ return (0 == mbuf_read_mem(mb, (uint8_t *)&v, sizeof(v))) ? v : 0;
+}
+
+
+/**
+ * Read a string from a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param str Buffer to read string to
+ * @param size Size of buffer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_read_str(struct mbuf *mb, char *str, size_t size)
+{
+ if (!mb || !str)
+ return EINVAL;
+
+ while (size--) {
+ const uint8_t c = mbuf_read_u8(mb);
+ *str++ = c;
+ if ('\0' == c)
+ break;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Duplicate a null-terminated string from a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param strp Pointer to destination string; allocated and set
+ * @param len Length of string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_strdup(struct mbuf *mb, char **strp, size_t len)
+{
+ char *str;
+ int err;
+
+ if (!mb || !strp)
+ return EINVAL;
+
+ str = mem_alloc(len + 1, NULL);
+ if (!str)
+ return ENOMEM;
+
+ err = mbuf_read_mem(mb, (uint8_t *)str, len);
+ if (err)
+ goto out;
+
+ str[len] = '\0';
+
+ out:
+ if (err)
+ mem_deref(str);
+ else
+ *strp = str;
+
+ return err;
+}
+
+
+static int vprintf_handler(const char *p, size_t size, void *arg)
+{
+ struct mbuf *mb = arg;
+
+ return mbuf_write_mem(mb, (const uint8_t *)p, size);
+}
+
+
+/**
+ * Print a formatted variable argument list to a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param fmt Formatted string
+ * @param ap Variable argument list
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_vprintf(struct mbuf *mb, const char *fmt, va_list ap)
+{
+ return re_vhprintf(fmt, ap, vprintf_handler, mb);
+}
+
+
+/**
+ * Print a formatted string to a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param fmt Formatted string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_printf(struct mbuf *mb, const char *fmt, ...)
+{
+ int err = 0;
+ va_list ap;
+
+ va_start(ap, fmt);
+ err = re_vhprintf(fmt, ap, vprintf_handler, mb);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Write a pointer-length string to a memory buffer, excluding a section
+ *
+ * @param mb Memory buffer
+ * @param pl Pointer-length string
+ * @param skip Part of pl to exclude
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * @todo: create substf variante
+ */
+int mbuf_write_pl_skip(struct mbuf *mb, const struct pl *pl,
+ const struct pl *skip)
+{
+ struct pl r;
+ int err;
+
+ if (!pl || !skip)
+ return EINVAL;
+
+ if (pl->p > skip->p || (skip->p + skip->l) > (pl->p + pl->l))
+ return ERANGE;
+
+ r.p = pl->p;
+ r.l = skip->p - pl->p;
+
+ err = mbuf_write_mem(mb, (const uint8_t *)r.p, r.l);
+ if (err)
+ return err;
+
+ r.p = skip->p + skip->l;
+ r.l = pl->p + pl->l - r.p;
+
+ return mbuf_write_mem(mb, (const uint8_t *)r.p, r.l);
+}
+
+
+/**
+ * Write n bytes of value 'c' to a memory buffer
+ *
+ * @param mb Memory buffer
+ * @param c Value to write
+ * @param n Number of bytes to write
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_fill(struct mbuf *mb, uint8_t c, size_t n)
+{
+ size_t rsize;
+
+ if (!mb || !n)
+ return EINVAL;
+
+ rsize = mb->pos + n;
+
+ if (rsize > mb->size) {
+ const size_t dsize = mb->size ? (mb->size * 2)
+ : DEFAULT_SIZE;
+ int err;
+
+ err = mbuf_resize(mb, MAX(rsize, dsize));
+ if (err)
+ return err;
+ }
+
+ memset(mb->buf + mb->pos, c, n);
+
+ mb->pos += n;
+ mb->end = MAX(mb->end, mb->pos);
+
+ return 0;
+}
+
+
+/**
+ * Debug the memory buffer
+ *
+ * @param pf Print handler
+ * @param mb Memory buffer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mbuf_debug(struct re_printf *pf, const struct mbuf *mb)
+{
+ if (!mb)
+ return 0;
+
+ return re_hprintf(pf, "buf=%p pos=%zu end=%zu size=%zu",
+ mb->buf, mb->pos, mb->end, mb->size);
+}
diff --git a/src/mbuf/mod.mk b/src/mbuf/mod.mk
new file mode 100644
index 0000000..62f20e2
--- /dev/null
+++ b/src/mbuf/mod.mk
@@ -0,0 +1,7 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += mbuf/mbuf.c
diff --git a/src/md5/md5.c b/src/md5/md5.c
new file mode 100644
index 0000000..cbb0a24
--- /dev/null
+++ b/src/md5/md5.c
@@ -0,0 +1,384 @@
+/*
+ Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved.
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ L. Peter Deutsch
+ ghost@aladdin.com
+
+ */
+/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
+/*
+ Independent implementation of MD5 (RFC 1321).
+
+ This code implements the MD5 Algorithm defined in RFC 1321, whose
+ text is available at
+ http://www.ietf.org/rfc/rfc1321.txt
+ The code is derived from the text of the RFC, including the test suite
+ (section A.5) but excluding the rest of Appendix A. It does not include
+ any code or documentation that is identified in the RFC as being
+ copyrighted.
+
+ The original and principal author of md5.c is L. Peter Deutsch
+ <ghost@aladdin.com>. Other authors are noted in the change history
+ that follows (in reverse chronological order):
+
+ 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
+ either statically or dynamically; added missing #include <string.h>
+ in library.
+ 2002-03-11 lpd Corrected argument list for main(), and added int return
+ type, in test program and T value program.
+ 2002-02-21 lpd Added missing #include <stdio.h> in test program.
+ 2000-07-03 lpd Patched to eliminate warnings about "constant is
+ unsigned in ANSI C, signed in traditional"; made test program
+ self-checking.
+ 1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+ 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
+ 1999-05-03 lpd Original version.
+ */
+
+#include "md5.h"
+#include <string.h>
+
+#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */
+#ifdef ARCH_IS_BIG_ENDIAN
+# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
+#else
+# define BYTE_ORDER 0
+#endif
+
+#define T_MASK ((md5_word_t)~0)
+#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
+#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
+#define T3 0x242070db
+#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
+#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
+#define T6 0x4787c62a
+#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
+#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
+#define T9 0x698098d8
+#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
+#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
+#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
+#define T13 0x6b901122
+#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
+#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
+#define T16 0x49b40821
+#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
+#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
+#define T19 0x265e5a51
+#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
+#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
+#define T22 0x02441453
+#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
+#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
+#define T25 0x21e1cde6
+#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
+#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
+#define T28 0x455a14ed
+#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
+#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
+#define T31 0x676f02d9
+#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
+#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
+#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
+#define T35 0x6d9d6122
+#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
+#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
+#define T38 0x4bdecfa9
+#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
+#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
+#define T41 0x289b7ec6
+#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
+#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
+#define T44 0x04881d05
+#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
+#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
+#define T47 0x1fa27cf8
+#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
+#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
+#define T50 0x432aff97
+#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
+#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
+#define T53 0x655b59c3
+#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
+#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
+#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
+#define T57 0x6fa87e4f
+#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
+#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
+#define T60 0x4e0811a1
+#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
+#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
+#define T63 0x2ad7d2bb
+#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
+
+
+static void
+md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
+{
+ md5_word_t
+ a = pms->abcd[0], b = pms->abcd[1],
+ c = pms->abcd[2], d = pms->abcd[3];
+ md5_word_t t;
+#if BYTE_ORDER > 0
+ /* Define storage only for big-endian CPUs. */
+ md5_word_t X[16];
+#else
+ /* Define storage for little-endian or both types of CPUs. */
+ md5_word_t xbuf[16];
+ const md5_word_t *X;
+#endif
+
+ {
+#if BYTE_ORDER == 0
+ /*
+ * Determine dynamically whether this is a big-endian or
+ * little-endian machine, since we can use a more efficient
+ * algorithm on the latter.
+ */
+ static const int w = 1;
+
+ if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
+#endif
+#if BYTE_ORDER <= 0 /* little-endian */
+ {
+ /*
+ * On little-endian machines, we can process
+ * properly aligned data without copying it.
+ */
+ if (!((data - (const md5_byte_t *)0) & 3)) {
+ /* data are properly aligned */
+ X = (const md5_word_t *)(void *)data;
+ }
+ else {
+ /* not aligned */
+ memcpy(xbuf, data, 64);
+ X = xbuf;
+ }
+ }
+#endif
+#if BYTE_ORDER == 0
+ else /* dynamic big-endian */
+#endif
+#if BYTE_ORDER >= 0 /* big-endian */
+ {
+ /*
+ * On big-endian machines, we must arrange
+ * the bytes in the right order.
+ */
+ const md5_byte_t *xp = data;
+ int i;
+
+# if BYTE_ORDER == 0
+ X = xbuf; /* (dynamic only) */
+# else
+# define xbuf X /* (static only) */
+# endif
+ for (i = 0; i < 16; ++i, xp += 4)
+ xbuf[i] = xp[0] + (xp[1] << 8)
+ + (xp[2] << 16)
+ + (xp[3] << 24);
+ }
+#endif
+ }
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+ /* Round 1. */
+ /* Let [abcd k s i] denote the operation
+ a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define SET(a, b, c, d, k, s, Ti)\
+ t = a + F(b,c,d) + X[k] + Ti; \
+ a = ROTATE_LEFT(t, s) + b
+ /* Do the following 16 operations. */
+ SET(a, b, c, d, 0, 7, T1);
+ SET(d, a, b, c, 1, 12, T2);
+ SET(c, d, a, b, 2, 17, T3);
+ SET(b, c, d, a, 3, 22, T4);
+ SET(a, b, c, d, 4, 7, T5);
+ SET(d, a, b, c, 5, 12, T6);
+ SET(c, d, a, b, 6, 17, T7);
+ SET(b, c, d, a, 7, 22, T8);
+ SET(a, b, c, d, 8, 7, T9);
+ SET(d, a, b, c, 9, 12, T10);
+ SET(c, d, a, b, 10, 17, T11);
+ SET(b, c, d, a, 11, 22, T12);
+ SET(a, b, c, d, 12, 7, T13);
+ SET(d, a, b, c, 13, 12, T14);
+ SET(c, d, a, b, 14, 17, T15);
+ SET(b, c, d, a, 15, 22, T16);
+#undef SET
+
+ /* Round 2. */
+ /* Let [abcd k s i] denote the operation
+ a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+ t = a + G(b,c,d) + X[k] + Ti; \
+ a = ROTATE_LEFT(t, s) + b
+ /* Do the following 16 operations. */
+ SET(a, b, c, d, 1, 5, T17);
+ SET(d, a, b, c, 6, 9, T18);
+ SET(c, d, a, b, 11, 14, T19);
+ SET(b, c, d, a, 0, 20, T20);
+ SET(a, b, c, d, 5, 5, T21);
+ SET(d, a, b, c, 10, 9, T22);
+ SET(c, d, a, b, 15, 14, T23);
+ SET(b, c, d, a, 4, 20, T24);
+ SET(a, b, c, d, 9, 5, T25);
+ SET(d, a, b, c, 14, 9, T26);
+ SET(c, d, a, b, 3, 14, T27);
+ SET(b, c, d, a, 8, 20, T28);
+ SET(a, b, c, d, 13, 5, T29);
+ SET(d, a, b, c, 2, 9, T30);
+ SET(c, d, a, b, 7, 14, T31);
+ SET(b, c, d, a, 12, 20, T32);
+#undef SET
+
+ /* Round 3. */
+ /* Let [abcd k s t] denote the operation
+ a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define SET(a, b, c, d, k, s, Ti)\
+ t = a + H(b,c,d) + X[k] + Ti; \
+ a = ROTATE_LEFT(t, s) + b
+ /* Do the following 16 operations. */
+ SET(a, b, c, d, 5, 4, T33);
+ SET(d, a, b, c, 8, 11, T34);
+ SET(c, d, a, b, 11, 16, T35);
+ SET(b, c, d, a, 14, 23, T36);
+ SET(a, b, c, d, 1, 4, T37);
+ SET(d, a, b, c, 4, 11, T38);
+ SET(c, d, a, b, 7, 16, T39);
+ SET(b, c, d, a, 10, 23, T40);
+ SET(a, b, c, d, 13, 4, T41);
+ SET(d, a, b, c, 0, 11, T42);
+ SET(c, d, a, b, 3, 16, T43);
+ SET(b, c, d, a, 6, 23, T44);
+ SET(a, b, c, d, 9, 4, T45);
+ SET(d, a, b, c, 12, 11, T46);
+ SET(c, d, a, b, 15, 16, T47);
+ SET(b, c, d, a, 2, 23, T48);
+#undef SET
+
+ /* Round 4. */
+ /* Let [abcd k s t] denote the operation
+ a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+ t = a + I(b,c,d) + X[k] + Ti; \
+ a = ROTATE_LEFT(t, s) + b
+ /* Do the following 16 operations. */
+ SET(a, b, c, d, 0, 6, T49);
+ SET(d, a, b, c, 7, 10, T50);
+ SET(c, d, a, b, 14, 15, T51);
+ SET(b, c, d, a, 5, 21, T52);
+ SET(a, b, c, d, 12, 6, T53);
+ SET(d, a, b, c, 3, 10, T54);
+ SET(c, d, a, b, 10, 15, T55);
+ SET(b, c, d, a, 1, 21, T56);
+ SET(a, b, c, d, 8, 6, T57);
+ SET(d, a, b, c, 15, 10, T58);
+ SET(c, d, a, b, 6, 15, T59);
+ SET(b, c, d, a, 13, 21, T60);
+ SET(a, b, c, d, 4, 6, T61);
+ SET(d, a, b, c, 11, 10, T62);
+ SET(c, d, a, b, 2, 15, T63);
+ SET(b, c, d, a, 9, 21, T64);
+#undef SET
+
+ /* Then perform the following additions. (That is increment each
+ of the four registers by the value it had before this block
+ was started.) */
+ pms->abcd[0] += a;
+ pms->abcd[1] += b;
+ pms->abcd[2] += c;
+ pms->abcd[3] += d;
+}
+
+void
+md5_init(md5_state_t *pms)
+{
+ pms->count[0] = pms->count[1] = 0;
+ pms->abcd[0] = 0x67452301;
+ pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
+ pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
+ pms->abcd[3] = 0x10325476;
+}
+
+void
+md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
+{
+ const md5_byte_t *p = data;
+ int left = nbytes;
+ int offset = (pms->count[0] >> 3) & 63;
+ md5_word_t nbits = (md5_word_t)(nbytes << 3);
+
+ if (nbytes <= 0)
+ return;
+
+ /* Update the message length. */
+ pms->count[1] += nbytes >> 29;
+ pms->count[0] += nbits;
+ if (pms->count[0] < nbits)
+ pms->count[1]++;
+
+ /* Process an initial partial block. */
+ if (offset) {
+ int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
+
+ memcpy(pms->buf + offset, p, copy);
+ if (offset + copy < 64)
+ return;
+ p += copy;
+ left -= copy;
+ md5_process(pms, pms->buf);
+ }
+
+ /* Process full blocks. */
+ for (; left >= 64; p += 64, left -= 64)
+ md5_process(pms, p);
+
+ /* Process a final partial block. */
+ if (left)
+ memcpy(pms->buf, p, left);
+}
+
+void
+md5_finish(md5_state_t *pms, md5_byte_t digest[16])
+{
+ static const md5_byte_t pad[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ md5_byte_t data[8];
+ int i;
+
+ /* Save the length before padding. */
+ for (i = 0; i < 8; ++i)
+ data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
+ /* Pad to 56 bytes mod 64. */
+ md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
+ /* Append the length. */
+ md5_append(pms, data, 8);
+ for (i = 0; i < 16; ++i)
+ digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
+}
diff --git a/src/md5/md5.h b/src/md5/md5.h
new file mode 100644
index 0000000..7ffb7b0
--- /dev/null
+++ b/src/md5/md5.h
@@ -0,0 +1,91 @@
+/*
+ Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+ L. Peter Deutsch
+ ghost@aladdin.com
+
+ */
+/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
+/*
+ Independent implementation of MD5 (RFC 1321).
+
+ This code implements the MD5 Algorithm defined in RFC 1321, whose
+ text is available at
+ http://www.ietf.org/rfc/rfc1321.txt
+ The code is derived from the text of the RFC, including the test suite
+ (section A.5) but excluding the rest of Appendix A. It does not include
+ any code or documentation that is identified in the RFC as being
+ copyrighted.
+
+ The original and principal author of md5.h is L. Peter Deutsch
+ <ghost@aladdin.com>. Other authors are noted in the change history
+ that follows (in reverse chronological order):
+
+ 2002-04-13 lpd Removed support for non-ANSI compilers; removed
+ references to Ghostscript; clarified derivation from RFC 1321;
+ now handles byte order either statically or dynamically.
+ 1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+ 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+ added conditionalization for C++ compilation from Martin
+ Purschke <purschke@bnl.gov>.
+ 1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+# define md5_INCLUDED
+
+/*
+ * This package supports both compile-time and run-time determination of CPU
+ * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
+ * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
+ * defined as non-zero, the code will be compiled to run only on big-endian
+ * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
+ * run on either big- or little-endian CPUs, but will run slightly less
+ * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
+ */
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+ md5_word_t count[2]; /* message length in bits, lsw first */
+ md5_word_t abcd[4]; /* digest buffer */
+ md5_byte_t buf[64]; /* accumulate block */
+} md5_state_t;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* Initialize the algorithm. */
+void md5_init(md5_state_t *pms);
+
+/* Append a string to the message. */
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+
+/* Finish the message and return the digest. */
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+
+#ifdef __cplusplus
+} /* end extern "C" */
+#endif
+
+#endif /* md5_INCLUDED */
diff --git a/src/md5/mod.mk b/src/md5/mod.mk
new file mode 100644
index 0000000..891e3b3
--- /dev/null
+++ b/src/md5/mod.mk
@@ -0,0 +1,11 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+ifeq ($(USE_OPENSSL),)
+SRCS += md5/md5.c
+endif
+
+SRCS += md5/wrap.c
diff --git a/src/md5/wrap.c b/src/md5/wrap.c
new file mode 100644
index 0000000..97107e0
--- /dev/null
+++ b/src/md5/wrap.c
@@ -0,0 +1,66 @@
+/**
+ * @file wrap.c MD5 wrappers
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#ifdef USE_OPENSSL
+#include <stddef.h>
+#include <openssl/md5.h>
+#else
+#include "md5.h"
+#endif
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_md5.h>
+
+
+/**
+ * Calculate the MD5 hash from a buffer
+ *
+ * @param d Data buffer (input)
+ * @param n Number of input bytes
+ * @param md Calculated MD5 hash (output)
+ */
+void md5(const uint8_t *d, size_t n, uint8_t *md)
+{
+#ifdef USE_OPENSSL
+ (void)MD5(d, n, md);
+#else
+ md5_state_t state;
+
+ md5_init(&state);
+ md5_append(&state, d, (int)n);
+ md5_finish(&state, md);
+#endif
+}
+
+
+/**
+ * Calculate the MD5 hash from a formatted string
+ *
+ * @param md Calculated MD5 hash
+ * @param fmt Formatted string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int md5_printf(uint8_t *md, const char *fmt, ...)
+{
+ struct mbuf mb;
+ va_list ap;
+ int err;
+
+ mbuf_init(&mb);
+
+ va_start(ap, fmt);
+ err = mbuf_vprintf(&mb, fmt, ap);
+ va_end(ap);
+
+ if (!err)
+ md5(mb.buf, mb.end, md);
+
+ mbuf_reset(&mb);
+
+ return err;
+}
diff --git a/src/mem/mem.c b/src/mem/mem.c
new file mode 100644
index 0000000..1f634c6
--- /dev/null
+++ b/src/mem/mem.c
@@ -0,0 +1,509 @@
+/**
+ * @file mem.c Memory management with reference counting
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+#endif
+#include <re_types.h>
+#include <re_list.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_mem.h>
+
+
+#define DEBUG_MODULE "mem"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+#ifndef RELEASE
+#define MEM_DEBUG 1 /**< Enable memory debugging */
+#endif
+
+
+/** Defines a reference-counting memory object */
+struct mem {
+ uint32_t nrefs; /**< Number of references */
+ mem_destroy_h *dh; /**< Destroy handler */
+#if MEM_DEBUG
+ struct le le; /**< Linked list element */
+ uint32_t magic; /**< Magic number */
+ size_t size; /**< Size of memory object */
+#endif
+};
+
+#if MEM_DEBUG
+/* Memory debugging */
+static struct list meml = LIST_INIT;
+static const uint32_t mem_magic = 0xe7fb9ac4;
+static ssize_t threshold = -1; /**< Memory threshold, disabled by default */
+
+static struct memstat memstat = {
+ 0,0,0,0,~0,0
+};
+
+#ifdef HAVE_PTHREAD
+
+static pthread_mutex_t mem_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static inline void mem_lock(void)
+{
+ pthread_mutex_lock(&mem_mutex);
+}
+
+
+static inline void mem_unlock(void)
+{
+ pthread_mutex_unlock(&mem_mutex);
+}
+
+#else
+
+#define mem_lock() /**< Stub */
+#define mem_unlock() /**< Stub */
+
+#endif
+
+/** Update statistics for mem_zalloc() */
+#define STAT_ALLOC(m, size) \
+ mem_lock(); \
+ memstat.bytes_cur += (size); \
+ memstat.bytes_peak = max(memstat.bytes_cur, memstat.bytes_peak); \
+ ++memstat.blocks_cur; \
+ memstat.blocks_peak = max(memstat.blocks_cur, memstat.blocks_peak); \
+ memstat.size_min = min(memstat.size_min, size); \
+ memstat.size_max = max(memstat.size_max, size); \
+ mem_unlock(); \
+ (m)->size = (size); \
+ (m)->magic = mem_magic;
+
+/** Update statistics for mem_realloc() */
+#define STAT_REALLOC(m, size) \
+ mem_lock(); \
+ memstat.bytes_cur += ((size) - (m)->size); \
+ memstat.bytes_peak = max(memstat.bytes_cur, memstat.bytes_peak); \
+ memstat.size_min = min(memstat.size_min, size); \
+ memstat.size_max = max(memstat.size_max, size); \
+ mem_unlock(); \
+ (m)->size = (size)
+
+/** Update statistics for mem_deref() */
+#define STAT_DEREF(m) \
+ mem_lock(); \
+ memstat.bytes_cur -= (m)->size; \
+ --memstat.blocks_cur; \
+ mem_unlock(); \
+ memset((m), 0xb5, sizeof(struct mem) + (m)->size)
+
+/** Check magic number in memory object */
+#define MAGIC_CHECK(m) \
+ if (mem_magic != (m)->magic) { \
+ DEBUG_WARNING("%s: magic check failed 0x%08x (%p)\n", \
+ __REFUNC__, (m)->magic, (m)+1); \
+ BREAKPOINT; \
+ }
+#else
+#define STAT_ALLOC(m, size)
+#define STAT_REALLOC(m, size)
+#define STAT_DEREF(m)
+#define MAGIC_CHECK(m)
+#endif
+
+
+/**
+ * Allocate a new reference-counted memory object
+ *
+ * @param size Size of memory object
+ * @param dh Optional destructor, called when destroyed
+ *
+ * @return Pointer to allocated object
+ */
+void *mem_alloc(size_t size, mem_destroy_h *dh)
+{
+ struct mem *m;
+
+#if MEM_DEBUG
+ mem_lock();
+ if (-1 != threshold && (memstat.blocks_cur >= (size_t)threshold)) {
+ mem_unlock();
+ return NULL;
+ }
+ mem_unlock();
+#endif
+
+ m = malloc(sizeof(*m) + size);
+ if (!m)
+ return NULL;
+
+#if MEM_DEBUG
+ memset(&m->le, 0, sizeof(struct le));
+ mem_lock();
+ list_append(&meml, &m->le, m);
+ mem_unlock();
+#endif
+
+ m->nrefs = 1;
+ m->dh = dh;
+
+ STAT_ALLOC(m, size);
+
+ return (void *)(m + 1);
+}
+
+
+/**
+ * Allocate a new reference-counted memory object. Memory is zeroed.
+ *
+ * @param size Size of memory object
+ * @param dh Optional destructor, called when destroyed
+ *
+ * @return Pointer to allocated object
+ */
+void *mem_zalloc(size_t size, mem_destroy_h *dh)
+{
+ void *p;
+
+ p = mem_alloc(size, dh);
+ if (!p)
+ return NULL;
+
+ memset(p, 0, size);
+
+ return p;
+}
+
+
+/**
+ * Re-allocate a reference-counted memory object
+ *
+ * @param data Memory object
+ * @param size New size of memory object
+ *
+ * @return New pointer to allocated object
+ *
+ * @note Realloc NULL pointer is not supported
+ */
+void *mem_realloc(void *data, size_t size)
+{
+ struct mem *m, *m2;
+
+ if (!data)
+ return NULL;
+
+ m = ((struct mem *)data) - 1;
+
+ MAGIC_CHECK(m);
+
+#if MEM_DEBUG
+ mem_lock();
+
+ /* Simulate OOM */
+ if (-1 != threshold && size > m->size) {
+ if (memstat.blocks_cur >= (size_t)threshold) {
+ mem_unlock();
+ return NULL;
+ }
+ }
+
+ list_unlink(&m->le);
+ mem_unlock();
+#endif
+
+ m2 = realloc(m, sizeof(*m2) + size);
+
+#if MEM_DEBUG
+ mem_lock();
+ list_append(&meml, m2 ? &m2->le : &m->le, m2 ? m2 : m);
+ mem_unlock();
+#endif
+
+ if (!m2) {
+ return NULL;
+ }
+
+ STAT_REALLOC(m2, size);
+
+ return (void *)(m2 + 1);
+}
+
+
+#ifndef SIZE_MAX
+#define SIZE_MAX (~((size_t)0))
+#endif
+
+
+/**
+ * Re-allocate a reference-counted array
+ *
+ * @param ptr Pointer to existing array, NULL to allocate a new array
+ * @param nmemb Number of members in array
+ * @param membsize Number of bytes in each member
+ * @param dh Optional destructor, only used when ptr is NULL
+ *
+ * @return New pointer to allocated array
+ */
+void *mem_reallocarray(void *ptr, size_t nmemb, size_t membsize,
+ mem_destroy_h *dh)
+{
+ size_t tsize;
+
+ if (membsize && nmemb > SIZE_MAX / membsize) {
+ return NULL;
+ }
+
+ tsize = nmemb * membsize;
+
+ if (ptr) {
+ return mem_realloc(ptr, tsize);
+ }
+ else {
+ return mem_alloc(tsize, dh);
+ }
+}
+
+
+/**
+ * Reference a reference-counted memory object
+ *
+ * @param data Memory object
+ *
+ * @return Memory object (same as data)
+ */
+void *mem_ref(void *data)
+{
+ struct mem *m;
+
+ if (!data)
+ return NULL;
+
+ m = ((struct mem *)data) - 1;
+
+ MAGIC_CHECK(m);
+
+ ++m->nrefs;
+
+ return data;
+}
+
+
+/**
+ * Dereference a reference-counted memory object. When the reference count
+ * is zero, the destroy handler will be called (if present) and the memory
+ * will be freed
+ *
+ * @param data Memory object
+ *
+ * @return Always NULL
+ */
+void *mem_deref(void *data)
+{
+ struct mem *m;
+
+ if (!data)
+ return NULL;
+
+ m = ((struct mem *)data) - 1;
+
+ MAGIC_CHECK(m);
+
+ if (--m->nrefs > 0)
+ return NULL;
+
+ if (m->dh)
+ m->dh(data);
+
+ /* NOTE: check if the destructor called mem_ref() */
+ if (m->nrefs > 0)
+ return NULL;
+
+#if MEM_DEBUG
+ mem_lock();
+ list_unlink(&m->le);
+ mem_unlock();
+#endif
+
+ STAT_DEREF(m);
+
+ free(m);
+
+ return NULL;
+}
+
+
+/**
+ * Get number of references to a reference-counted memory object
+ *
+ * @param data Memory object
+ *
+ * @return Number of references
+ */
+uint32_t mem_nrefs(const void *data)
+{
+ struct mem *m;
+
+ if (!data)
+ return 0;
+
+ m = ((struct mem *)data) - 1;
+
+ MAGIC_CHECK(m);
+
+ return m->nrefs;
+}
+
+
+#if MEM_DEBUG
+static bool debug_handler(struct le *le, void *arg)
+{
+ struct mem *m = le->data;
+ const uint8_t *p = (const uint8_t *)(m + 1);
+ size_t i;
+
+ (void)arg;
+
+ (void)re_fprintf(stderr, " %p: nrefs=%-2u", p, m->nrefs);
+
+ (void)re_fprintf(stderr, " size=%-7u", m->size);
+
+ (void)re_fprintf(stderr, " [");
+
+ for (i=0; i<16; i++) {
+ if (i >= m->size)
+ (void)re_fprintf(stderr, " ");
+ else
+ (void)re_fprintf(stderr, "%02x ", p[i]);
+ }
+
+ (void)re_fprintf(stderr, "] [");
+
+ for (i=0; i<16; i++) {
+ if (i >= m->size)
+ (void)re_fprintf(stderr, " ");
+ else
+ (void)re_fprintf(stderr, "%c",
+ isprint(p[i]) ? p[i] : '.');
+ }
+
+ (void)re_fprintf(stderr, "]");
+
+ MAGIC_CHECK(m);
+
+ (void)re_fprintf(stderr, "\n");
+
+ return false;
+}
+#endif
+
+
+/**
+ * Debug all allocated memory objects
+ */
+void mem_debug(void)
+{
+#if MEM_DEBUG
+ uint32_t n;
+
+ mem_lock();
+ n = list_count(&meml);
+ mem_unlock();
+
+ if (!n)
+ return;
+
+ DEBUG_WARNING("Memory leaks (%u):\n", n);
+
+ mem_lock();
+ (void)list_apply(&meml, true, debug_handler, NULL);
+ mem_unlock();
+#endif
+}
+
+
+/**
+ * Set the memory allocation threshold. This is only used for debugging
+ * and out-of-memory simulation
+ *
+ * @param n Threshold value
+ */
+void mem_threshold_set(ssize_t n)
+{
+#if MEM_DEBUG
+ mem_lock();
+ threshold = n;
+ mem_unlock();
+#else
+ (void)n;
+#endif
+}
+
+
+/**
+ * Print memory status
+ *
+ * @param pf Print handler for debug output
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mem_status(struct re_printf *pf, void *unused)
+{
+#if MEM_DEBUG
+ struct memstat stat;
+ uint32_t c;
+ int err = 0;
+
+ (void)unused;
+
+ mem_lock();
+ memcpy(&stat, &memstat, sizeof(stat));
+ c = list_count(&meml);
+ mem_unlock();
+
+ err |= re_hprintf(pf, "Memory status: (%u bytes overhead pr block)\n",
+ sizeof(struct mem));
+ err |= re_hprintf(pf, " Cur: %u blocks, %u bytes (total %u bytes)\n",
+ stat.blocks_cur, stat.bytes_cur,
+ stat.bytes_cur
+ +(stat.blocks_cur*sizeof(struct mem)));
+ err |= re_hprintf(pf, " Peak: %u blocks, %u bytes (total %u bytes)\n",
+ stat.blocks_peak, stat.bytes_peak,
+ stat.bytes_peak
+ +(stat.blocks_peak*sizeof(struct mem)));
+ err |= re_hprintf(pf, " Block size: min=%u, max=%u\n",
+ stat.size_min, stat.size_max);
+ err |= re_hprintf(pf, " Total %u blocks allocated\n", c);
+
+ return err;
+#else
+ (void)pf;
+ (void)unused;
+ return 0;
+#endif
+}
+
+
+/**
+ * Get memory statistics
+ *
+ * @param mstat Returned memory statistics
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mem_get_stat(struct memstat *mstat)
+{
+ if (!mstat)
+ return EINVAL;
+#if MEM_DEBUG
+ mem_lock();
+ memcpy(mstat, &memstat, sizeof(*mstat));
+ mem_unlock();
+ return 0;
+#else
+ return ENOSYS;
+#endif
+}
diff --git a/src/mem/mod.mk b/src/mem/mod.mk
new file mode 100644
index 0000000..0de616b
--- /dev/null
+++ b/src/mem/mod.mk
@@ -0,0 +1,8 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += mem/mem.c
+SRCS += mem/secure.c
diff --git a/src/mem/secure.c b/src/mem/secure.c
new file mode 100644
index 0000000..6cf64d9
--- /dev/null
+++ b/src/mem/secure.c
@@ -0,0 +1,37 @@
+/**
+ * @file mem/secure.c Secure memory functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re_types.h>
+#include <re_mem.h>
+
+
+/**
+ * Compare two byte strings in constant time. This function can be used
+ * by secure code to compare secret data, such as authentication tags,
+ * to avoid side-channel attacks.
+ *
+ * @param s1 First byte string
+ * @param s2 Second byte string
+ * @param n Number of bytes
+ *
+ * @return a negative number if argument errors
+ * 0 if both byte strings matching
+ * a positive number if not matching
+ */
+int mem_seccmp(const volatile uint8_t *volatile s1,
+ const volatile uint8_t *volatile s2,
+ size_t n)
+{
+ uint8_t val = 0;
+
+ if (!s1 || !s2)
+ return -1;
+
+ while (n--)
+ val |= *s1++ ^ *s2++;
+
+ return val;
+}
diff --git a/src/mod/dl.c b/src/mod/dl.c
new file mode 100644
index 0000000..a449927
--- /dev/null
+++ b/src/mod/dl.c
@@ -0,0 +1,89 @@
+/**
+ * @file dl.c Interface to dynamic linking loader
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <dlfcn.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include "mod_internal.h"
+
+
+#define DEBUG_MODULE "dl"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static const int dl_flag = RTLD_NOW | RTLD_LOCAL;
+
+
+/**
+ * Load a dynamic library file
+ *
+ * @param name Name of library to load
+ *
+ * @return Opaque library handle, NULL if not loaded
+ */
+void *_mod_open(const char *name)
+{
+ void *h;
+
+ if (!name)
+ return NULL;
+
+ h = dlopen(name, dl_flag);
+ if (!h) {
+ DEBUG_WARNING("mod: %s (%s)\n", name, dlerror());
+ return NULL;
+ }
+
+ return h;
+}
+
+
+/**
+ * Resolve address of symbol in dynamic library
+ *
+ * @param h Library handle
+ * @param symbol Name of symbol to resolve
+ *
+ * @return Address, NULL if failure
+ */
+void *_mod_sym(void *h, const char *symbol)
+{
+ void *sym;
+ const char *err;
+
+ if (!h || !symbol)
+ return NULL;
+
+ (void)dlerror(); /* Clear any existing error */
+
+ sym = dlsym(h, symbol);
+ err = dlerror();
+ if (err) {
+ DEBUG_WARNING("dlsym: %s\n", err);
+ return NULL;
+ }
+
+ return sym;
+}
+
+
+/**
+ * Unload a dynamic library
+ *
+ * @param h Library handle
+ */
+void _mod_close(void *h)
+{
+ int err;
+
+ if (!h)
+ return;
+
+ err = dlclose(h);
+ if (0 != err) {
+ DEBUG_WARNING("dlclose: %d\n", err);
+ }
+}
diff --git a/src/mod/mod.c b/src/mod/mod.c
new file mode 100644
index 0000000..6fc0aa1
--- /dev/null
+++ b/src/mod/mod.c
@@ -0,0 +1,245 @@
+/**
+ * @file mod.c Loadable modules
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <sys/types.h>
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_mod.h>
+#include "mod_internal.h"
+
+
+#define DEBUG_MODULE "mod"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Defines a loadable module */
+struct mod {
+ struct le le; /**< Linked list element */
+ void *h; /**< Module handler */
+ const struct mod_export *me; /**< Module exports */
+};
+
+
+static struct list modl; /* struct mod */
+
+
+/**
+ * Initialise module loading
+ */
+void mod_init(void)
+{
+ list_init(&modl);
+}
+
+
+/**
+ * Unload all modules
+ */
+void mod_close(void)
+{
+ list_flush(&modl);
+}
+
+
+static void mod_destructor(void *data)
+{
+ struct mod *m = data;
+ const struct mod_export *me = m->me;
+ int err;
+
+ if (me && me->close && (err = me->close())) {
+ DEBUG_NOTICE("close: error (%m)\n", err);
+ }
+
+ list_unlink(&m->le);
+
+ _mod_close(m->h);
+}
+
+
+/**
+ * Find a module by name in the list of loaded modules
+ *
+ * @param name Name of module to find
+ *
+ * @return Module if found, NULL if not found
+ */
+struct mod *mod_find(const char *name)
+{
+ struct le *le;
+ struct pl x;
+
+ if (!name)
+ return NULL;
+
+ if (re_regex(name, strlen(name), "[/]*[^./]+" MOD_EXT, NULL, &x))
+ return NULL;
+
+ for (le = modl.head; le; le = le->next) {
+ struct mod *m = le->data;
+
+ if (0 == pl_strcasecmp(&x, m->me->name))
+ return m;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Load and initialise a loadable module by name
+ *
+ * @param mp Pointer to allocated module object
+ * @param name Name of loadable module
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mod_load(struct mod **mp, const char *name)
+{
+ struct mod *m;
+ int err = 0;
+
+ if (!mp || !name)
+ return EINVAL;
+
+ /* check if already loaded */
+ m = mod_find(name);
+ if (m) {
+ DEBUG_NOTICE("module already loaded: %s\n", name);
+ return EALREADY;
+ }
+
+ m = mem_zalloc(sizeof(*m), mod_destructor);
+ if (!m)
+ return ENOMEM;
+
+ list_append(&modl, &m->le, m);
+
+ m->h = _mod_open(name);
+ if (!m->h) {
+ err = ENOENT;
+ goto out;
+ }
+
+ m->me = _mod_sym(m->h, "exports");
+ if (!m->me) {
+ err = ELIBBAD;
+ goto out;
+ }
+
+ if (m->me->init && (err = m->me->init()))
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(m);
+ else
+ *mp = m;
+
+ return err;
+}
+
+
+/**
+ * Add and initialise an external module with exports
+ *
+ * @param mp Pointer to allocated module object
+ * @param me Module exports
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mod_add(struct mod **mp, const struct mod_export *me)
+{
+ struct mod *m;
+ int err = 0;
+
+ if (!mp || !me)
+ return EINVAL;
+
+ /* check if already loaded */
+ m = mod_find(me->name);
+ if (m) {
+ DEBUG_NOTICE("module already loaded: %s\n", me->name);
+ return EALREADY;
+ }
+
+ m = mem_zalloc(sizeof(*m), mod_destructor);
+ if (!m)
+ return ENOMEM;
+
+ list_append(&modl, &m->le, m);
+
+ m->me = me;
+
+ if (m->me->init)
+ err = m->me->init();
+
+ if (err)
+ mem_deref(m);
+ else
+ *mp = m;
+
+ return err;
+}
+
+
+/**
+ * Get module export from a loadable module
+ *
+ * @param m Loadable module
+ *
+ * @return Module export
+ */
+const struct mod_export *mod_export(const struct mod *m)
+{
+ return m ? m->me : NULL;
+}
+
+
+/**
+ * Get the list of loaded modules
+ *
+ * @return Module list
+ */
+struct list *mod_list(void)
+{
+ return &modl;
+}
+
+
+/**
+ * Debug loadable modules
+ *
+ * @param pf Print handler for debug output
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mod_debug(struct re_printf *pf, void *unused)
+{
+ struct le *le;
+ int err;
+
+ (void)unused;
+
+ err = re_hprintf(pf, "\n--- Modules (%u) ---\n", list_count(&modl));
+
+ for (le = modl.head; le && !err; le = le->next) {
+ const struct mod *m = le->data;
+ const struct mod_export *me = m->me;
+
+ err = re_hprintf(pf, " %16s type=%-12s ref=%u\n",
+ me->name, me->type, mem_nrefs(m));
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
diff --git a/src/mod/mod.mk b/src/mod/mod.mk
new file mode 100644
index 0000000..f9e50a5
--- /dev/null
+++ b/src/mod/mod.mk
@@ -0,0 +1,16 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += mod/mod.c
+
+# Unix dlopen
+ifdef HAVE_DLFCN_H
+SRCS += mod/dl.c
+endif
+
+ifeq ($(OS),win32)
+SRCS += mod/win32/dll.c
+endif
diff --git a/src/mod/mod_internal.h b/src/mod/mod_internal.h
new file mode 100644
index 0000000..b764a93
--- /dev/null
+++ b/src/mod/mod_internal.h
@@ -0,0 +1,20 @@
+/**
+ * @file mod_internal.h Internal interface to loadable module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+void *_mod_open(const char *name);
+void *_mod_sym(void *h, const char *symbol);
+void _mod_close(void *h);
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/mod/win32/dll.c b/src/mod/win32/dll.c
new file mode 100644
index 0000000..44b4957
--- /dev/null
+++ b/src/mod/win32/dll.c
@@ -0,0 +1,85 @@
+/**
+ * @file dll.c Dynamic library loading for Windows
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <windows.h>
+#include <re_types.h>
+#include "../mod_internal.h"
+
+
+#define DEBUG_MODULE "dll"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/**
+ * Open a DLL file
+ *
+ * @param name Name of DLL to open
+ *
+ * @return Handle (NULL if failed)
+ */
+void *_mod_open(const char *name)
+{
+ HINSTANCE DllHandle = 0;
+
+ DEBUG_INFO("loading %s\n", name);
+
+ DllHandle = LoadLibraryA(name);
+ if (!DllHandle) {
+ DEBUG_WARNING("open: %s LoadLibraryA() failed\n", name);
+ return NULL;
+ }
+
+ return DllHandle;
+}
+
+
+/**
+ * Resolve a symbol address in a DLL
+ *
+ * @param h DLL Handle
+ * @param symbol Symbol to resolve
+ *
+ * @return Address of symbol
+ */
+void *_mod_sym(void *h, const char *symbol)
+{
+ HINSTANCE DllHandle = (HINSTANCE)h;
+ union {
+ FARPROC sym;
+ void *ptr;
+ } u;
+
+ if (!DllHandle)
+ return NULL;
+
+ DEBUG_INFO("get symbol: %s\n", symbol);
+
+ u.sym = GetProcAddress(DllHandle, symbol);
+ if (!u.sym) {
+ DEBUG_WARNING("GetProcAddress: no symbol %s\n", symbol);
+ return NULL;
+ }
+
+ return u.ptr;
+}
+
+
+/**
+ * Close a DLL
+ *
+ * @param h DLL Handle
+ */
+void _mod_close(void *h)
+{
+ HINSTANCE DllHandle = (HINSTANCE)h;
+
+ DEBUG_INFO("unloading %p\n", h);
+
+ if (!DllHandle)
+ return;
+
+ FreeLibrary(DllHandle);
+}
diff --git a/src/mqueue/mod.mk b/src/mqueue/mod.mk
new file mode 100644
index 0000000..6e1d865
--- /dev/null
+++ b/src/mqueue/mod.mk
@@ -0,0 +1,11 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += mqueue/mqueue.c
+
+ifeq ($(OS),win32)
+SRCS += mqueue/win32/pipe.c
+endif
diff --git a/src/mqueue/mqueue.c b/src/mqueue/mqueue.c
new file mode 100644
index 0000000..34dbc40
--- /dev/null
+++ b/src/mqueue/mqueue.c
@@ -0,0 +1,168 @@
+/**
+ * @file mqueue.c Thread Safe Message Queue
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_main.h>
+#include <re_net.h>
+#include <re_mqueue.h>
+#include "mqueue.h"
+
+
+#define MAGIC 0x14553399
+
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#define close closesocket
+#endif
+
+
+/**
+ * Defines a Thread-safe Message Queue
+ *
+ * The Message Queue can be used to communicate between two threads. The
+ * receiving thread must run the re_main() loop which will be woken up on
+ * incoming messages from other threads. The sender thread can be any thread.
+ */
+struct mqueue {
+ int pfd[2];
+ mqueue_h *h;
+ void *arg;
+};
+
+struct msg {
+ void *data;
+ uint32_t magic;
+ int id;
+};
+
+
+static void destructor(void *arg)
+{
+ struct mqueue *q = arg;
+
+ if (q->pfd[0] >= 0) {
+ fd_close(q->pfd[0]);
+ (void)close(q->pfd[0]);
+ }
+ if (q->pfd[1] >= 0)
+ (void)close(q->pfd[1]);
+}
+
+
+static void event_handler(int flags, void *arg)
+{
+ struct mqueue *mq = arg;
+ struct msg msg;
+ ssize_t n;
+
+ if (!(flags & FD_READ))
+ return;
+
+ n = pipe_read(mq->pfd[0], &msg, sizeof(msg));
+ if (n < 0)
+ return;
+
+ if (n != sizeof(msg)) {
+ (void)re_fprintf(stderr, "mqueue: short read of %d bytes\n",
+ n);
+ return;
+ }
+
+ if (msg.magic != MAGIC) {
+ (void)re_fprintf(stderr, "mqueue: bad magic on read (%08x)\n",
+ msg.magic);
+ return;
+ }
+
+ mq->h(msg.id, msg.data, mq->arg);
+}
+
+
+/**
+ * Allocate a new Message Queue
+ *
+ * @param mqp Pointer to allocated Message Queue
+ * @param h Message handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mqueue_alloc(struct mqueue **mqp, mqueue_h *h, void *arg)
+{
+ struct mqueue *mq;
+ int err = 0;
+
+ if (!mqp || !h)
+ return EINVAL;
+
+ mq = mem_zalloc(sizeof(*mq), destructor);
+ if (!mq)
+ return ENOMEM;
+
+ mq->h = h;
+ mq->arg = arg;
+
+ mq->pfd[0] = mq->pfd[1] = -1;
+ if (pipe(mq->pfd) < 0) {
+ err = errno;
+ goto out;
+ }
+
+ err = net_sockopt_blocking_set(mq->pfd[0], false);
+ if (err)
+ goto out;
+
+ err = net_sockopt_blocking_set(mq->pfd[1], false);
+ if (err)
+ goto out;
+
+ err = fd_listen(mq->pfd[0], FD_READ, event_handler, mq);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(mq);
+ else
+ *mqp = mq;
+
+ return err;
+}
+
+
+/**
+ * Push a new message onto the Message Queue
+ *
+ * @param mq Message Queue
+ * @param id General purpose Identifier
+ * @param data Application data
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int mqueue_push(struct mqueue *mq, int id, void *data)
+{
+ struct msg msg;
+ ssize_t n;
+
+ if (!mq)
+ return EINVAL;
+
+ msg.id = id;
+ msg.data = data;
+ msg.magic = MAGIC;
+
+ n = pipe_write(mq->pfd[1], &msg, sizeof(msg));
+ if (n < 0)
+ return errno;
+
+ return (n != sizeof(msg)) ? EPIPE : 0;
+}
diff --git a/src/mqueue/mqueue.h b/src/mqueue/mqueue.h
new file mode 100644
index 0000000..3027419
--- /dev/null
+++ b/src/mqueue/mqueue.h
@@ -0,0 +1,23 @@
+/**
+ * @file mqueue.h Thread Safe Message Queue -- Internal API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+#ifdef WIN32
+int pipe(int fds[2]);
+ssize_t pipe_read(int s, void *buf, size_t len);
+ssize_t pipe_write(int s, const void *buf, size_t len);
+#else
+static inline ssize_t pipe_read(int s, void *buf, size_t len)
+{
+ return read(s, buf, len);
+}
+
+
+static inline ssize_t pipe_write(int s, const void *buf, size_t len)
+{
+ return write(s, buf, len);
+}
+#endif
diff --git a/src/mqueue/win32/pipe.c b/src/mqueue/win32/pipe.c
new file mode 100644
index 0000000..1469e57
--- /dev/null
+++ b/src/mqueue/win32/pipe.c
@@ -0,0 +1,77 @@
+/**
+ * @file pipe.c Pipe-emulation for Windows
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <winsock2.h>
+#include <re_types.h>
+#include "../mqueue.h"
+
+
+/*
+ * Emulate pipe on Windows -- pipe() with select() is not working
+ */
+int pipe(int fds[2])
+{
+ SOCKET s, rd, wr;
+ struct sockaddr_in serv_addr;
+ int len = sizeof(serv_addr);
+
+ if ((s = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
+ return ENOSYS;
+
+ memset((void *) &serv_addr, 0, sizeof(serv_addr));
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_port = htons(0);
+ serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ if (bind(s, (SOCKADDR *) & serv_addr, len) == SOCKET_ERROR)
+ goto error;
+
+ if (listen(s, 1) == SOCKET_ERROR)
+ goto error;
+
+ if (getsockname(s, (SOCKADDR *) &serv_addr, &len) == SOCKET_ERROR)
+ goto error;
+
+ if ((wr = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
+ goto error;
+
+ if (connect(wr, (SOCKADDR *) &serv_addr, len) == SOCKET_ERROR) {
+ closesocket(wr);
+ goto error;
+ }
+
+ rd = accept(s, (SOCKADDR *) &serv_addr, &len);
+ if (rd == INVALID_SOCKET) {
+ closesocket(wr);
+ goto error;
+ }
+
+ fds[0] = rd;
+ fds[1] = wr;
+
+ closesocket(s);
+ return 0;
+
+error:
+ closesocket(s);
+ return ENOSYS;
+}
+
+
+ssize_t pipe_read(int s, void *buf, size_t len)
+{
+ int ret = recv(s, buf, len, 0);
+
+ if (ret < 0 && WSAGetLastError() == WSAECONNRESET)
+ ret = 0;
+
+ return ret;
+}
+
+
+ssize_t pipe_write(int s, const void *buf, size_t len)
+{
+ return send(s, buf, len, 0);
+}
+
diff --git a/src/msg/ctype.c b/src/msg/ctype.c
new file mode 100644
index 0000000..710f2c7
--- /dev/null
+++ b/src/msg/ctype.c
@@ -0,0 +1,62 @@
+/**
+ * @file ctype.c Content-Type decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_msg.h>
+
+
+/**
+ * Decode a pointer-length string into Content-Type header
+ *
+ * @param ctype Content-Type header
+ * @param pl Pointer-length string
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int msg_ctype_decode(struct msg_ctype *ctype, const struct pl *pl)
+{
+ struct pl ws;
+
+ if (!ctype || !pl)
+ return EINVAL;
+
+ if (re_regex(pl->p, pl->l,
+ "[ \t\r\n]*[^ \t\r\n;/]+[ \t\r\n]*/[ \t\r\n]*[^ \t\r\n;]+"
+ "[^]*",
+ &ws, &ctype->type, NULL, NULL, &ctype->subtype,
+ &ctype->params))
+ return EBADMSG;
+
+ if (ws.p != pl->p)
+ return EBADMSG;
+
+ return 0;
+}
+
+
+/**
+ * Compare Content-Type
+ *
+ * @param ctype Content-Type header
+ * @param type Media type
+ * @param subtype Media sub-type
+ *
+ * @return true if match, false if no match
+ */
+bool msg_ctype_cmp(const struct msg_ctype *ctype,
+ const char *type, const char *subtype)
+{
+ if (!ctype || !type || !subtype)
+ return false;
+
+ if (pl_strcasecmp(&ctype->type, type))
+ return false;
+
+ if (pl_strcasecmp(&ctype->subtype, subtype))
+ return false;
+
+ return true;
+}
diff --git a/src/msg/mod.mk b/src/msg/mod.mk
new file mode 100644
index 0000000..8f81b73
--- /dev/null
+++ b/src/msg/mod.mk
@@ -0,0 +1,8 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += msg/ctype.c
+SRCS += msg/param.c
diff --git a/src/msg/param.c b/src/msg/param.c
new file mode 100644
index 0000000..1974b82
--- /dev/null
+++ b/src/msg/param.c
@@ -0,0 +1,70 @@
+/**
+ * @file param.c SIP Parameter decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_msg.h>
+
+
+/**
+ * Check if a parameter exists
+ *
+ * @param pl Pointer-length string
+ * @param name Parameter name
+ * @param val Returned parameter value
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int msg_param_exists(const struct pl *pl, const char *name, struct pl *val)
+{
+ struct pl v1, v2;
+ char xpr[128];
+
+ if (!pl || !name || !val)
+ return EINVAL;
+
+ (void)re_snprintf(xpr, sizeof(xpr), ";[ \t\r\n]*%s[ \t\r\n;=]*", name);
+
+ if (re_regex(pl->p, pl->l, xpr, &v1, &v2))
+ return ENOENT;
+
+ if (!v2.l && v2.p < pl->p + pl->l)
+ return ENOENT;
+
+ val->p = v1.p - 1;
+ val->l = v2.p - val->p;
+
+ return 0;
+}
+
+
+/**
+ * Decode a Parameter
+ *
+ * @param pl Pointer-length string
+ * @param name Parameter name
+ * @param val Returned parameter value
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int msg_param_decode(const struct pl *pl, const char *name, struct pl *val)
+{
+ char expr[128];
+ struct pl v;
+
+ if (!pl || !name || !val)
+ return EINVAL;
+
+ (void)re_snprintf(expr, sizeof(expr),
+ ";[ \t\r\n]*%s[ \t\r\n]*=[ \t\r\n]*[~ \t\r\n;]+",
+ name);
+
+ if (re_regex(pl->p, pl->l, expr, NULL, NULL, NULL, &v))
+ return ENOENT;
+
+ *val = v;
+
+ return 0;
+}
diff --git a/src/natbd/filtering.c b/src/natbd/filtering.c
new file mode 100644
index 0000000..8d0acc5
--- /dev/null
+++ b/src/natbd/filtering.c
@@ -0,0 +1,242 @@
+/**
+ * @file filtering.c NAT Filtering Behaviour Discovery
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include <re_natbd.h>
+
+
+#define DEBUG_MODULE "natbd_filtering"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/* Determining NAT Filtering Behavior
+
+ This will also require at most three tests. These tests should be
+ performed using a port that wasn't used for mapping or other tests as
+ packets sent during those tests may affect results. In test I, the
+ client performs the UDP connectivity test. The server will return
+ its alternate address and port in OTHER-ADDRESS in the binding
+ response. If OTHER-ADDRESS is not returned, the server does not
+ support this usage and this test cannot be run.
+
+ In test II, the client sends a binding request to the primary address
+ of the server with the CHANGE-REQUEST attribute set to change-port
+ and change-IP. This will cause the server to send its response from
+ its alternate IP address and alternate port. If the client receives
+ a response the current behaviour of the NAT is Endpoint-Independent
+ Filtering.
+
+ If no response is received, test III must be performed to distinguish
+ between Address-Dependent Filtering and Address and Port-Dependent
+ Filtering. In test III, the client sends a binding request to the
+ original server address with CHANGE-REQUEST set to change-port. If
+ the client receives a response the current behaviour is Address-
+ Dependent Filtering; if no response is received the current behaviour
+ is Address and Port-Dependent Filtering.
+ */
+
+
+/** Defines a NAT Filtering Behaviour Discovery session */
+struct nat_filtering {
+ struct stun *stun; /**< STUN instance */
+ struct sa srv; /**< Server IP address and port */
+ int test_phase; /**< State machine */
+ nat_filtering_h *fh; /**< Result handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static void stun_response_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct stun_change_req change_req;
+ struct nat_filtering *nf = arg;
+ struct stun_attr *attr;
+ (void)reason;
+
+ if (err == ECONNABORTED) {
+ nf->fh(err, NAT_TYPE_UNKNOWN, nf->arg);
+ return;
+ }
+
+ attr = stun_msg_attr(msg, STUN_ATTR_OTHER_ADDR);
+ if (!err && !attr) {
+ DEBUG_WARNING("no OTHER-ADDRESS in response - abort\n");
+ nf->fh(EINVAL, NAT_TYPE_UNKNOWN, nf->arg);
+ return;
+ }
+
+ switch (nf->test_phase) {
+
+ case 1:
+ /* Test I completed */
+
+ if (err || scode) {
+ DEBUG_WARNING("Test I: stun_response_handler: %m\n",
+ err);
+ nf->fh(err, NAT_TYPE_UNKNOWN, nf->arg);
+ return;
+ }
+
+ /* Start Test II */
+
+ /*
+ In test II, the client sends a binding request to the
+ primary address of the server with the CHANGE-REQUEST
+ attribute set to change-port and change-IP.
+ */
+
+ ++nf->test_phase;
+
+ change_req.ip = true;
+ change_req.port = true;
+
+ err = stun_request(NULL, nf->stun, IPPROTO_UDP, NULL, &nf->srv,
+ 0, STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler, nf, 2,
+ STUN_ATTR_SOFTWARE, stun_software,
+ STUN_ATTR_CHANGE_REQ, &change_req);
+ if (err) {
+ DEBUG_WARNING("stunc_request_send: (%m)\n", err);
+ nf->fh(err, NAT_TYPE_UNKNOWN, nf->arg);
+ }
+ break;
+
+ case 2:
+ /* Test II completed */
+
+ /*
+ If the client receives a response the current behaviour of
+ the NAT is Endpoint Independent Filtering.
+ */
+
+ if (0 == err) {
+ if (!scode) {
+ nf->fh(0, NAT_TYPE_ENDP_INDEP, nf->arg);
+ }
+ else {
+ nf->fh(EINVAL, NAT_TYPE_UNKNOWN, nf->arg);
+ }
+ return;
+ }
+
+ /* Start Test III - the client sends a binding request to the
+ original server address with CHANGE-REQUEST set to
+ change-port
+ */
+ ++nf->test_phase;
+
+ change_req.ip = false;
+ change_req.port = true;
+
+ err = stun_request(NULL, nf->stun, IPPROTO_UDP, NULL, &nf->srv,
+ 0, STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler, nf, 2,
+ STUN_ATTR_SOFTWARE, stun_software,
+ STUN_ATTR_CHANGE_REQ, &change_req);
+ if (err) {
+ DEBUG_WARNING("stunc_request_send: (%m)\n", err);
+ nf->fh(err, NAT_TYPE_UNKNOWN, nf->arg);
+ }
+ break;
+
+ case 3:
+ /* Test III completed */
+ DEBUG_INFO("Test III completed\n");
+
+ if (0 == err && !scode) {
+ nf->fh(0, NAT_TYPE_ADDR_DEP, nf->arg);
+ }
+ else {
+ nf->fh(0, NAT_TYPE_ADDR_PORT_DEP, nf->arg);
+ }
+ break;
+
+ default:
+ DEBUG_WARNING("invalid test phase %d\n", nf->test_phase);
+ nf->fh(EINVAL, NAT_TYPE_UNKNOWN, nf->arg);
+ return;
+ }
+}
+
+
+static void filtering_destructor(void *data)
+{
+ struct nat_filtering *nf = data;
+
+ mem_deref(nf->stun);
+}
+
+
+/**
+ * Allocate a NAT Filtering Behaviour Discovery session
+ *
+ * @param nfp Pointer to allocated NAT filtering object
+ * @param srv STUN Server IP address and port number
+ * @param conf STUN configuration (Optional)
+ * @param fh Filtering result handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_filtering_alloc(struct nat_filtering **nfp, const struct sa *srv,
+ const struct stun_conf *conf,
+ nat_filtering_h *fh, void *arg)
+{
+ struct nat_filtering *nf;
+ int err;
+
+ if (!nfp || !srv || !fh)
+ return EINVAL;
+
+ nf = mem_zalloc(sizeof(*nf), filtering_destructor);
+ if (!nf)
+ return ENOMEM;
+
+ err = stun_alloc(&nf->stun, conf, NULL, NULL);
+ if (err)
+ goto out;
+
+ sa_cpy(&nf->srv, srv);
+
+ nf->fh = fh;
+ nf->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(nf);
+ else
+ *nfp = nf;
+
+ return err;
+}
+
+
+/**
+ * Start a NAT Filtering Behaviour Discovery session
+ *
+ * @param nf NAT filtering object
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_filtering_start(struct nat_filtering *nf)
+{
+ if (!nf)
+ return EINVAL;
+
+ nf->test_phase = 1;
+
+ return stun_request(NULL, nf->stun, IPPROTO_UDP, NULL, &nf->srv, 0,
+ STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler, nf, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
diff --git a/src/natbd/genalg.c b/src/natbd/genalg.c
new file mode 100644
index 0000000..50b3827
--- /dev/null
+++ b/src/natbd/genalg.c
@@ -0,0 +1,152 @@
+/**
+ * @file genalg.c Detecting Generic ALGs
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include <re_natbd.h>
+
+
+#define DEBUG_MODULE "natbd_genalg"
+#define DEBUG_LEVEL 7
+#include <re_dbg.h>
+
+
+/*
+ Detecting Generic ALGs
+
+ A number of NAT boxes are now being deployed into the market which
+ try to provide "generic" ALG functionality. These generic ALGs hunt
+ for IP addresses, either in text or binary form within a packet, and
+ rewrite them if they match a binding. This behavior can be detected
+ because the STUN server returns both the MAPPED-ADDRESS and XOR-
+ MAPPED-ADDRESS in the same response. If the result in the two does
+ not match, there a NAT with a generic ALG in the path.
+ */
+
+
+/** Defines a NAT Generic ALG detection session */
+struct nat_genalg {
+ struct stun *stun; /**< STUN Client */
+ struct sa srv; /**< Server address and port */
+ int proto; /**< IP protocol */
+ nat_genalg_h *h; /**< Result handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static void stun_response_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct stun_attr *xmap, *map;
+ struct nat_genalg *ng = arg;
+ int status = 0;
+ (void)reason;
+
+ if (err) {
+ ng->h(err, 0, NULL, -1, NULL, ng->arg);
+ return;
+ }
+
+ switch (scode) {
+
+ case 0:
+ map = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
+ xmap = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ if (!map || !xmap) {
+ ng->h(EINVAL, scode, reason, -1, NULL, ng->arg);
+ break;
+ }
+
+ status = sa_cmp(&map->v.sa, &xmap->v.sa, SA_ALL) ? -1 : 1;
+
+ ng->h(0, scode, reason, status, &xmap->v.sa, ng->arg);
+ break;
+
+ default:
+ ng->h(0, scode, reason, -1, NULL, ng->arg);
+ break;
+ }
+}
+
+
+static void genalg_destructor(void *data)
+{
+ struct nat_genalg *ng = data;
+
+ mem_deref(ng->stun);
+}
+
+
+/**
+ * Allocate a new NAT Generic ALG detection session
+ *
+ * @param ngp Pointer to allocated NAT Generic ALG object
+ * @param srv STUN Server IP address and port
+ * @param proto Transport protocol
+ * @param conf STUN configuration (Optional)
+ * @param gh Generic ALG handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_genalg_alloc(struct nat_genalg **ngp, const struct sa *srv, int proto,
+ const struct stun_conf *conf,
+ nat_genalg_h *gh, void *arg)
+{
+ struct nat_genalg *ng;
+ int err;
+
+ if (!ngp || !srv || !proto || !gh)
+ return EINVAL;
+
+ ng = mem_zalloc(sizeof(*ng), genalg_destructor);
+ if (!ng)
+ return ENOMEM;
+
+ err = stun_alloc(&ng->stun, conf, NULL, NULL);
+ if (err)
+ goto out;
+
+ sa_cpy(&ng->srv, srv);
+ ng->proto = proto;
+ ng->h = gh;
+ ng->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(ng);
+ else
+ *ngp = ng;
+
+ return err;
+}
+
+
+/**
+ * Start the NAT Generic ALG detection
+ *
+ * @param ng NAT Generic ALG object
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_genalg_start(struct nat_genalg *ng)
+{
+ int err;
+
+ if (!ng)
+ return EINVAL;
+
+ err = stun_request(NULL, ng->stun, ng->proto, NULL, &ng->srv, 0,
+ STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler, ng, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+
+ return err;
+}
diff --git a/src/natbd/hairpinning.c b/src/natbd/hairpinning.c
new file mode 100644
index 0000000..396d50e
--- /dev/null
+++ b/src/natbd/hairpinning.c
@@ -0,0 +1,363 @@
+/**
+ * @file hairpinning.c NAT Hairpinning Behaviour discovery
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_mem.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include <re_natbd.h>
+
+
+#define DEBUG_MODULE "natbd_hairpinning"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/*
+ Diagnosing NAT Hairpinning
+
+ STUN Binding Requests allow a a client to determine whether it is
+ behind a NAT that support hairpinning of datagrams. To perform this
+ test, the client first sends a Binding Request to its STUN server to
+ determine its mapped address. The client then sends a STUN Binding
+ Request to this mapped address from a different port. If the client
+ receives its own request, the NAT hairpins datagrams. This test
+ applies to UDP, TCP, or TCP/TLS connections.
+
+ */
+
+
+/** Defines NAT Hairpinning Behaviour Discovery */
+struct nat_hairpinning {
+ struct stun *stun; /**< STUN Client */
+ int proto; /**< IP Protocol */
+ struct sa srv; /**< Server address and port */
+ struct udp_sock *us; /**< UDP socket */
+ struct tcp_conn *tc; /**< Client TCP Connection */
+ struct tcp_sock *ts; /**< Server TCP Socket */
+ struct tcp_conn *tc2; /**< Server TCP Connection */
+ nat_hairpinning_h *hph; /**< Result handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static void hairpinning_destructor(void *data)
+{
+ struct nat_hairpinning *nh = data;
+
+ mem_deref(nh->us);
+ mem_deref(nh->tc);
+ mem_deref(nh->ts);
+ mem_deref(nh->tc2);
+ mem_deref(nh->stun);
+}
+
+
+static void msg_recv(struct nat_hairpinning *nh, int proto, void *sock,
+ const struct sa *src, struct mbuf *mb)
+{
+ struct stun_unknown_attr ua;
+ struct stun_msg *msg;
+
+ if (0 != stun_msg_decode(&msg, mb, &ua))
+ return;
+
+ switch (stun_msg_class(msg)) {
+
+ case STUN_CLASS_REQUEST:
+ (void)stun_reply(proto, sock, src, 0, msg, NULL, 0, false, 3,
+ STUN_ATTR_XOR_MAPPED_ADDR, src,
+ STUN_ATTR_MAPPED_ADDR, src,
+ STUN_ATTR_SOFTWARE, stun_software);
+ break;
+
+ case STUN_CLASS_ERROR_RESP:
+ case STUN_CLASS_SUCCESS_RESP:
+ (void)stun_ctrans_recv(nh->stun, msg, &ua);
+ break;
+
+ default:
+ DEBUG_WARNING("unknown class 0x%04x\n", stun_msg_class(msg));
+ break;
+ }
+
+ mem_deref(msg);
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb,
+ void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+
+ msg_recv(nh, IPPROTO_UDP, nh->us, src, mb);
+}
+
+
+static void stun_response_handler2(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+ (void)reason;
+ (void)msg;
+
+ if (err || scode) {
+ nh->hph(0, false, nh->arg);
+ return;
+ }
+
+ /* Hairpinning supported */
+ nh->hph(0, true, nh->arg);
+}
+
+
+static int hairpin_send(struct nat_hairpinning *nh, const struct sa *srv)
+{
+ return stun_request(NULL, nh->stun, nh->proto, NULL,
+ srv, 0, STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler2, nh, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+/*
+ * TCP Connections: STUN Client2 to Embedded STUN Server
+ */
+
+
+static void tcp_recv_handler2(struct mbuf *mb, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+
+ msg_recv(nh, IPPROTO_TCP, nh->tc2, NULL, mb);
+}
+
+
+static void tcp_close_handler2(int err, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+
+ if (err)
+ nh->hph(err, false, nh->arg);
+}
+
+
+static void stun_response_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+ const struct stun_attr *attr;
+ (void)reason;
+
+ if (err) {
+ nh->hph(err, false, nh->arg);
+ return;
+ }
+
+ attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ if (!attr)
+ attr = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
+
+ if (scode || !attr) {
+ nh->hph(EBADMSG, false, nh->arg);
+ return;
+ }
+
+ /* Send hairpinning test message */
+ err = hairpin_send(nh, &attr->v.sa);
+ if (err) {
+ DEBUG_WARNING("hairpin_send: (%m)\n", err);
+ }
+
+ if (err)
+ nh->hph(err, false, nh->arg);
+}
+
+
+static int mapped_send(struct nat_hairpinning *nh)
+{
+ return stun_request(NULL, nh->stun, nh->proto, nh->us ?
+ (void *)nh->us : (void *)nh->tc,
+ &nh->srv, 0, STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler, nh, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+static void tcp_conn_handler(const struct sa *peer, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+ int err;
+
+ (void)peer;
+
+ err = tcp_accept(&nh->tc2, nh->ts, NULL, tcp_recv_handler2,
+ tcp_close_handler2, nh);
+ if (err) {
+ DEBUG_WARNING("TCP conn: tcp_accept: %m\n", err);
+ }
+}
+
+
+/*
+ * TCP Connection: STUN Client to STUN Server
+ */
+
+static void tcp_estab_handler(void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+ int err;
+
+ err = mapped_send(nh);
+ if (err) {
+ DEBUG_WARNING("TCP established: mapped_send (%m)\n", err);
+ nh->hph(err, false, nh->arg);
+ }
+}
+
+
+static void tcp_recv_handler(struct mbuf *mb, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+ int err;
+
+ err = stun_recv(nh->stun, mb);
+ if (err && ENOENT != err) {
+ DEBUG_WARNING("stun recv: %m\n", err);
+ }
+}
+
+
+static void tcp_close_handler(int err, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+
+ if (err)
+ nh->hph(err, false, nh->arg);
+}
+
+
+/**
+ * Allocate a new NAT Hairpinning discovery session
+ *
+ * @param nhp Pointer to allocated NAT Hairpinning object
+ * @param proto Transport protocol
+ * @param srv STUN Server IP address and port number
+ * @param proto Transport protocol
+ * @param conf STUN configuration (Optional)
+ * @param hph Hairpinning result handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_hairpinning_alloc(struct nat_hairpinning **nhp,
+ const struct sa *srv, int proto,
+ const struct stun_conf *conf,
+ nat_hairpinning_h *hph, void *arg)
+{
+ struct nat_hairpinning *nh;
+ struct sa local;
+ int err;
+
+ if (!srv || !hph)
+ return EINVAL;
+
+ nh = mem_zalloc(sizeof(*nh), hairpinning_destructor);
+ if (!nh)
+ return ENOMEM;
+
+ err = stun_alloc(&nh->stun, conf, NULL, NULL);
+ if (err)
+ goto out;
+
+ sa_cpy(&nh->srv, srv);
+ nh->proto = proto;
+ nh->hph = hph;
+ nh->arg = arg;
+
+ switch (proto) {
+
+ case IPPROTO_UDP:
+ err = udp_listen(&nh->us, NULL, udp_recv_handler, nh);
+ break;
+
+ case IPPROTO_TCP:
+ sa_set_in(&local, 0, 0);
+
+ /*
+ * Part I - Allocate and bind all sockets
+ */
+ err = tcp_sock_alloc(&nh->ts, &local, tcp_conn_handler, nh);
+ if (err)
+ break;
+
+ err = tcp_conn_alloc(&nh->tc, &nh->srv,
+ tcp_estab_handler, tcp_recv_handler,
+ tcp_close_handler, nh);
+ if (err)
+ break;
+
+ err = tcp_sock_bind(nh->ts, &local);
+ if (err)
+ break;
+
+ err = tcp_sock_local_get(nh->ts, &local);
+ if (err)
+ break;
+
+ err = tcp_conn_bind(nh->tc, &local);
+ if (err)
+ break;
+
+ /*
+ * Part II - Listen and connect all sockets
+ */
+ err = tcp_sock_listen(nh->ts, 5);
+ break;
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ out:
+ if (err)
+ mem_deref(nh);
+ else
+ *nhp = nh;
+
+ return err;
+}
+
+
+/**
+ * Start a new NAT Hairpinning discovery session
+ *
+ * @param nh NAT Hairpinning object
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_hairpinning_start(struct nat_hairpinning *nh)
+{
+ if (!nh)
+ return EINVAL;
+
+ switch (nh->proto) {
+
+ case IPPROTO_UDP:
+ return mapped_send(nh);
+
+ case IPPROTO_TCP:
+ return tcp_conn_connect(nh->tc, &nh->srv);
+
+ default:
+ return EPROTONOSUPPORT;
+ }
+}
diff --git a/src/natbd/lifetime.c b/src/natbd/lifetime.c
new file mode 100644
index 0000000..0bbffb9
--- /dev/null
+++ b/src/natbd/lifetime.c
@@ -0,0 +1,332 @@
+/**
+ * @file lifetime.c NAT Binding Lifetime Discovery
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_natbd.h>
+
+
+#define DEBUG_MODULE "natbd_lifetime"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/* Binding Lifetime Discovery
+
+ STUN can also be used to probe the lifetimes of the bindings created
+ by the NAT. For many NAT devices, an absolute refresh interval
+ cannot be determined; bindings might be closed quicker under heavy
+ load or might not behave as the tests suggest. For this reason
+ applications that require reliable bindings must send keep-alives as
+ frequently as required by all NAT devices that will be encountered.
+ */
+
+
+/** Defines a NAT Binding Lifetime Discovery session */
+struct nat_lifetime {
+ struct stun *stun; /**< STUN Client */
+ struct stun_ctrans *ctx; /**< STUN Transaction 1 */
+ struct stun_ctrans *cty; /**< STUN Transaction 2 */
+ struct udp_sock *us_x; /**< First UDP socket */
+ struct udp_sock *us_y; /**< Second UDP socket */
+ struct sa srv; /**< Server IP-address/port */
+ struct sa map; /**< Mapped IP address/port */
+ struct tmr tmr; /**< Refresh timer */
+ bool probing; /**< Probing flag */
+ struct nat_lifetime_interval interval; /**< Lifetime intervals */
+ nat_lifetime_h *lh; /**< Result handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static void timeout(void *arg);
+static void binding_ok(struct nat_lifetime *nl);
+static void binding_expired(struct nat_lifetime *nl);
+
+
+/*
+ * X socket
+ */
+
+
+static void msg_recv(struct stun *stun, const struct sa *src,
+ struct mbuf *mb)
+{
+ int err;
+ (void)src;
+
+ err = stun_recv(stun, mb);
+ if (err && ENOENT != err) {
+ DEBUG_WARNING("msg_recv: stunc_recv(): (%m)\n", err);
+ }
+}
+
+
+static void udp_recv_handler_x(const struct sa *src, struct mbuf *mb,
+ void *arg)
+{
+ struct nat_lifetime *nl = arg;
+
+ /* Forward response to socket */
+ msg_recv(nl->stun, src, mb);
+}
+
+
+static void stun_response_handler_x(int err, uint16_t scode,
+ const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct nat_lifetime *nl = arg;
+ struct stun_attr *attr;
+
+ (void)reason;
+
+ if (err) {
+ DEBUG_WARNING("stun_response_handler X: %m\n", err);
+ goto out;
+ }
+
+ switch (scode) {
+
+ case 0:
+ attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ if (!attr) {
+ err = EPROTO;
+ break;
+ }
+
+ nl->map = attr->v.xor_mapped_addr;
+
+ DEBUG_INFO("Starting timer of %d seconds...[zzz]...\n",
+ nl->interval.cur);
+
+ tmr_start(&nl->tmr, nl->interval.cur*1000, timeout, nl);
+ return;
+
+ default:
+ err = EPROTO;
+ break;
+ }
+
+ out:
+ nl->lh(err, &nl->interval, nl->arg);
+ binding_expired(nl);
+}
+
+
+/*
+ * Y socket
+ */
+
+
+static void udp_recv_handler_y(const struct sa *src, struct mbuf *mb,
+ void *arg)
+{
+ struct nat_lifetime *nl = arg;
+
+ (void)src;
+ (void)mb;
+
+ if (!nl->probing) {
+ DEBUG_WARNING("Y: hmm, not probing?\n");
+ }
+
+ binding_expired(nl);
+}
+
+
+static void stun_response_handler_y(int err, uint16_t scode,
+ const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct nat_lifetime *nl = arg;
+ (void)reason;
+ (void)msg;
+
+ if (err) {
+ binding_expired(nl);
+ return;
+ }
+
+ switch (scode) {
+
+ case 0:
+ binding_ok(nl);
+ break;
+
+ default:
+ nl->lh(EBADMSG, &nl->interval, nl->arg);
+ break;
+ }
+}
+
+
+/*
+ * Common
+ */
+
+
+static int start_test(struct nat_lifetime *nl)
+{
+ nl->probing = false;
+
+ tmr_cancel(&nl->tmr);
+
+ nl->ctx = mem_deref(nl->ctx);
+ return stun_request(&nl->ctx, nl->stun, IPPROTO_UDP, nl->us_x,
+ &nl->srv, 0, STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler_x, nl, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+static void timeout(void *arg)
+{
+ struct nat_lifetime *nl = arg;
+ const uint16_t rp = sa_port(&nl->map);
+ int err;
+
+ nl->probing = true;
+
+ nl->cty = mem_deref(nl->cty);
+ err = stun_request(&nl->cty, nl->stun, IPPROTO_UDP, nl->us_y,
+ &nl->srv, 0, STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler_y, nl, 2,
+ STUN_ATTR_RESP_PORT, &rp,
+ STUN_ATTR_SOFTWARE, stun_software);
+ if (err)
+ goto out;
+
+ return;
+
+ out:
+ DEBUG_WARNING("timeout: (%m)\n", err);
+
+ nl->lh(err, &nl->interval, nl->arg);
+
+ (void)start_test(nl);
+}
+
+
+/* Binding OK - recalculate current interval */
+static void binding_ok(struct nat_lifetime *nl)
+{
+ nl->interval.min = max(1, nl->interval.cur);
+
+ if (nl->interval.max > 0)
+ nl->interval.cur = (nl->interval.min + nl->interval.max) / 2;
+ else
+ nl->interval.cur *= 2;
+
+ nl->lh(0, &nl->interval, nl->arg);
+
+ (void)start_test(nl);
+}
+
+
+/* Request timed out - recalculate current interval */
+static void binding_expired(struct nat_lifetime *nl)
+{
+ nl->interval.max = nl->interval.cur;
+
+ nl->interval.cur = (nl->interval.min + nl->interval.max) / 2;
+
+ nl->lh(0, &nl->interval, nl->arg);
+
+ (void)start_test(nl);
+}
+
+
+static void lifetime_destructor(void *data)
+{
+ struct nat_lifetime *nl = data;
+
+ tmr_cancel(&nl->tmr);
+
+ mem_deref(nl->ctx);
+ mem_deref(nl->cty);
+ mem_deref(nl->us_x);
+ mem_deref(nl->us_y);
+ mem_deref(nl->stun);
+}
+
+
+/**
+ * Allocate a new NAT Lifetime discovery session
+ *
+ * @param nlp Pointer to allocated NAT Lifetime object
+ * @param srv STUN Server IP address and port number
+ * @param interval Initial interval in [seconds]
+ * @param conf STUN configuration (Optional)
+ * @param lh Lifetime handler - called for each probe
+ * @param arg Handler argument
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_lifetime_alloc(struct nat_lifetime **nlp, const struct sa *srv,
+ uint32_t interval, const struct stun_conf *conf,
+ nat_lifetime_h *lh, void *arg)
+{
+ struct nat_lifetime *nl;
+ int err;
+
+ if (!nlp || !srv || !interval || !lh)
+ return EINVAL;
+
+ nl = mem_zalloc(sizeof(*nl), lifetime_destructor);
+ if (!nl)
+ return ENOMEM;
+
+ tmr_init(&nl->tmr);
+
+ err = stun_alloc(&nl->stun, conf, NULL, NULL);
+ if (err)
+ goto out;
+
+ err = udp_listen(&nl->us_x, NULL, udp_recv_handler_x, nl);
+ if (err)
+ goto out;
+
+ err = udp_listen(&nl->us_y, NULL, udp_recv_handler_y, nl);
+ if (err)
+ goto out;
+
+ sa_cpy(&nl->srv, srv);
+ nl->interval.min = 1;
+ nl->interval.cur = interval;
+ nl->lh = lh;
+ nl->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(nl);
+ else
+ *nlp = nl;
+
+ return err;
+}
+
+
+/**
+ * Start a new NAT Lifetime discovery session
+ *
+ * @param nl NAT Lifetime object
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_lifetime_start(struct nat_lifetime *nl)
+{
+ if (!nl)
+ return EINVAL;
+
+ return start_test(nl);
+}
diff --git a/src/natbd/mapping.c b/src/natbd/mapping.c
new file mode 100644
index 0000000..e304560
--- /dev/null
+++ b/src/natbd/mapping.c
@@ -0,0 +1,354 @@
+/**
+ * @file mapping.c NAT Mapping Behaviour discovery
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include <re_natbd.h>
+
+
+#define DEBUG_MODULE "natbd_mapping"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/* Determining NAT Mapping Behavior
+
+ This will require at most three tests. In test I, the client
+ performs the UDP connectivity test. The server will return its
+ alternate address and port in OTHER-ADDRESS in the binding response.
+ If OTHER-ADDRESS is not returned, the server does not support this
+ usage and this test cannot be run. The client examines the XOR-
+ MAPPED-ADDRESS attribute. If this address and port are the same as
+ the local IP address and port of the socket used to send the request,
+ the client knows that it is not NATed and the effective mapping will
+ be Endpoint-Independent.
+
+ In test II, the client sends a Binding Request to the alternate
+ address, but primary port. If the XOR-MAPPED-ADDRESS in the Binding
+ Response is the same as test I the NAT currently has Endpoint-
+ Independent Mapping. If not, test III is performed: the client sends
+ a Binding Request to the alternate address and port. If the XOR-
+ MAPPED-ADDRESS matches test II, the NAT currently has Address-
+ Dependent Mapping; if it doesn't match it currently has Address and
+ Port-Dependent Mapping.
+ */
+
+
+/** Defines a NAT Mapping Behaviour Discovery session */
+struct nat_mapping {
+ struct stun *stun; /**< STUN Instance */
+ struct udp_sock *us; /**< UDP socket */
+ struct tcp_conn *tc; /**< TCP connection */
+ struct sa laddr; /**< Local IP address and port */
+ struct sa map[3]; /**< XOR Mapped address/ports */
+ struct sa srv; /**< STUN server address/port */
+ nat_mapping_h *mh; /**< Result handler */
+ void *arg; /**< Handler argument */
+ int proto; /**< IP Protocol */
+ uint32_t test_phase; /**< State machine */
+ struct tcp_conn *tcv[3]; /**< TCP Connections */
+};
+
+
+static int mapping_send(struct nat_mapping *nm);
+
+
+static void stun_response_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct nat_mapping *nm = arg;
+ struct stun_attr *map, *other;
+
+ if (err) {
+ DEBUG_WARNING("stun_response_handler: (%m)\n", err);
+ nm->mh(err, NAT_TYPE_UNKNOWN, nm->arg);
+ return;
+ }
+
+ switch (scode) {
+
+ case 0:
+ other = stun_msg_attr(msg, STUN_ATTR_OTHER_ADDR);
+ map = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ if (!map)
+ map = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
+
+ if (!map || !other) {
+ DEBUG_WARNING("missing attributes: %s %s\n",
+ map ? "" : "MAPPED-ADDR",
+ other ? "" : "OTHER-ADDR");
+ nm->mh(EPROTO, NAT_TYPE_UNKNOWN, nm->arg);
+ return;
+ }
+
+ nm->map[nm->test_phase-1] = map->v.sa;
+ break;
+
+ default:
+ DEBUG_WARNING("Binding Error Resp: %u %s\n", scode, reason);
+ nm->mh(EPROTO, NAT_TYPE_UNKNOWN, nm->arg);
+ return;
+ }
+
+ switch (nm->test_phase) {
+
+ case 1:
+ /* Test I completed */
+ if (sa_cmp(&nm->laddr, &nm->map[0], SA_ALL)) {
+ nm->mh(0, NAT_TYPE_ENDP_INDEP, nm->arg);
+ return;
+ }
+
+ /* Start Test II - the client sends a Binding Request to the
+ alternate *address* */
+ ++nm->test_phase;
+
+ sa_set_port(&other->v.other_addr, sa_port(&nm->srv));
+ sa_cpy(&nm->srv, &other->v.other_addr);
+
+ err = mapping_send(nm);
+ if (err) {
+ DEBUG_WARNING("stunc_request_send: (%m)\n", err);
+ nm->mh(err, NAT_TYPE_UNKNOWN, nm->arg);
+ }
+ break;
+
+ case 2:
+ /* Test II completed */
+ if (sa_cmp(&nm->map[0], &nm->map[1], SA_ALL)) {
+ nm->mh(0, NAT_TYPE_ENDP_INDEP, nm->arg);
+ return;
+ }
+
+ /* Start Test III - the client sends a Binding Request
+ to the alternate address and port */
+ ++nm->test_phase;
+
+ sa_set_port(&nm->srv, sa_port(&other->v.other_addr));
+ err = mapping_send(nm);
+ if (err) {
+ DEBUG_WARNING("stunc_request_send: (%m)\n", err);
+ nm->mh(err, NAT_TYPE_UNKNOWN, nm->arg);
+ }
+ break;
+
+ case 3:
+ /* Test III completed */
+ if (sa_cmp(&nm->map[1], &nm->map[2], SA_ALL)) {
+ nm->mh(0, NAT_TYPE_ADDR_DEP, nm->arg);
+ }
+ else {
+ nm->mh(0, NAT_TYPE_ADDR_PORT_DEP, nm->arg);
+ }
+ ++nm->test_phase;
+ break;
+
+ default:
+ DEBUG_WARNING("invalid test phase %d\n", nm->test_phase);
+ nm->mh(EINVAL, NAT_TYPE_UNKNOWN, nm->arg);
+ break;
+ }
+}
+
+
+static int mapping_send(struct nat_mapping *nm)
+{
+ switch (nm->proto) {
+
+ case IPPROTO_UDP:
+ return stun_request(NULL, nm->stun, IPPROTO_UDP, nm->us,
+ &nm->srv, 0, STUN_METHOD_BINDING, NULL, 0,
+ false, stun_response_handler, nm, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+
+ case IPPROTO_TCP:
+ nm->tc = mem_deref(nm->tc);
+ nm->tc = mem_ref(nm->tcv[nm->test_phase-1]);
+ return tcp_conn_connect(nm->tc, &nm->srv);
+
+ default:
+ return EPROTONOSUPPORT;
+ }
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb,
+ void *arg)
+{
+ struct nat_mapping *nm = arg;
+ int err;
+ (void)src;
+
+ err = stun_recv(nm->stun, mb);
+ if (err && ENOENT != err) {
+ DEBUG_WARNING("udp_recv_handler: stunc_recv(): (%m)\n", err);
+ }
+}
+
+
+static void mapping_destructor(void *data)
+{
+ struct nat_mapping *nm = data;
+ int i;
+
+ mem_deref(nm->us);
+ mem_deref(nm->tc);
+
+ for (i=0; i<3; i++)
+ mem_deref(nm->tcv[i]);
+
+ mem_deref(nm->stun);
+}
+
+
+static void tcp_estab_handler(void *arg)
+{
+ struct nat_mapping *nm = arg;
+ int err;
+
+ err = stun_request(NULL, nm->stun, IPPROTO_TCP, nm->tc, NULL, 0,
+ STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler, nm, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+
+ if (err) {
+ DEBUG_WARNING("TCP established: mapping_send (%m)\n", err);
+ nm->mh(err, NAT_TYPE_UNKNOWN, nm->arg);
+ }
+}
+
+
+static void tcp_recv_handler(struct mbuf *mb, void *arg)
+{
+ struct nat_mapping *nm = arg;
+ int err;
+
+ err = stun_recv(nm->stun, mb);
+ if (err && ENOENT != err) {
+ DEBUG_WARNING("stunc recv: %m\n", err);
+ }
+}
+
+
+static void tcp_close_handler(int err, void *arg)
+{
+ struct nat_mapping *nm = arg;
+
+ DEBUG_NOTICE("TCP Connection Closed (%m)\n", err);
+
+ if (err) {
+ nm->mh(err, NAT_TYPE_UNKNOWN, nm->arg);
+ }
+}
+
+
+/**
+ * Allocate a new NAT Mapping Behaviour Discovery session
+ *
+ * @param nmp Pointer to allocated NAT Mapping object
+ * @param laddr Local IP address
+ * @param srv STUN Server IP address and port
+ * @param proto Transport protocol
+ * @param conf STUN configuration (Optional)
+ * @param mh Mapping handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_mapping_alloc(struct nat_mapping **nmp, const struct sa *laddr,
+ const struct sa *srv, int proto,
+ const struct stun_conf *conf,
+ nat_mapping_h *mh, void *arg)
+{
+ struct nat_mapping *nm;
+ int i, err;
+
+ if (!nmp || !laddr || !srv || !mh)
+ return EINVAL;
+
+ nm = mem_zalloc(sizeof(*nm), mapping_destructor);
+ if (!nm)
+ return ENOMEM;
+
+ err = stun_alloc(&nm->stun, conf, NULL, NULL);
+ if (err)
+ goto out;
+
+ nm->proto = proto;
+ sa_cpy(&nm->laddr, laddr);
+
+ switch (proto) {
+
+ case IPPROTO_UDP:
+ err = udp_listen(&nm->us, &nm->laddr, udp_recv_handler, nm);
+ if (err)
+ goto out;
+ err = udp_local_get(nm->us, &nm->laddr);
+ if (err)
+ goto out;
+ break;
+
+ case IPPROTO_TCP:
+
+ /* Allocate and bind 3 TCP Sockets */
+ for (i=0; i<3; i++) {
+ err = tcp_conn_alloc(&nm->tcv[i], srv,
+ tcp_estab_handler,
+ tcp_recv_handler,
+ tcp_close_handler, nm);
+ if (err)
+ goto out;
+
+ err = tcp_conn_bind(nm->tcv[i], &nm->laddr);
+ if (err)
+ goto out;
+
+ err = tcp_conn_local_get(nm->tcv[i], &nm->laddr);
+ if (err)
+ goto out;
+ }
+ break;
+
+ default:
+ err = EPROTONOSUPPORT;
+ goto out;
+ }
+
+ sa_cpy(&nm->srv, srv);
+ nm->mh = mh;
+ nm->arg = arg;
+
+ *nmp = nm;
+
+ out:
+ if (err)
+ mem_deref(nm);
+ return err;
+}
+
+
+/**
+ * Start a new NAT Mapping Behaviour Discovery session
+ *
+ * @param nm NAT Mapping object
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_mapping_start(struct nat_mapping *nm)
+{
+ if (!nm)
+ return EINVAL;
+
+ nm->test_phase = 1;
+
+ return mapping_send(nm);
+}
diff --git a/src/natbd/mod.mk b/src/natbd/mod.mk
new file mode 100644
index 0000000..7a267fe
--- /dev/null
+++ b/src/natbd/mod.mk
@@ -0,0 +1,12 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += natbd/filtering.c
+SRCS += natbd/genalg.c
+SRCS += natbd/hairpinning.c
+SRCS += natbd/lifetime.c
+SRCS += natbd/mapping.c
+SRCS += natbd/natstr.c
diff --git a/src/natbd/natstr.c b/src/natbd/natstr.c
new file mode 100644
index 0000000..96a8fa8
--- /dev/null
+++ b/src/natbd/natstr.c
@@ -0,0 +1,32 @@
+/**
+ * @file natstr.c NAT Behaviour Discovery strings
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include <re_natbd.h>
+
+
+/**
+ * Get the name of the NAT Mapping/Filtering type
+ *
+ * @param type NAT Mapping/Filtering type
+ *
+ * @return Name of the NAT Mapping/Filtering type
+ */
+const char *nat_type_str(enum nat_type type)
+{
+ switch (type) {
+
+ case NAT_TYPE_UNKNOWN: return "Unknown";
+ case NAT_TYPE_ENDP_INDEP: return "Endpoint Independent";
+ case NAT_TYPE_ADDR_DEP: return "Address Dependent";
+ case NAT_TYPE_ADDR_PORT_DEP: return "Address and Port Dependent";
+ default: return "???";
+ }
+}
diff --git a/src/net/bsd/brt.c b/src/net/bsd/brt.c
new file mode 100644
index 0000000..d30aab4
--- /dev/null
+++ b/src/net/bsd/brt.c
@@ -0,0 +1,101 @@
+/**
+ * @file bsd/brt.c BSD routing table code
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_sa.h>
+#include <re_net.h>
+#include <sys/sysctl.h>
+#include <net/route.h>
+#include <net/if.h>
+
+
+/*
+ * See https://github.com/boundary/libdnet/blob/master/src/route-bsd.c
+ */
+
+#ifdef __APPLE__
+#define RT_MSGHDR_ALIGNMENT sizeof(uint32_t)
+#else
+#define RT_MSGHDR_ALIGNMENT sizeof(unsigned long)
+#endif
+
+#define ROUNDUP(a) \
+ ((a) > 0 \
+ ? (1 + (((size_t)(a) - 1) | (RT_MSGHDR_ALIGNMENT - 1))) \
+ : RT_MSGHDR_ALIGNMENT)
+
+
+int net_rt_list(net_rt_h *rth, void *arg)
+{
+ /* net.route.0.inet.flags.gateway */
+ int mib[] = {CTL_NET, PF_ROUTE, 0, AF_UNSPEC,
+ NET_RT_FLAGS, RTF_GATEWAY};
+ char ifname[IFNAMSIZ], *buf, *p;
+ struct rt_msghdr *rt;
+ struct sockaddr *sa, *sa_tab[RTAX_MAX];
+ struct sa dst, gw;
+ size_t l;
+ int i, err = 0;
+
+ if (sysctl(mib, sizeof(mib)/sizeof(int), 0, &l, 0, 0) < 0)
+ return errno;
+ if (!l)
+ return ENOENT;
+
+ buf = mem_alloc(l, NULL);
+ if (!buf)
+ return ENOMEM;
+
+ if (sysctl(mib, sizeof(mib)/sizeof(int), buf, &l, 0, 0) < 0) {
+ err = errno;
+ goto out;
+ }
+
+ for (p = buf; p<buf+l; p += rt->rtm_msglen) {
+ rt = (void *)p; /* buffer is aligned */
+ sa = (struct sockaddr *)(rt + 1);
+
+ if (rt->rtm_type != RTM_GET)
+ continue;
+
+ if (!(rt->rtm_flags & RTF_UP))
+ continue;
+
+ for (i=0; i<RTAX_MAX; i++) {
+
+ if (rt->rtm_addrs & (1 << i)) {
+ sa_tab[i] = sa;
+ sa = (struct sockaddr *)
+ ((char *)sa + ROUNDUP(sa->sa_len));
+ }
+ else {
+ sa_tab[i] = NULL;
+ }
+ }
+
+ if ((rt->rtm_addrs & RTA_DST) == RTA_DST) {
+ err = sa_set_sa(&dst, sa_tab[RTAX_DST]);
+ if (err)
+ continue;
+ }
+ if ((rt->rtm_addrs & RTA_GATEWAY) == RTA_GATEWAY) {
+ err = sa_set_sa(&gw, sa_tab[RTAX_GATEWAY]);
+ if (err)
+ continue;
+ }
+
+ if_indextoname(rt->rtm_index, ifname);
+
+ if (rth(ifname, &dst, 0, &gw, arg))
+ break;
+ }
+
+ out:
+ mem_deref(buf);
+
+ return err;
+}
diff --git a/src/net/if.c b/src/net/if.c
new file mode 100644
index 0000000..ed99cbf
--- /dev/null
+++ b/src/net/if.c
@@ -0,0 +1,233 @@
+/**
+ * @file net/if.c Network interface code
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_net.h>
+
+
+#define DEBUG_MODULE "netif"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Interface address entry */
+struct ifentry {
+ int af; /**< Address family */
+ char *ifname; /**< Interface name */
+ struct sa *ip; /**< IP address */
+ size_t sz; /**< Size of buffer */
+ bool found; /**< Found flag */
+};
+
+
+static bool if_getname_handler(const char *ifname, const struct sa *sa,
+ void *arg)
+{
+ struct ifentry *ife = arg;
+
+ if (ife->af != sa_af(sa))
+ return false;
+
+ if (sa_cmp(sa, ife->ip, SA_ADDR)) {
+ str_ncpy(ife->ifname, ifname, ife->sz);
+ ife->found = true;
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Get the name of the interface for a given IP address
+ *
+ * @param ifname Buffer for returned network interface name
+ * @param sz Size of buffer
+ * @param af Address Family
+ * @param ip Given IP address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_if_getname(char *ifname, size_t sz, int af, const struct sa *ip)
+{
+ struct ifentry ife;
+ int err;
+
+ if (!ifname || !sz || !ip)
+ return EINVAL;
+
+ ife.af = af;
+ ife.ifname = ifname;
+ ife.ip = (struct sa *)ip;
+ ife.sz = sz;
+ ife.found = false;
+
+ err = net_if_list(if_getname_handler, &ife);
+
+ return ife.found ? err : ENODEV;
+}
+
+
+static bool if_getaddr_handler(const char *ifname,
+ const struct sa *sa, void *arg)
+{
+ struct ifentry *ife = arg;
+
+ /* Match name of interface? */
+ if (str_isset(ife->ifname) && 0 != str_casecmp(ife->ifname, ifname))
+ return false;
+
+ if (!sa_isset(sa, SA_ADDR))
+ return false;
+
+#if 1
+ /* skip loopback and link-local IP */
+ if (sa_is_loopback(sa) || sa_is_linklocal(sa))
+ return false;
+#endif
+
+ /* Match address family */
+ if (ife->af != sa_af(sa))
+ return false;
+
+ /* Match - copy address */
+ sa_cpy(ife->ip, sa);
+ ife->found = true;
+
+ return ife->found;
+}
+
+
+/**
+ * Get IP address for a given network interface
+ *
+ * @param ifname Network interface name (optional)
+ * @param af Address Family
+ * @param ip Returned IP address
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * @deprecated Works for IPv4 only
+ */
+int net_if_getaddr(const char *ifname, int af, struct sa *ip)
+{
+ struct ifentry ife;
+ int err;
+
+ if (!ip)
+ return EINVAL;
+
+ ife.af = af;
+ ife.ifname = (char *)ifname;
+ ife.ip = ip;
+ ife.sz = 0;
+ ife.found = false;
+
+#ifdef HAVE_GETIFADDRS
+ err = net_getifaddrs(if_getaddr_handler, &ife);
+#else
+ err = net_if_list(if_getaddr_handler, &ife);
+#endif
+
+ return ife.found ? err : ENODEV;
+}
+
+
+static bool if_debug_handler(const char *ifname, const struct sa *sa,
+ void *arg)
+{
+ struct re_printf *pf = arg;
+
+ (void)re_hprintf(pf, " %10s: %j\n", ifname, sa);
+
+ return false;
+}
+
+
+/**
+ * Debug network interfaces
+ *
+ * @param pf Print handler for debug output
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_if_debug(struct re_printf *pf, void *unused)
+{
+ int err;
+
+ (void)unused;
+
+ err = re_hprintf(pf, "net interfaces:\n");
+
+#ifdef HAVE_GETIFADDRS
+ err |= net_getifaddrs(if_debug_handler, pf);
+#else
+ err |= net_if_list(if_debug_handler, pf);
+#endif
+
+ return err;
+}
+
+
+static bool linklocal_handler(const char *ifname, const struct sa *sa,
+ void *arg)
+{
+ void **argv = arg;
+ int af = *(int *)argv[1];
+
+ if (argv[0] && 0 != str_casecmp(argv[0], ifname))
+ return false;
+
+ if (af != AF_UNSPEC && af != sa_af(sa))
+ return false;
+
+ if (sa_is_linklocal(sa)) {
+ *((struct sa *)argv[2]) = *sa;
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Get the Link-local address for a specific network interface
+ *
+ * @param ifname Name of the interface
+ * @param af Address family
+ * @param ip Returned link-local address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_if_getlinklocal(const char *ifname, int af, struct sa *ip)
+{
+ struct sa addr;
+ void *argv[3];
+ int err;
+
+ if (!ip)
+ return EINVAL;
+
+ sa_init(&addr, sa_af(ip));
+
+ argv[0] = (void *)ifname;
+ argv[1] = ⁡
+ argv[2] = &addr;
+
+ err = net_if_apply(linklocal_handler, argv);
+ if (err)
+ return err;
+
+ if (!sa_isset(&addr, SA_ADDR))
+ return ENOENT;
+
+ *ip = addr;
+
+ return 0;
+}
diff --git a/src/net/ifaddrs.c b/src/net/ifaddrs.c
new file mode 100644
index 0000000..2116bda
--- /dev/null
+++ b/src/net/ifaddrs.c
@@ -0,0 +1,64 @@
+/**
+ * @file ifaddrs.c Network interface code using getifaddrs().
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <unistd.h>
+#include <sys/socket.h>
+#define __USE_MISC 1 /**< Use MISC code */
+#include <net/if.h>
+#include <ifaddrs.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_sa.h>
+#include <re_net.h>
+
+
+#define DEBUG_MODULE "ifaddrs"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/**
+ * Get a list of all network interfaces including name and IP address.
+ * Both IPv4 and IPv6 are supported.
+ *
+ * @param ifh Interface handler, called once per network interface.
+ * @param arg Handler argument.
+ *
+ * @return 0 if success, otherwise errorcode.
+ */
+int net_getifaddrs(net_ifaddr_h *ifh, void *arg)
+{
+ struct ifaddrs *ifa, *ifp;
+ int err;
+
+ if (!ifh)
+ return EINVAL;
+
+ if (0 != getifaddrs(&ifa)) {
+ err = errno;
+ DEBUG_WARNING("getifaddrs: %m\n", err);
+ return err;
+ }
+
+ for (ifp = ifa; ifa; ifa = ifa->ifa_next) {
+ struct sa sa;
+
+ DEBUG_INFO("ifaddr: %10s flags=%08x\n", ifa->ifa_name,
+ ifa->ifa_flags);
+
+ if (ifa->ifa_flags & IFF_UP) {
+ err = sa_set_sa(&sa, ifa->ifa_addr);
+ if (err)
+ continue;
+
+ if (ifh(ifa->ifa_name, &sa, arg))
+ break;
+ }
+ }
+
+ freeifaddrs(ifp);
+
+ return 0;
+}
diff --git a/src/net/linux/rt.c b/src/net/linux/rt.c
new file mode 100644
index 0000000..2152af1
--- /dev/null
+++ b/src/net/linux/rt.c
@@ -0,0 +1,253 @@
+/**
+ * @file linux/rt.c Routing table code for Linux. See rtnetlink(7)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _BSD_SOURCE 1
+#define _DEFAULT_SOURCE 1
+#include <string.h>
+#include <unistd.h>
+#define __USE_POSIX 1 /**< Use POSIX flag */
+#include <netdb.h>
+#define __USE_MISC 1
+#include <net/if.h>
+#undef __STRICT_ANSI__
+#include <linux/types.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_fmt.h>
+#include <re_sa.h>
+#include <re_net.h>
+
+
+#define DEBUG_MODULE "linuxrt"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/* Override macros to avoid casting alignment warning */
+#undef RTM_RTA
+#define RTM_RTA(r) (void *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg)))
+#undef RTA_NEXT
+#define RTA_NEXT(rta, len) ((len) -= RTA_ALIGN((rta)->rta_len), \
+ (void *)(((char *)(rta)) + RTA_ALIGN((rta)->rta_len)))
+#undef NLMSG_NEXT
+#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
+ (void*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
+
+
+enum {BUFSIZE = 8192};
+
+
+/** Defines a network route */
+struct net_rt {
+ char ifname[IFNAMSIZ]; /**< Interface name */
+ struct sa dst; /**< Destination IP address/network */
+ int dstlen; /**< Prefix length of destination */
+ struct sa gw; /**< Gateway IP address */
+};
+
+
+static int read_sock(int fd, uint8_t *buf, size_t size, int seq, int pid)
+{
+ struct nlmsghdr *nlhdr;
+ int n = 0, len = 0;
+
+ do {
+ /* Receive response from the kernel */
+ if ((n = recv(fd, buf, size - len, 0)) < 0) {
+ DEBUG_WARNING("SOCK READ: %m\n", errno);
+ return -1;
+ }
+ nlhdr = (struct nlmsghdr *)(void *)buf;
+
+ /* Check if the header is valid */
+ if (0 == NLMSG_OK(nlhdr, (uint32_t)n) ||
+ NLMSG_ERROR == nlhdr->nlmsg_type) {
+ DEBUG_WARNING("Error in received packet\n");
+ return -1;
+ }
+
+ /* Check if the its the last message */
+ if (NLMSG_DONE == nlhdr->nlmsg_type) {
+ break;
+ }
+ else{
+ /* Else move the pointer to buffer appropriately */
+ buf += n;
+ len += n;
+ }
+
+ /* Check if its a multi part message */
+ if (0 == (nlhdr->nlmsg_flags & NLM_F_MULTI)) {
+ /* return if its not */
+ break;
+ }
+ } while (nlhdr->nlmsg_seq != (uint32_t)seq ||
+ nlhdr->nlmsg_pid != (uint32_t)pid);
+
+ return len;
+}
+
+
+/* Parse one route */
+static int rt_parse(const struct nlmsghdr *nlhdr, struct net_rt *rt)
+{
+ struct rtmsg *rtmsg;
+ struct rtattr *rtattr;
+ int len;
+
+ rtmsg = (struct rtmsg *)NLMSG_DATA(nlhdr);
+
+ /* If the route does not belong to main routing table then return. */
+ if (RT_TABLE_MAIN != rtmsg->rtm_table)
+ return EINVAL;
+
+ sa_init(&rt->dst, rtmsg->rtm_family);
+ rt->dstlen = rtmsg->rtm_dst_len;
+
+ /* get the rtattr field */
+ rtattr = (struct rtattr *)RTM_RTA(rtmsg);
+ len = RTM_PAYLOAD(nlhdr);
+ for (;RTA_OK(rtattr, len); rtattr = RTA_NEXT(rtattr, len)) {
+
+ switch (rtattr->rta_type) {
+
+ case RTA_OIF:
+ if_indextoname(*(int *)RTA_DATA(rtattr), rt->ifname);
+ break;
+
+ case RTA_GATEWAY:
+ switch (rtmsg->rtm_family) {
+
+ case AF_INET:
+ sa_init(&rt->gw, AF_INET);
+ rt->gw.u.in.sin_addr.s_addr
+ = *(uint32_t *)RTA_DATA(rtattr);
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ sa_set_in6(&rt->gw, RTA_DATA(rtattr), 0);
+ break;
+#endif
+
+ default:
+ DEBUG_WARNING("RTA_DST: unknown family %d\n",
+ rtmsg->rtm_family);
+ break;
+ }
+ break;
+
+#if 0
+ case RTA_PREFSRC:
+ rt->srcaddr = *(uint32_t *)RTA_DATA(rtattr);
+ break;
+#endif
+
+ case RTA_DST:
+ switch (rtmsg->rtm_family) {
+
+ case AF_INET:
+ sa_init(&rt->dst, AF_INET);
+ rt->dst.u.in.sin_addr.s_addr
+ = *(uint32_t *)RTA_DATA(rtattr);
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ sa_set_in6(&rt->dst, RTA_DATA(rtattr), 0);
+ break;
+#endif
+
+ default:
+ DEBUG_WARNING("RTA_DST: unknown family %d\n",
+ rtmsg->rtm_family);
+ break;
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * List all entries in the routing table
+ *
+ * @param rth Route entry handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_rt_list(net_rt_h *rth, void *arg)
+{
+ union {
+ uint8_t buf[BUFSIZE];
+ struct nlmsghdr msg[1];
+ } u;
+ struct nlmsghdr *nlmsg;
+ int sock, len, seq = 0, err = 0;
+
+ if (!rth)
+ return EINVAL;
+
+ /* Create Socket */
+ if ((sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0) {
+ DEBUG_WARNING("list: socket(): (%m)\n", errno);
+ return errno;
+ }
+
+ /* Initialize the buffer */
+ memset(u.buf, 0, sizeof(u.buf));
+
+ /* point the header and the msg structure pointers into the buffer */
+ nlmsg = u.msg;
+
+ /* Fill in the nlmsg header*/
+ nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ nlmsg->nlmsg_type = RTM_GETROUTE;
+ nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
+ nlmsg->nlmsg_seq = seq++;
+ nlmsg->nlmsg_pid = getpid();
+
+ /* Send the request */
+ if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
+ err = errno;
+ DEBUG_WARNING("list: write to socket failed (%m)\n", err);
+ goto out;
+ }
+
+ /* Read the response */
+ if ((len = read_sock(sock, u.buf, sizeof(u.buf), seq, getpid())) < 0) {
+ err = errno;
+ DEBUG_WARNING("list: read from socket failed (%m)\n", err);
+ goto out;
+ }
+
+ /* Parse and print the response */
+ for (;NLMSG_OK(nlmsg,(uint32_t)len);nlmsg = NLMSG_NEXT(nlmsg,len)) {
+ struct net_rt rt;
+
+ memset(&rt, 0, sizeof(struct net_rt));
+ if (0 != rt_parse(nlmsg, &rt))
+ continue;
+
+#ifdef HAVE_INET6
+ if (AF_INET6 == sa_af(&rt.dst)
+ && IN6_IS_ADDR_UNSPECIFIED(&rt.dst.u.in6.sin6_addr))
+ continue;
+#endif
+
+ if (rth(rt.ifname, &rt.dst, rt.dstlen, &rt.gw, arg))
+ break;
+ }
+
+ out:
+ (void)close(sock);
+
+ return err;
+}
diff --git a/src/net/mod.mk b/src/net/mod.mk
new file mode 100644
index 0000000..8f12899
--- /dev/null
+++ b/src/net/mod.mk
@@ -0,0 +1,41 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+# Generic files
+SRCS += net/if.c
+SRCS += net/net.c
+SRCS += net/netstr.c
+SRCS += net/rt.c
+SRCS += net/sock.c
+SRCS += net/sockopt.c
+
+
+# Platform dependant files
+ifneq ($(OS),win32)
+SRCS += net/posix/pif.c
+else
+SRCS += net/win32/wif.c
+endif
+
+
+# Routing
+ifeq ($(OS),linux)
+SRCS += net/linux/rt.c
+CFLAGS += -DHAVE_ROUTE_LIST
+else
+
+ifneq ($(HAVE_SYS_SYSCTL_H),)
+ifneq ($(HAVE_NET_ROUTE_H),)
+SRCS += net/bsd/brt.c
+CFLAGS += -DHAVE_ROUTE_LIST
+endif
+endif
+
+endif
+
+ifdef HAVE_GETIFADDRS
+SRCS += net/ifaddrs.c
+endif
diff --git a/src/net/net.c b/src/net/net.c
new file mode 100644
index 0000000..0978b1b
--- /dev/null
+++ b/src/net/net.c
@@ -0,0 +1,157 @@
+/**
+ * @file net.c Networking code.
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _BSD_SOURCE 1
+#define _DEFAULT_SOURCE 1
+#include <stdlib.h>
+#include <string.h>
+#if !defined(WIN32)
+#define __USE_BSD 1 /**< Use BSD code */
+#include <unistd.h>
+#include <netdb.h>
+#endif
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_net.h>
+
+
+#define DEBUG_MODULE "net"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/**
+ * Get the IP address of the host
+ *
+ * @param af Address Family
+ * @param ip Returned IP address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_hostaddr(int af, struct sa *ip)
+{
+ char hostname[256];
+ struct in_addr in;
+ struct hostent *he;
+
+ if (-1 == gethostname(hostname, sizeof(hostname)))
+ return errno;
+
+ he = gethostbyname(hostname);
+ if (!he)
+ return ENOENT;
+
+ if (af != he->h_addrtype)
+ return EAFNOSUPPORT;
+
+ /* Get the first entry */
+ memcpy(&in, he->h_addr_list[0], sizeof(in));
+ sa_set_in(ip, ntohl(in.s_addr), 0);
+
+ return 0;
+}
+
+
+/**
+ * Get the default source IP address
+ *
+ * @param af Address Family
+ * @param ip Returned IP address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_default_source_addr_get(int af, struct sa *ip)
+{
+#if defined(WIN32)
+ return net_hostaddr(af, ip);
+#else
+ char ifname[64] = "";
+
+#ifdef HAVE_ROUTE_LIST
+ /* Get interface with default route */
+ (void)net_rt_default_get(af, ifname, sizeof(ifname));
+#endif
+
+ /* First try with default interface */
+ if (0 == net_if_getaddr(ifname, af, ip))
+ return 0;
+
+ /* Then try first real IP */
+ if (0 == net_if_getaddr(NULL, af, ip))
+ return 0;
+
+ return net_if_getaddr4(ifname, af, ip);
+#endif
+}
+
+
+/**
+ * Get a list of all network interfaces including name and IP address.
+ * Both IPv4 and IPv6 are supported
+ *
+ * @param ifh Interface handler, called once per network interface
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_if_apply(net_ifaddr_h *ifh, void *arg)
+{
+#ifdef HAVE_GETIFADDRS
+ return net_getifaddrs(ifh, arg);
+#else
+ return net_if_list(ifh, arg);
+#endif
+}
+
+
+static bool net_rt_handler(const char *ifname, const struct sa *dst,
+ int dstlen, const struct sa *gw, void *arg)
+{
+ void **argv = arg;
+ struct sa *ip = argv[1];
+ (void)dst;
+ (void)dstlen;
+
+ if (0 == str_cmp(ifname, argv[0])) {
+ *ip = *gw;
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Get the IP-address of the default gateway
+ *
+ * @param af Address Family
+ * @param gw Returned Gateway address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_default_gateway_get(int af, struct sa *gw)
+{
+ char ifname[64];
+ void *argv[2];
+ int err;
+
+ if (!af || !gw)
+ return EINVAL;
+
+ err = net_rt_default_get(af, ifname, sizeof(ifname));
+ if (err)
+ return err;
+
+ argv[0] = ifname;
+ argv[1] = gw;
+
+ err = net_rt_list(net_rt_handler, argv);
+ if (err)
+ return err;
+
+ return 0;
+}
diff --git a/src/net/netstr.c b/src/net/netstr.c
new file mode 100644
index 0000000..3f356d3
--- /dev/null
+++ b/src/net/netstr.c
@@ -0,0 +1,50 @@
+/**
+ * @file netstr.c Network strings
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_net.h>
+
+
+/**
+ * Get the name of a protocol
+ *
+ * @param proto Protocol
+ *
+ * @return Protocol name
+ */
+const char *net_proto2name(int proto)
+{
+ switch (proto) {
+
+ case IPPROTO_UDP: return "UDP";
+ case IPPROTO_TCP: return "TCP";
+#ifdef IPPROTO_SCTP
+ case IPPROTO_SCTP: return "SCTP";
+#endif
+ default: return "???";
+ }
+}
+
+
+/**
+ * Get the name of a address family
+ *
+ * @param af Address family
+ *
+ * @return Address family name
+ */
+const char *net_af2name(int af)
+{
+ switch (af) {
+
+ case AF_UNSPEC: return "AF_UNSPEC";
+ case AF_INET: return "AF_INET";
+#ifdef HAVE_INET6
+ case AF_INET6: return "AF_INET6";
+#endif
+ default: return "???";
+ }
+}
diff --git a/src/net/posix/pif.c b/src/net/posix/pif.c
new file mode 100644
index 0000000..17e93ab
--- /dev/null
+++ b/src/net/posix/pif.c
@@ -0,0 +1,167 @@
+/**
+ * @file posix/pif.c POSIX network interface code
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#define __USE_POSIX 1 /**< Use POSIX code */
+#define __USE_XOPEN2K 1/**< Use POSIX.1:2001 code */
+#include <netdb.h>
+#define __USE_MISC 1 /**< Use MISC code */
+#include <net/if.h>
+#include <arpa/inet.h>
+/*#include <net/if_arp.h>*/
+#ifdef __sun
+#include <sys/sockio.h>
+#endif
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_net.h>
+
+
+#define DEBUG_MODULE "posixif"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/**
+ * Get IP address for a given network interface
+ *
+ * @param ifname Network interface name
+ * @param af Address Family
+ * @param ip Returned IP address
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * @deprecated Works for IPv4 only
+ */
+int net_if_getaddr4(const char *ifname, int af, struct sa *ip)
+{
+ struct addrinfo hints, *res, *r;
+ int error, err;
+
+ if (AF_INET != af)
+ return EAFNOSUPPORT;
+
+ memset(&hints, 0, sizeof(hints));
+ /* set-up hints structure */
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_socktype = SOCK_DGRAM;
+ error = getaddrinfo(NULL, "0", &hints, &res);
+ if (error) {
+ DEBUG_WARNING("get_ifaddr: getaddrinfo(): %s\n",
+ gai_strerror(error));
+ return EADDRNOTAVAIL;
+ }
+
+ err = ENOENT;
+ for (r = res; r; r = r->ai_next) {
+ struct ifreq ifrr;
+ int fd = -1;
+
+ fd = socket(r->ai_family, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ continue;
+ }
+
+ ifrr.ifr_addr.sa_family = r->ai_family;
+ str_ncpy(ifrr.ifr_name, ifname, sizeof(ifrr.ifr_name));
+
+ if (ioctl(fd, SIOCGIFADDR, &ifrr) < 0) {
+ err = errno;
+ goto next;
+ }
+
+ err = sa_set_sa(ip, &ifrr.ifr_ifru.ifru_addr);
+
+ next:
+ (void)close(fd);
+ }
+
+ freeaddrinfo(res);
+ return err;
+}
+
+
+/**
+ * Enumerate all network interfaces
+ *
+ * @param ifh Interface handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * @deprecated Works for IPv4 only
+ */
+int net_if_list(net_ifaddr_h *ifh, void *arg)
+{
+ struct ifreq ifrv[32], *ifr;
+ struct ifconf ifc;
+ int sockfd = -1;
+ int err = 0;
+
+ if (0 > (sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP))) {
+ err = errno;
+ DEBUG_WARNING("interface list: socket(): (%m)\n", err);
+ goto out;
+ }
+
+ ifc.ifc_len = sizeof(ifrv);
+ ifc.ifc_req = ifrv;
+
+ if (0 != ioctl(sockfd, SIOCGIFCONF, &ifc)) {
+ err = errno;
+ DEBUG_WARNING("interface list: ioctl SIOCFIFCONF: %m\n", err);
+ goto out;
+ }
+
+ for (ifr = ifc.ifc_req;
+ (char *)ifr < ((char *)ifc.ifc_buf + ifc.ifc_len);
+ ++ifr) {
+ struct ifreq ifrr;
+ struct sa sa;
+
+ if (ifr->ifr_addr.sa_data == (ifr+1)->ifr_addr.sa_data)
+ continue; /* duplicate, skip it */
+
+ if (ioctl(sockfd, SIOCGIFFLAGS, ifr))
+ continue; /* failed to get flags, skip it */
+
+#if 0
+ if (ifr->ifr_flags & IFF_LOOPBACK)
+ continue;
+#endif
+
+ if (!(ifr->ifr_flags & IFF_UP))
+ continue;
+
+ ifrr.ifr_addr.sa_family = AF_INET;
+ str_ncpy(ifrr.ifr_name, ifr->ifr_name, sizeof(ifrr.ifr_name));
+
+ if (ioctl(sockfd, SIOCGIFADDR, &ifrr) < 0) {
+ err = errno;
+ continue;
+ }
+
+ err = sa_set_sa(&sa, &ifrr.ifr_ifru.ifru_addr);
+ if (err) {
+ DEBUG_WARNING("if_list: sa_set_sa %m\n", err);
+ break;
+ }
+
+ if (ifh && ifh(ifr->ifr_name, &sa, arg))
+ break;
+ }
+
+ out:
+ if (sockfd >= 0)
+ (void)close(sockfd);
+
+ return err;
+}
diff --git a/src/net/rt.c b/src/net/rt.c
new file mode 100644
index 0000000..f5d2ef5
--- /dev/null
+++ b/src/net/rt.c
@@ -0,0 +1,158 @@
+/**
+ * @file net/rt.c Generic routing table code
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _BSD_SOURCE 1
+#define _DEFAULT_SOURCE 1
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_net.h>
+
+
+struct net_rt {
+ int af;
+ char *ifname;
+ size_t size;
+ int prefix;
+};
+
+
+static bool rt_debug_handler(const char *ifname, const struct sa *dst,
+ int dstlen, const struct sa *gw, void *arg)
+{
+ char addr[64];
+ struct re_printf *pf = arg;
+ int err = 0;
+
+ (void)re_snprintf(addr, sizeof(addr), "%j/%d", dst, dstlen);
+
+ err |= re_hprintf(pf, " %-44s", addr);
+ err |= re_hprintf(pf, "%-40j", gw);
+ err |= re_hprintf(pf, " %-15s ", ifname);
+
+#ifdef HAVE_INET6
+ if (AF_INET6 == sa_af(dst)) {
+ const struct sockaddr_in6 *sin6 = &dst->u.in6;
+ const struct in6_addr *in6 = &sin6->sin6_addr;
+
+ if (IN6_IS_ADDR_MULTICAST(in6))
+ err |= re_hprintf(pf, " MULTICAST");
+ if (IN6_IS_ADDR_LINKLOCAL(in6))
+ err |= re_hprintf(pf, " LINKLOCAL");
+ if (IN6_IS_ADDR_SITELOCAL(in6))
+ err |= re_hprintf(pf, " SITELOCAL");
+ }
+#endif
+
+ err |= re_hprintf(pf, "\n");
+
+ return 0 != err;
+}
+
+
+/**
+ * Dump the routing table
+ *
+ * @param pf Print function for output
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_rt_debug(struct re_printf *pf, void *unused)
+{
+ int err = 0;
+
+ (void)unused;
+
+ err |= re_hprintf(pf, "net routes:\n");
+
+ err |= re_hprintf(pf, " Destination "
+ "Next Hop"
+ " Iface "
+ "Type\n");
+
+ err |= net_rt_list(rt_debug_handler, pf);
+
+ return err;
+}
+
+
+static bool rt_default_get_handler(const char *_ifname, const struct sa *dst,
+ int dstlen, const struct sa *gw, void *arg)
+{
+ struct net_rt *rt = arg;
+
+ (void)dstlen;
+ (void)gw;
+
+ if (sa_af(dst) != rt->af)
+ return false;
+
+ switch (rt->af) {
+
+ case AF_INET:
+ if (0 == sa_in(dst)) {
+ str_ncpy(rt->ifname, _ifname, rt->size);
+ return true;
+ }
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ if (IN6_IS_ADDR_MULTICAST(&dst->u.in6.sin6_addr))
+ return false;
+ if (IN6_IS_ADDR_LINKLOCAL(&dst->u.in6.sin6_addr))
+ return false;
+
+ if (dstlen < rt->prefix) {
+ rt->prefix = dstlen;
+ str_ncpy(rt->ifname, _ifname, rt->size);
+ return false;
+ }
+ break;
+#endif
+ }
+
+ return false;
+}
+
+
+/**
+ * Get the interface name of the default route
+ *
+ * @param af Address family
+ * @param ifname Buffer for returned interface name
+ * @param size Size of buffer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_rt_default_get(int af, char *ifname, size_t size)
+{
+ struct net_rt rt;
+ int err;
+
+ rt.af = af;
+ rt.ifname = ifname;
+ rt.size = size;
+ rt.prefix = 256;
+
+ err = net_rt_list(rt_default_get_handler, &rt);
+ if (err)
+ return err;
+
+ return '\0' != ifname[0] ? 0 : EINVAL;
+}
+
+
+#ifndef HAVE_ROUTE_LIST
+/* We must provide a stub */
+int net_rt_list(net_rt_h *rth, void *arg)
+{
+ (void)rth;
+ (void)arg;
+ return ENOSYS;
+}
+#endif
diff --git a/src/net/sock.c b/src/net/sock.c
new file mode 100644
index 0000000..7b74dee
--- /dev/null
+++ b/src/net/sock.c
@@ -0,0 +1,91 @@
+/**
+ * @file net/sock.c Networking sockets code
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+
+
+#define DEBUG_MODULE "netsock"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static bool inited = false;
+
+
+#ifdef WIN32
+static int wsa_init(void)
+{
+ WORD wVersionRequested = MAKEWORD(2, 2);
+ WSADATA wsaData;
+ int err;
+
+ err = WSAStartup(wVersionRequested, &wsaData);
+ if (err != 0) {
+ DEBUG_WARNING("Could not load winsock (%m)\n", err);
+ return err;
+ }
+
+ /* Confirm that the WinSock DLL supports 2.2.*/
+ /* Note that if the DLL supports versions greater */
+ /* than 2.2 in addition to 2.2, it will still return */
+ /* 2.2 in wVersion since that is the version we */
+ /* requested. */
+ if (LOBYTE(wsaData.wVersion) != 2 ||
+ HIBYTE(wsaData.wVersion) != 2 ) {
+ WSACleanup();
+ DEBUG_WARNING("Bad winsock verion (%d.%d)\n",
+ HIBYTE(wsaData.wVersion),
+ LOBYTE(wsaData.wVersion));
+ return EINVAL;
+ }
+
+ return 0;
+}
+#endif
+
+
+/**
+ * Initialise network sockets
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_sock_init(void)
+{
+ int err = 0;
+
+ DEBUG_INFO("sock init: inited=%d\n", inited);
+
+ if (inited)
+ return 0;
+
+#ifdef WIN32
+ err = wsa_init();
+#endif
+
+ inited = true;
+
+ return err;
+}
+
+
+/**
+ * Cleanup network sockets
+ */
+void net_sock_close(void)
+{
+#ifdef WIN32
+ const int err = WSACleanup();
+ if (0 != err) {
+ DEBUG_WARNING("sock close: WSACleanup (%d)\n", err);
+ }
+#endif
+
+ inited = false;
+
+ DEBUG_INFO("sock close\n");
+}
diff --git a/src/net/sockopt.c b/src/net/sockopt.c
new file mode 100644
index 0000000..2385a69
--- /dev/null
+++ b/src/net/sockopt.c
@@ -0,0 +1,112 @@
+/**
+ * @file sockopt.c Networking socket options
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_net.h>
+
+
+#define DEBUG_MODULE "sockopt"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Platform independent buffer type cast */
+#ifdef WIN32
+#define BUF_CAST (char *)
+#else
+#define BUF_CAST
+#endif
+
+
+/**
+ * Set socket option blocking or non-blocking
+ *
+ * @param fd Socket file descriptor
+ * @param blocking true for blocking, false for non-blocking
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_sockopt_blocking_set(int fd, bool blocking)
+{
+#ifdef WIN32
+ unsigned long noblock = !blocking;
+ int err = 0;
+
+ if (0 != ioctlsocket(fd, FIONBIO, &noblock)) {
+ err = WSAGetLastError();
+ DEBUG_WARNING("nonblock set: fd=%d err=%d (%m)\n",
+ fd, err, err);
+ }
+ return err;
+#else
+ int flags;
+ int err = 0;
+
+ flags = fcntl(fd, F_GETFL);
+ if (-1 == flags) {
+ err = errno;
+ DEBUG_WARNING("sockopt set: fnctl F_GETFL: (%m)\n", err);
+ goto out;
+ }
+
+ if (blocking)
+ flags &= ~O_NONBLOCK;
+ else
+ flags |= O_NONBLOCK;
+
+ if (-1 == fcntl(fd, F_SETFL, flags)) {
+ err = errno;
+ DEBUG_WARNING("sockopt set: fcntl F_SETFL non-block (%m)\n",
+ err);
+ }
+
+ out:
+ return err;
+#endif
+}
+
+
+/**
+ * Set socket option to reuse address and port
+ *
+ * @param fd Socket file descriptor
+ * @param reuse true for reuse, false for no reuse
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_sockopt_reuse_set(int fd, bool reuse)
+{
+ int r = reuse;
+
+#ifdef SO_REUSEADDR
+ if (-1 == setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+ BUF_CAST &r, sizeof(r))) {
+ DEBUG_WARNING("SO_REUSEADDR: %m\n", errno);
+ return errno;
+ }
+#endif
+
+#ifdef SO_REUSEPORT
+ if (-1 == setsockopt(fd, SOL_SOCKET, SO_REUSEPORT,
+ BUF_CAST &r, sizeof(r))) {
+ DEBUG_INFO("SO_REUSEPORT: %m\n", errno);
+ return errno;
+ }
+#endif
+
+#if !defined(SO_REUSEADDR) && !defined(SO_REUSEPORT)
+ (void)r;
+ (void)fd;
+ (void)reuse;
+ return ENOSYS;
+#else
+ return 0;
+#endif
+}
diff --git a/src/net/win32/wif.c b/src/net/win32/wif.c
new file mode 100644
index 0000000..f24894f
--- /dev/null
+++ b/src/net/win32/wif.c
@@ -0,0 +1,127 @@
+/**
+ * @file wif.c Windows network interface code
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <winsock2.h>
+#include <iphlpapi.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_net.h>
+#include <re_sa.h>
+
+
+#define DEBUG_MODULE "wif"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/**
+ * List interfaces using GetAdaptersAddresses, which handles both
+ * IPv4 and IPv6 address families.
+ *
+ * This is available from Windows XP and Windows Server 2003
+ */
+static int if_list_gaa(net_ifaddr_h *ifh, void *arg)
+{
+ IP_ADAPTER_ADDRESSES addrv[64], *cur;
+ ULONG ret, len = sizeof(addrv);
+ const ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST;
+ HANDLE hLib;
+ union {
+ FARPROC proc;
+ ULONG (WINAPI *gaa)(ULONG, ULONG, PVOID,
+ PIP_ADAPTER_ADDRESSES, PULONG);
+ } u;
+ bool stop = false;
+ int err = 0;
+
+ hLib = LoadLibrary(TEXT("iphlpapi.dll"));
+ if (!hLib)
+ return ENOSYS;
+
+ u.proc = GetProcAddress(hLib, TEXT("GetAdaptersAddresses"));
+ if (!u.proc) {
+ err = ENOSYS;
+ goto out;
+ }
+
+ ret = (*u.gaa)(AF_UNSPEC, flags, NULL, addrv, &len);
+ if (ret != ERROR_SUCCESS) {
+ DEBUG_WARNING("if_list: GetAdaptersAddresses ret=%u\n", ret);
+ err = ENODEV;
+ goto out;
+ }
+
+ for (cur = addrv; cur && !stop; cur = cur->Next) {
+ PIP_ADAPTER_UNICAST_ADDRESS ip;
+
+ /* an interface can have many IP-addresses */
+ for (ip = cur->FirstUnicastAddress; ip; ip = ip->Next) {
+ struct sa sa;
+
+ sa_set_sa(&sa, ip->Address.lpSockaddr);
+
+ if (ifh && ifh(cur->AdapterName, &sa, arg)) {
+ stop = true;
+ break;
+ }
+ }
+ }
+
+ out:
+ FreeLibrary(hLib);
+
+ return err;
+}
+
+
+/**
+ * List interfaces using GetAdaptersInfo, which handles only IPv4 family.
+ *
+ * This is available from Windows 2000, and also works under Wine.
+ */
+static int if_list_gai(net_ifaddr_h *ifh, void *arg)
+{
+ IP_ADAPTER_INFO info[32];
+ PIP_ADAPTER_INFO p = info;
+ ULONG ulOutBufLen = sizeof(info);
+ DWORD ret;
+
+ ret = GetAdaptersInfo(info, &ulOutBufLen);
+ if (ret != ERROR_SUCCESS) {
+ DEBUG_WARNING("if_list: GetAdaptersInfo ret=%u\n", ret);
+ return ENODEV;
+ }
+
+ for (p = info; p; p = p->Next) {
+ struct sa sa;
+
+ if (sa_set_str(&sa, p->IpAddressList.IpAddress.String, 0))
+ continue;
+
+ if (ifh && ifh(p->AdapterName, &sa, arg))
+ break;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Enumerate all network interfaces
+ *
+ * @param ifh Interface handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_if_list(net_ifaddr_h *ifh, void *arg)
+{
+ /* Try both methods .. */
+
+ if (!if_list_gaa(ifh, arg))
+ return 0;
+
+ return if_list_gai(ifh, arg);
+}
diff --git a/src/odict/entry.c b/src/odict/entry.c
new file mode 100644
index 0000000..53daf2b
--- /dev/null
+++ b/src/odict/entry.c
@@ -0,0 +1,152 @@
+/**
+ * @file odict/entry.c Ordered Dictionary -- entry
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+#include "re_types.h"
+#include "re_fmt.h"
+#include "re_mem.h"
+#include "re_list.h"
+#include "re_hash.h"
+#include "re_odict.h"
+
+
+static void destructor(void *arg)
+{
+ struct odict_entry *e = arg;
+
+ switch (e->type) {
+
+ case ODICT_OBJECT:
+ case ODICT_ARRAY:
+ mem_deref(e->u.odict);
+ break;
+
+ case ODICT_STRING:
+ mem_deref(e->u.str);
+ break;
+
+ default:
+ break;
+ }
+
+ hash_unlink(&e->he);
+ list_unlink(&e->le);
+ mem_deref(e->key);
+}
+
+
+int odict_entry_add(struct odict *o, const char *key,
+ int type, ...)
+{
+ struct odict_entry *e;
+ va_list ap;
+ int err;
+
+ if (!o || !key)
+ return EINVAL;
+
+ e = mem_zalloc(sizeof(*e), destructor);
+ if (!e)
+ return ENOMEM;
+
+ e->type = type;
+
+ err = str_dup(&e->key, key);
+ if (err)
+ goto out;
+
+ va_start(ap, type);
+
+ switch (e->type) {
+
+ case ODICT_OBJECT:
+ case ODICT_ARRAY:
+ e->u.odict = mem_ref(va_arg(ap, struct odict *));
+ break;
+
+ case ODICT_STRING:
+ err = str_dup(&e->u.str, va_arg(ap, const char *));
+ break;
+
+ case ODICT_INT:
+ e->u.integer = va_arg(ap, int64_t);
+ break;
+
+ case ODICT_DOUBLE:
+ e->u.dbl = va_arg(ap, double);
+ break;
+
+ case ODICT_BOOL:
+ e->u.boolean = va_arg(ap, int);
+ break;
+
+ case ODICT_NULL:
+ break;
+
+ default:
+ err = EINVAL;
+ break;
+ }
+
+ va_end(ap);
+
+ if (err)
+ goto out;
+
+ list_append(&o->lst, &e->le, e);
+ hash_append(o->ht, hash_fast_str(e->key), &e->he, e);
+
+ out:
+ if (err)
+ mem_deref(e);
+
+ return err;
+}
+
+
+void odict_entry_del(struct odict *o, const char *key)
+{
+ mem_deref((struct odict_entry *)odict_lookup(o, key));
+}
+
+
+int odict_entry_debug(struct re_printf *pf, const struct odict_entry *e)
+{
+ int err;
+
+ if (!e)
+ return 0;
+
+ err = re_hprintf(pf, "%s", e->key);
+
+ switch (e->type) {
+
+ case ODICT_OBJECT:
+ case ODICT_ARRAY:
+ err |= re_hprintf(pf, ":%H", odict_debug, e->u.odict);
+ break;
+
+ case ODICT_STRING:
+ err |= re_hprintf(pf, ":%s", e->u.str);
+ break;
+
+ case ODICT_INT:
+ err |= re_hprintf(pf, ":%lli", e->u.integer);
+ break;
+
+ case ODICT_DOUBLE:
+ err |= re_hprintf(pf, ":%f", e->u.dbl);
+ break;
+
+ case ODICT_BOOL:
+ err |= re_hprintf(pf, ":%s", e->u.boolean ? "true" : "false");
+ break;
+
+ case ODICT_NULL:
+ break;
+ }
+
+ return err;
+}
diff --git a/src/odict/get.c b/src/odict/get.c
new file mode 100644
index 0000000..816a805
--- /dev/null
+++ b/src/odict/get.c
@@ -0,0 +1,89 @@
+/**
+ * @file get.c Ordered Dictionary -- high level accessors
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include "re_types.h"
+#include "re_fmt.h"
+#include "re_mem.h"
+#include "re_list.h"
+#include "re_hash.h"
+#include "re_odict.h"
+
+
+const struct odict_entry *odict_get_type(const struct odict *o,
+ enum odict_type type, const char *key)
+{
+ const struct odict_entry *entry;
+
+ if (!o || !key)
+ return NULL;
+
+ entry = odict_lookup(o, key);
+ if (!entry)
+ return NULL;
+
+ if (entry->type != type)
+ return NULL;
+
+ return entry;
+}
+
+
+const char *odict_string(const struct odict *o, const char *key)
+{
+ const struct odict_entry *entry;
+
+ entry = odict_get_type(o, ODICT_STRING, key);
+ if (!entry)
+ return NULL;
+
+ return entry->u.str;
+}
+
+
+bool odict_get_number(const struct odict *o, uint64_t *num, const char *key)
+{
+ const struct odict_entry *entry;
+
+ if (!o || !key)
+ return false;
+
+ entry = odict_lookup(o, key);
+ if (!entry)
+ return false;
+
+ switch (entry->type) {
+
+ case ODICT_DOUBLE:
+ if (num)
+ *num = (uint64_t)entry->u.dbl;
+ break;
+
+ case ODICT_INT:
+ if (num)
+ *num = entry->u.integer;
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+
+bool odict_get_boolean(const struct odict *o, bool *value, const char *key)
+{
+ const struct odict_entry *entry;
+
+ entry = odict_get_type(o, ODICT_BOOL, key);
+ if (!entry)
+ return false;
+
+ if (value)
+ *value = entry->u.boolean;
+
+ return true;
+}
diff --git a/src/odict/mod.mk b/src/odict/mod.mk
new file mode 100644
index 0000000..74023a3
--- /dev/null
+++ b/src/odict/mod.mk
@@ -0,0 +1,10 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 - 2015 Creytiv.com
+#
+
+SRCS += odict/entry.c
+SRCS += odict/odict.c
+SRCS += odict/type.c
+SRCS += odict/get.c
diff --git a/src/odict/odict.c b/src/odict/odict.c
new file mode 100644
index 0000000..8e87968
--- /dev/null
+++ b/src/odict/odict.c
@@ -0,0 +1,124 @@
+/**
+ * @file odict.c Ordered Dictionary
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include "re_types.h"
+#include "re_fmt.h"
+#include "re_mem.h"
+#include "re_list.h"
+#include "re_hash.h"
+#include "re_odict.h"
+
+
+static void destructor(void *arg)
+{
+ struct odict *o = arg;
+
+ hash_clear(o->ht);
+ list_flush(&o->lst);
+ mem_deref(o->ht);
+}
+
+
+int odict_alloc(struct odict **op, uint32_t hash_size)
+{
+ struct odict *o;
+ int err;
+
+ if (!op || !hash_size)
+ return EINVAL;
+
+ o = mem_zalloc(sizeof(*o), destructor);
+ if (!o)
+ return ENOMEM;
+
+ err = hash_alloc(&o->ht, hash_valid_size(hash_size));
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(o);
+ else
+ *op = o;
+
+ return err;
+}
+
+
+const struct odict_entry *odict_lookup(const struct odict *o, const char *key)
+{
+ struct le *le;
+
+ if (!o || !key)
+ return NULL;
+
+ le = list_head(hash_list(o->ht, hash_fast_str(key)));
+
+ while (le) {
+ const struct odict_entry *e = le->data;
+
+ if (!str_cmp(e->key, key))
+ return e;
+
+ le = le->next;
+ }
+
+ return NULL;
+}
+
+
+size_t odict_count(const struct odict *o, bool nested)
+{
+ struct le *le;
+ size_t n = 0;
+
+ if (!o)
+ return 0;
+
+ if (!nested)
+ return list_count(&o->lst);
+
+ for (le=o->lst.head; le; le=le->next) {
+
+ const struct odict_entry *e = le->data;
+
+ switch (e->type) {
+
+ case ODICT_OBJECT:
+ case ODICT_ARRAY:
+ n += odict_count(e->u.odict, true);
+ break;
+
+ default:
+ n += 1; /* count all entries */
+ break;
+ }
+ }
+
+ return n;
+}
+
+
+int odict_debug(struct re_printf *pf, const struct odict *o)
+{
+ struct le *le;
+ int err;
+
+ if (!o)
+ return 0;
+
+ err = re_hprintf(pf, "{");
+
+ for (le=o->lst.head; le; le=le->next) {
+
+ const struct odict_entry *e = le->data;
+
+ err |= re_hprintf(pf, " %H", odict_entry_debug, e);
+ }
+
+ err |= re_hprintf(pf, " }");
+
+ return err;
+}
diff --git a/src/odict/type.c b/src/odict/type.c
new file mode 100644
index 0000000..bb0dab9
--- /dev/null
+++ b/src/odict/type.c
@@ -0,0 +1,59 @@
+/**
+ * @file type.c Ordered Dictionary -- value types
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+#include "re_types.h"
+#include "re_fmt.h"
+#include "re_mem.h"
+#include "re_list.h"
+#include "re_hash.h"
+#include "re_odict.h"
+
+
+bool odict_type_iscontainer(enum odict_type type)
+{
+ switch (type) {
+
+ case ODICT_OBJECT:
+ case ODICT_ARRAY:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+
+bool odict_type_isreal(enum odict_type type)
+{
+ switch (type) {
+
+ case ODICT_STRING:
+ case ODICT_INT:
+ case ODICT_DOUBLE:
+ case ODICT_BOOL:
+ case ODICT_NULL:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+
+const char *odict_type_name(enum odict_type type)
+{
+ switch (type) {
+
+ case ODICT_OBJECT: return "Object";
+ case ODICT_ARRAY: return "Array";
+ case ODICT_STRING: return "String";
+ case ODICT_INT: return "Integer";
+ case ODICT_DOUBLE: return "Double";
+ case ODICT_BOOL: return "Boolean";
+ case ODICT_NULL: return "Null";
+ default: return "???";
+ }
+}
diff --git a/src/rtmp/README.md b/src/rtmp/README.md
new file mode 100644
index 0000000..5f1a6ee
--- /dev/null
+++ b/src/rtmp/README.md
@@ -0,0 +1,126 @@
+RTMP module
+-----------
+
+This module implements Real Time Messaging Protocol (RTMP) [1].
+
+
+
+
+Functional overview:
+-------------------
+
+```
+RTMP Specification v1.0 .......... YES
+RTMP with TCP transport .......... YES
+
+RTMPS (RTMP over TLS) ............ NO
+RTMPE (RTMP over Adobe Encryption) NO
+RTMPT (RTMP over HTTP) ........... NO
+RTMFP (RTMP over UDP) ............ NO
+
+Transport:
+Client ........................... YES
+Server ........................... YES
+IPv4 ............................. YES
+IPv6 ............................. YES
+DNS Resolving A/AAAA ............. YES
+
+RTMP Components:
+RTMP Handshake ................... YES
+RTMP Header encoding and decoding. YES
+RTMP Chunking .................... YES
+RTMP Dechunking .................. YES
+AMF0 (Action Message Format) ..... YES
+AMF3 (Action Message Format) ..... NO
+Send and receive audio/video ..... YES
+Regular and extended timestamp ... YES
+Multiple streams ................. YES
+```
+
+
+
+
+TODO:
+----
+
+- [x] improve AMF encoding API
+- [x] implement AMF transaction matching
+- [x] add support for Data Message
+- [x] add support for AMF Strict Array (type 10)
+- [ ] add support for TLS encryption
+- [x] add support for extended timestamp
+
+
+
+
+Protocol stack:
+--------------
+
+ .-------. .-------. .-------.
+ | AMF | | Audio | | Video |
+ '-------' '-------' '-------'
+ | | |
+ +----------+----------'
+ |
+ .-------.
+ | RTMP |
+ '-------'
+ |
+ |
+ .-------.
+ | TCP |
+ '-------'
+
+
+
+
+Message Sequence:
+----------------
+
+
+```
+Client Server
+
+|----------------- TCP Connect -------------->|
+| |
+| |
+| |
+|<-------------- 3-way Handshake ------------>|
+| |
+| |
+| |
+|----------- Command Message(connect) ------->| chunkid=3, streamid=0, tid=1
+| |
+|<------- Window Acknowledgement Size --------| chunkid=2, streamid=0
+| |
+|<----------- Set Peer Bandwidth -------------| chunkid=2, streamid=0
+| |
+|-------- Window Acknowledgement Size ------->|
+| |
+|<------ User Control Message(StreamBegin) ---| chunkid=2, streamid=0
+| |
+|<------------ Command Message ---------------| chunkid=3, streamid=0, tid=1
+| (_result- connect response) |
+```
+
+
+Interop:
+-------
+
+- Wowza Streaming Engine 4.7.1
+- Youtube service
+- FFmpeg's RTMP module
+
+
+
+
+References:
+----------
+
+[1] http://wwwimages.adobe.com/www.adobe.com/content/dam/acom/en/devnet/rtmp/pdf/rtmp_specification_1.0.pdf
+
+[2] https://wwwimages2.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10_1.pdf
+
+[3] https://en.wikipedia.org/wiki/Action_Message_Format
+
+[4] https://wwwimages2.adobe.com/content/dam/acom/en/devnet/pdf/amf0-file-format-specification.pdf
diff --git a/src/rtmp/amf.c b/src/rtmp/amf.c
new file mode 100644
index 0000000..c95763e
--- /dev/null
+++ b/src/rtmp/amf.c
@@ -0,0 +1,163 @@
+/**
+ * @file rtmp/amf.c Real Time Messaging Protocol (RTMP) -- AMF Commands
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_tcp.h>
+#include <re_sys.h>
+#include <re_odict.h>
+#include <re_rtmp.h>
+#include "rtmp.h"
+
+
+int rtmp_command_header_encode(struct mbuf *mb, const char *name, uint64_t tid)
+{
+ int err;
+
+ if (!mb || !name)
+ return EINVAL;
+
+ err = rtmp_amf_encode_string(mb, name);
+ err |= rtmp_amf_encode_number(mb, tid);
+
+ return err;
+}
+
+
+int rtmp_amf_command(const struct rtmp_conn *conn, uint32_t stream_id,
+ const char *command, unsigned body_propc, ...)
+{
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ if (!conn || !command)
+ return EINVAL;
+
+ mb = mbuf_alloc(512);
+ if (!mb)
+ return ENOMEM;
+
+ err = rtmp_amf_encode_string(mb, command);
+ if (err)
+ goto out;
+
+ if (body_propc) {
+ va_start(ap, body_propc);
+ err = rtmp_amf_vencode_object(mb, RTMP_AMF_TYPE_ROOT,
+ body_propc, &ap);
+ va_end(ap);
+ if (err)
+ goto out;
+ }
+
+ err = rtmp_send_amf_command(conn, 0, RTMP_CHUNK_ID_CONN,
+ RTMP_TYPE_AMF0,
+ stream_id, mb->buf, mb->end);
+
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+int rtmp_amf_reply(struct rtmp_conn *conn, uint32_t stream_id, bool success,
+ const struct odict *req,
+ unsigned body_propc, ...)
+{
+ struct mbuf *mb;
+ va_list ap;
+ uint64_t tid;
+ int err;
+
+ if (!conn || !req)
+ return EINVAL;
+
+ if (!odict_get_number(req, &tid, "1"))
+ return EPROTO;
+ if (tid == 0)
+ return EPROTO;
+
+ mb = mbuf_alloc(512);
+ if (!mb)
+ return ENOMEM;
+
+ err = rtmp_command_header_encode(mb,
+ success ? "_result" : "_error", tid);
+ if (err)
+ goto out;
+
+ if (body_propc) {
+ va_start(ap, body_propc);
+ err = rtmp_amf_vencode_object(mb, RTMP_AMF_TYPE_ROOT,
+ body_propc, &ap);
+ va_end(ap);
+ if (err)
+ goto out;
+ }
+
+ err = rtmp_send_amf_command(conn, 0, RTMP_CHUNK_ID_CONN,
+ RTMP_TYPE_AMF0,
+ stream_id, mb->buf, mb->end);
+
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+int rtmp_amf_data(const struct rtmp_conn *conn, uint32_t stream_id,
+ const char *command, unsigned body_propc, ...)
+{
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ if (!conn || !command)
+ return EINVAL;
+
+ mb = mbuf_alloc(512);
+ if (!mb)
+ return ENOMEM;
+
+ err = rtmp_amf_encode_string(mb, command);
+ if (err)
+ goto out;
+
+ if (body_propc) {
+ va_start(ap, body_propc);
+ err = rtmp_amf_vencode_object(mb, RTMP_AMF_TYPE_ROOT,
+ body_propc, &ap);
+ va_end(ap);
+ if (err)
+ goto out;
+ }
+
+ err = rtmp_send_amf_command(conn, 0, RTMP_CHUNK_ID_CONN,
+ RTMP_TYPE_DATA,
+ stream_id, mb->buf, mb->end);
+
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
diff --git a/src/rtmp/amf_dec.c b/src/rtmp/amf_dec.c
new file mode 100644
index 0000000..de35108
--- /dev/null
+++ b/src/rtmp/amf_dec.c
@@ -0,0 +1,235 @@
+/**
+ * @file rtmp/amf_dec.c Real Time Messaging Protocol (RTMP) -- AMF Decoding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_sys.h>
+#include <re_odict.h>
+#include <re_rtmp.h>
+#include "rtmp.h"
+
+
+enum {
+ AMF_HASH_SIZE = 32
+};
+
+
+static int amf_decode_value(struct odict *dict, const char *key,
+ struct mbuf *mb);
+
+
+static int amf_decode_object(struct odict *dict, struct mbuf *mb)
+{
+ char *key = NULL;
+ uint16_t len;
+ int err = 0;
+
+ while (mbuf_get_left(mb) > 0) {
+
+ if (mbuf_get_left(mb) < 2)
+ return ENODATA;
+
+ len = ntohs(mbuf_read_u16(mb));
+
+ if (len == 0) {
+ uint8_t val;
+
+ if (mbuf_get_left(mb) < 1)
+ return ENODATA;
+
+ val = mbuf_read_u8(mb);
+
+ if (val == RTMP_AMF_TYPE_OBJECT_END)
+ return 0;
+ else
+ return EBADMSG;
+ }
+
+ if (mbuf_get_left(mb) < len)
+ return ENODATA;
+
+ err = mbuf_strdup(mb, &key, len);
+ if (err)
+ return err;
+
+ err = amf_decode_value(dict, key, mb);
+
+ key = mem_deref(key);
+
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+
+static int amf_decode_value(struct odict *dict, const char *key,
+ struct mbuf *mb)
+{
+ union {
+ uint64_t i;
+ double f;
+ } num;
+ struct odict *object = NULL;
+ char *str = NULL;
+ uint32_t i, array_len;
+ uint8_t type;
+ uint16_t len;
+ bool boolean;
+ int err = 0;
+
+ if (mbuf_get_left(mb) < 1)
+ return ENODATA;
+
+ type = mbuf_read_u8(mb);
+
+ switch (type) {
+
+ case RTMP_AMF_TYPE_NUMBER:
+ if (mbuf_get_left(mb) < 8)
+ return ENODATA;
+
+ num.i = sys_ntohll(mbuf_read_u64(mb));
+
+ err = odict_entry_add(dict, key, ODICT_DOUBLE, num.f);
+ break;
+
+ case RTMP_AMF_TYPE_BOOLEAN:
+ if (mbuf_get_left(mb) < 1)
+ return ENODATA;
+
+ boolean = !!mbuf_read_u8(mb);
+
+ err = odict_entry_add(dict, key, ODICT_BOOL, boolean);
+ break;
+
+ case RTMP_AMF_TYPE_STRING:
+ if (mbuf_get_left(mb) < 2)
+ return ENODATA;
+
+ len = ntohs(mbuf_read_u16(mb));
+
+ if (mbuf_get_left(mb) < len)
+ return ENODATA;
+
+ err = mbuf_strdup(mb, &str, len);
+ if (err)
+ return err;
+
+ err = odict_entry_add(dict, key, ODICT_STRING, str);
+
+ mem_deref(str);
+ break;
+
+ case RTMP_AMF_TYPE_NULL:
+ err = odict_entry_add(dict, key, ODICT_NULL);
+ break;
+
+ case RTMP_AMF_TYPE_ECMA_ARRAY:
+ if (mbuf_get_left(mb) < 4)
+ return ENODATA;
+
+ array_len = ntohl(mbuf_read_u32(mb));
+
+ (void)array_len; /* ignore array length */
+
+ /* fallthrough */
+
+ case RTMP_AMF_TYPE_OBJECT:
+ err = odict_alloc(&object, 32);
+ if (err)
+ return err;
+
+ err = amf_decode_object(object, mb);
+ if (err) {
+ mem_deref(object);
+ return err;
+ }
+
+ err = odict_entry_add(dict, key, ODICT_OBJECT, object);
+
+ mem_deref(object);
+ break;
+
+ case RTMP_AMF_TYPE_STRICT_ARRAY:
+ if (mbuf_get_left(mb) < 4)
+ return ENODATA;
+
+ array_len = ntohl(mbuf_read_u32(mb));
+ if (!array_len)
+ return EPROTO;
+
+ err = odict_alloc(&object, 32);
+ if (err)
+ return err;
+
+ for (i=0; i<array_len; i++) {
+
+ char ix[32];
+
+ re_snprintf(ix, sizeof(ix), "%u", i);
+
+ err = amf_decode_value(object, ix, mb);
+ if (err) {
+ mem_deref(object);
+ return err;
+ }
+ }
+
+ err = odict_entry_add(dict, key, ODICT_ARRAY, object);
+
+ mem_deref(object);
+ break;
+
+ default:
+ err = EPROTO;
+ break;
+ }
+
+ return err;
+}
+
+
+int rtmp_amf_decode(struct odict **msgp, struct mbuf *mb)
+{
+ struct odict *msg;
+ unsigned ix = 0;
+ int err;
+
+ if (!msgp || !mb)
+ return EINVAL;
+
+ err = odict_alloc(&msg, AMF_HASH_SIZE);
+ if (err)
+ return err;
+
+ /* decode all entries on root-level */
+ while (mbuf_get_left(mb) > 0) {
+
+ char key[16];
+
+ re_snprintf(key, sizeof(key), "%u", ix++);
+
+ /* note: key is the numerical index */
+ err = amf_decode_value(msg, key, mb);
+ if (err)
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(msg);
+ else
+ *msgp = msg;
+
+ return err;
+}
diff --git a/src/rtmp/amf_enc.c b/src/rtmp/amf_enc.c
new file mode 100644
index 0000000..0ba157d
--- /dev/null
+++ b/src/rtmp/amf_enc.c
@@ -0,0 +1,245 @@
+/**
+ * @file rtmp/amf_enc.c Real Time Messaging Protocol (RTMP) -- AMF Encoding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_sys.h>
+#include <re_odict.h>
+#include <re_rtmp.h>
+#include "rtmp.h"
+
+
+static int rtmp_amf_encode_key(struct mbuf *mb, const char *key)
+{
+ size_t len;
+ int err;
+
+ len = str_len(key);
+
+ if (len > 65535)
+ return EOVERFLOW;
+
+ err = mbuf_write_u16(mb, htons((uint16_t)len));
+ err |= mbuf_write_str(mb, key);
+
+ return err;
+}
+
+
+static int rtmp_amf_encode_object_start(struct mbuf *mb)
+{
+ return mbuf_write_u8(mb, RTMP_AMF_TYPE_OBJECT);
+}
+
+
+static int rtmp_amf_encode_array_start(struct mbuf *mb,
+ uint8_t type, uint32_t length)
+{
+ int err;
+
+ err = mbuf_write_u8(mb, type);
+ err |= mbuf_write_u32(mb, htonl(length));
+
+ return err;
+}
+
+
+static int rtmp_amf_encode_object_end(struct mbuf *mb)
+{
+ int err;
+
+ err = mbuf_write_u16(mb, 0);
+ err |= mbuf_write_u8(mb, RTMP_AMF_TYPE_OBJECT_END);
+
+ return err;
+}
+
+
+static bool container_has_key(enum rtmp_amf_type type)
+{
+ switch (type) {
+
+ case RTMP_AMF_TYPE_OBJECT: return true;
+ case RTMP_AMF_TYPE_ECMA_ARRAY: return true;
+ case RTMP_AMF_TYPE_STRICT_ARRAY: return false;
+ default: return false;
+ }
+}
+
+
+int rtmp_amf_encode_number(struct mbuf *mb, double val)
+{
+ const union {
+ uint64_t i;
+ double f;
+ } num = {
+ .f = val
+ };
+ int err;
+
+ if (!mb)
+ return EINVAL;
+
+ err = mbuf_write_u8(mb, RTMP_AMF_TYPE_NUMBER);
+ err |= mbuf_write_u64(mb, sys_htonll(num.i));
+
+ return err;
+}
+
+
+int rtmp_amf_encode_boolean(struct mbuf *mb, bool boolean)
+{
+ int err;
+
+ if (!mb)
+ return EINVAL;
+
+ err = mbuf_write_u8(mb, RTMP_AMF_TYPE_BOOLEAN);
+ err |= mbuf_write_u8(mb, !!boolean);
+
+ return err;
+}
+
+
+int rtmp_amf_encode_string(struct mbuf *mb, const char *str)
+{
+ size_t len;
+ int err;
+
+ if (!mb || !str)
+ return EINVAL;
+
+ len = str_len(str);
+
+ if (len > 65535)
+ return EOVERFLOW;
+
+ err = mbuf_write_u8(mb, RTMP_AMF_TYPE_STRING);
+ err |= mbuf_write_u16(mb, htons((uint16_t)len));
+ err |= mbuf_write_str(mb, str);
+
+ return err;
+}
+
+
+int rtmp_amf_encode_null(struct mbuf *mb)
+{
+ if (!mb)
+ return EINVAL;
+
+ return mbuf_write_u8(mb, RTMP_AMF_TYPE_NULL);
+}
+
+
+/*
+ * NUMBER double
+ * BOOLEAN bool
+ * STRING const char *
+ * OBJECT const char *key sub-count
+ * NULL NULL
+ * ARRAY const char *key sub-count
+ */
+int rtmp_amf_vencode_object(struct mbuf *mb, enum rtmp_amf_type container,
+ unsigned propc, va_list *ap)
+{
+ bool encode_key;
+ unsigned i;
+ int err = 0;
+
+ if (!mb || !propc || !ap)
+ return EINVAL;
+
+ encode_key = container_has_key(container);
+
+ switch (container) {
+
+ case RTMP_AMF_TYPE_OBJECT:
+ err = rtmp_amf_encode_object_start(mb);
+ break;
+
+ case RTMP_AMF_TYPE_ECMA_ARRAY:
+ case RTMP_AMF_TYPE_STRICT_ARRAY:
+ err = rtmp_amf_encode_array_start(mb, container, propc);
+ break;
+
+ case RTMP_AMF_TYPE_ROOT:
+ break;
+
+ default:
+ return ENOTSUP;
+ }
+
+ if (err)
+ return err;
+
+ for (i=0; i<propc; i++) {
+
+ int type = va_arg(*ap, int);
+ const char *str;
+ int subcount;
+ double dbl;
+ bool b;
+
+ /* add key if ARRAY or OBJECT container */
+ if (encode_key) {
+ const char *key;
+
+ key = va_arg(*ap, const char *);
+ if (!key)
+ return EINVAL;
+
+ err = rtmp_amf_encode_key(mb, key);
+ if (err)
+ return err;
+ }
+
+ switch (type) {
+
+ case RTMP_AMF_TYPE_NUMBER:
+ dbl = va_arg(*ap, double);
+ err = rtmp_amf_encode_number(mb, dbl);
+ break;
+
+ case RTMP_AMF_TYPE_BOOLEAN:
+ b = va_arg(*ap, int);
+ err = rtmp_amf_encode_boolean(mb, b);
+ break;
+
+ case RTMP_AMF_TYPE_STRING:
+ str = va_arg(*ap, const char *);
+ err = rtmp_amf_encode_string(mb, str);
+ break;
+
+ case RTMP_AMF_TYPE_NULL:
+ err = rtmp_amf_encode_null(mb);
+ break;
+
+ case RTMP_AMF_TYPE_OBJECT:
+ case RTMP_AMF_TYPE_ECMA_ARRAY:
+ case RTMP_AMF_TYPE_STRICT_ARRAY:
+ /* recursive */
+ subcount = va_arg(*ap, int);
+ err = rtmp_amf_vencode_object(mb, type, subcount, ap);
+ break;
+
+ default:
+ return ENOTSUP;
+ }
+
+ if (err)
+ return err;
+ }
+
+ if (encode_key)
+ err = rtmp_amf_encode_object_end(mb);
+
+ return err;
+}
diff --git a/src/rtmp/chunk.c b/src/rtmp/chunk.c
new file mode 100644
index 0000000..bab08bb
--- /dev/null
+++ b/src/rtmp/chunk.c
@@ -0,0 +1,87 @@
+/**
+ * @file rtmp/chunk.c Real Time Messaging Protocol (RTMP) -- Chunking
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_sa.h>
+#include <re_tcp.h>
+#include <re_list.h>
+#include <re_rtmp.h>
+#include "rtmp.h"
+
+
+/*
+ * Stateless RTMP chunker
+ */
+int rtmp_chunker(unsigned format, uint32_t chunk_id,
+ uint32_t timestamp, uint32_t timestamp_delta,
+ uint8_t msg_type_id, uint32_t msg_stream_id,
+ const uint8_t *payload, size_t payload_len,
+ size_t max_chunk_sz, struct tcp_conn *tc)
+{
+ const uint8_t *pend = payload + payload_len;
+ struct rtmp_header hdr;
+ struct mbuf *mb;
+ size_t chunk_sz;
+ int err;
+
+ if (!payload || !payload_len || !max_chunk_sz || !tc)
+ return EINVAL;
+
+ mb = mbuf_alloc(payload_len + 256);
+ if (!mb)
+ return ENOMEM;
+
+ memset(&hdr, 0, sizeof(hdr));
+
+ hdr.format = format;
+ hdr.chunk_id = chunk_id;
+
+ hdr.timestamp = timestamp;
+ hdr.timestamp_delta = timestamp_delta;
+ hdr.length = (uint32_t)payload_len;
+ hdr.type_id = msg_type_id;
+ hdr.stream_id = msg_stream_id;
+
+ chunk_sz = min(payload_len, max_chunk_sz);
+
+ err = rtmp_header_encode(mb, &hdr);
+ err |= mbuf_write_mem(mb, payload, chunk_sz);
+ if (err)
+ goto out;
+
+ payload += chunk_sz;
+
+ hdr.format = 3;
+
+ while (payload < pend) {
+
+ const size_t len = pend - payload;
+
+ chunk_sz = min(len, max_chunk_sz);
+
+ err = rtmp_header_encode(mb, &hdr);
+ err |= mbuf_write_mem(mb, payload, chunk_sz);
+ if (err)
+ goto out;
+
+ payload += chunk_sz;
+ }
+
+ mb->pos = 0;
+
+ err = tcp_send(tc, mb);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
diff --git a/src/rtmp/conn.c b/src/rtmp/conn.c
new file mode 100644
index 0000000..f08a5a0
--- /dev/null
+++ b/src/rtmp/conn.c
@@ -0,0 +1,1023 @@
+/**
+ * @file rtmp/conn.c Real Time Messaging Protocol (RTMP) -- NetConnection
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_tcp.h>
+#include <re_sys.h>
+#include <re_odict.h>
+#include <re_dns.h>
+#include <re_uri.h>
+#include <re_rtmp.h>
+#include "rtmp.h"
+
+
+enum {
+ WINDOW_ACK_SIZE = 2500000
+};
+
+
+static int req_connect(struct rtmp_conn *conn);
+
+
+static void conn_destructor(void *data)
+{
+ struct rtmp_conn *conn = data;
+
+ list_flush(&conn->ctransl);
+ list_flush(&conn->streaml);
+
+ mem_deref(conn->dnsq6);
+ mem_deref(conn->dnsq4);
+ mem_deref(conn->dnsc);
+ mem_deref(conn->tc);
+ mem_deref(conn->mb);
+ mem_deref(conn->dechunk);
+ mem_deref(conn->uri);
+ mem_deref(conn->app);
+ mem_deref(conn->host);
+ mem_deref(conn->stream);
+}
+
+
+static int handle_amf_command(struct rtmp_conn *conn, uint32_t stream_id,
+ struct mbuf *mb)
+{
+ struct odict *msg = NULL;
+ const char *name;
+ int err;
+
+ err = rtmp_amf_decode(&msg, mb);
+ if (err)
+ return err;
+
+ name = odict_string(msg, "0");
+
+ if (conn->is_client &&
+ (0 == str_casecmp(name, "_result") ||
+ 0 == str_casecmp(name, "_error"))) {
+
+ /* forward response to transaction layer */
+ rtmp_ctrans_response(&conn->ctransl, msg);
+ }
+ else {
+ struct rtmp_stream *strm;
+
+ if (stream_id == 0) {
+ if (conn->cmdh)
+ conn->cmdh(msg, conn->arg);
+ }
+ else {
+ strm = rtmp_stream_find(conn, stream_id);
+ if (strm) {
+ if (strm->cmdh)
+ strm->cmdh(msg, strm->arg);
+ }
+ }
+ }
+
+ mem_deref(msg);
+
+ return 0;
+}
+
+
+static int handle_user_control_msg(struct rtmp_conn *conn, struct mbuf *mb)
+{
+ struct rtmp_stream *strm;
+ enum rtmp_event_type event;
+ uint32_t value;
+ int err;
+
+ if (mbuf_get_left(mb) < 6)
+ return EBADMSG;
+
+ event = ntohs(mbuf_read_u16(mb));
+ value = ntohl(mbuf_read_u32(mb));
+
+ switch (event) {
+
+ case RTMP_EVENT_STREAM_BEGIN:
+ case RTMP_EVENT_STREAM_EOF:
+ case RTMP_EVENT_STREAM_DRY:
+ case RTMP_EVENT_STREAM_IS_RECORDED:
+ case RTMP_EVENT_SET_BUFFER_LENGTH:
+
+ if (value != RTMP_CONTROL_STREAM_ID) {
+
+ strm = rtmp_stream_find(conn, value);
+ if (strm && strm->ctrlh)
+ strm->ctrlh(event, mb, strm->arg);
+ }
+ break;
+
+ case RTMP_EVENT_PING_REQUEST:
+
+ err = rtmp_control(conn, RTMP_TYPE_USER_CONTROL_MSG,
+ RTMP_EVENT_PING_RESPONSE, value);
+ if (err)
+ return err;
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+
+static int handle_data_message(struct rtmp_conn *conn, uint32_t stream_id,
+ struct mbuf *mb)
+{
+ struct rtmp_stream *strm;
+ struct odict *msg;
+ int err;
+
+ err = rtmp_amf_decode(&msg, mb);
+ if (err)
+ return err;
+
+ strm = rtmp_stream_find(conn, stream_id);
+ if (strm && strm->datah)
+ strm->datah(msg, strm->arg);
+
+ mem_deref(msg);
+
+ return 0;
+}
+
+
+static int rtmp_dechunk_handler(const struct rtmp_header *hdr,
+ struct mbuf *mb, void *arg)
+{
+ struct rtmp_conn *conn = arg;
+ struct rtmp_stream *strm;
+ uint32_t val;
+ uint32_t was;
+ uint8_t limit;
+ int err = 0;
+
+ switch (hdr->type_id) {
+
+ case RTMP_TYPE_SET_CHUNK_SIZE:
+ if (mbuf_get_left(mb) < 4)
+ return EBADMSG;
+
+ val = ntohl(mbuf_read_u32(mb));
+
+ val = val & 0x7fffffff;
+
+ rtmp_dechunker_set_chunksize(conn->dechunk, val);
+ break;
+
+ case RTMP_TYPE_ACKNOWLEDGEMENT:
+ if (mbuf_get_left(mb) < 4)
+ return EBADMSG;
+
+ val = ntohl(mbuf_read_u32(mb));
+ (void)val;
+ break;
+
+ case RTMP_TYPE_AMF0:
+ err = handle_amf_command(conn, hdr->stream_id, mb);
+ break;
+
+ case RTMP_TYPE_WINDOW_ACK_SIZE:
+ if (mbuf_get_left(mb) < 4)
+ return EBADMSG;
+
+ was = ntohl(mbuf_read_u32(mb));
+ if (was != 0)
+ conn->window_ack_size = was;
+ break;
+
+ case RTMP_TYPE_SET_PEER_BANDWIDTH:
+ if (mbuf_get_left(mb) < 5)
+ return EBADMSG;
+
+ was = ntohl(mbuf_read_u32(mb));
+ limit = mbuf_read_u8(mb);
+ (void)limit;
+
+ if (was != 0)
+ conn->window_ack_size = was;
+
+ err = rtmp_control(conn, RTMP_TYPE_WINDOW_ACK_SIZE,
+ (uint32_t)WINDOW_ACK_SIZE);
+ break;
+
+ case RTMP_TYPE_USER_CONTROL_MSG:
+ err = handle_user_control_msg(conn, mb);
+ break;
+
+ /* XXX: common code for audio+video */
+ case RTMP_TYPE_AUDIO:
+ strm = rtmp_stream_find(conn, hdr->stream_id);
+ if (strm) {
+ if (strm->auh) {
+ strm->auh(hdr->timestamp,
+ mb->buf, mb->end,
+ strm->arg);
+ }
+ }
+ break;
+
+ case RTMP_TYPE_VIDEO:
+ strm = rtmp_stream_find(conn, hdr->stream_id);
+ if (strm) {
+ if (strm->vidh) {
+ strm->vidh(hdr->timestamp,
+ mb->buf, mb->end,
+ strm->arg);
+ }
+ }
+ break;
+
+ case RTMP_TYPE_DATA:
+ err = handle_data_message(conn, hdr->stream_id, mb);
+ break;
+
+ default:
+ break;
+ }
+
+ return err;
+}
+
+
+static struct rtmp_conn *rtmp_conn_alloc(bool is_client,
+ rtmp_estab_h *estabh,
+ rtmp_command_h *cmdh,
+ rtmp_close_h *closeh,
+ void *arg)
+{
+ struct rtmp_conn *conn;
+ int err;
+
+ conn = mem_zalloc(sizeof(*conn), conn_destructor);
+ if (!conn)
+ return NULL;
+
+ conn->is_client = is_client;
+ conn->state = RTMP_STATE_UNINITIALIZED;
+
+ conn->send_chunk_size = RTMP_DEFAULT_CHUNKSIZE;
+ conn->window_ack_size = WINDOW_ACK_SIZE;
+
+ err = rtmp_dechunker_alloc(&conn->dechunk, RTMP_DEFAULT_CHUNKSIZE,
+ rtmp_dechunk_handler, conn);
+ if (err)
+ goto out;
+
+ /* must be above 2 */
+ conn->chunk_id_counter = RTMP_CHUNK_ID_CONN + 1;
+
+ conn->estabh = estabh;
+ conn->cmdh = cmdh;
+ conn->closeh = closeh;
+ conn->arg = arg;
+
+ out:
+ if (err)
+ return mem_deref(conn);
+
+ return conn;
+}
+
+
+static inline void set_state(struct rtmp_conn *conn,
+ enum rtmp_handshake_state state)
+{
+ conn->state = state;
+}
+
+
+static int send_packet(struct rtmp_conn *conn, const uint8_t *pkt, size_t len)
+{
+ struct mbuf *mb;
+ int err;
+
+ if (!conn || !pkt || !len)
+ return EINVAL;
+
+ mb = mbuf_alloc(len);
+ if (!mb)
+ return ENOMEM;
+
+ (void)mbuf_write_mem(mb, pkt, len);
+
+ mb->pos = 0;
+
+ err = tcp_send(conn->tc, mb);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+static int handshake_start(struct rtmp_conn *conn)
+{
+ uint8_t sig[1+RTMP_HANDSHAKE_SIZE];
+ int err;
+
+ sig[0] = RTMP_PROTOCOL_VERSION;
+ sig[1] = 0;
+ sig[2] = 0;
+ sig[3] = 0;
+ sig[4] = 0;
+ sig[5] = VER_MAJOR;
+ sig[6] = VER_MINOR;
+ sig[7] = VER_PATCH;
+ sig[8] = 0;
+ rand_bytes(sig + 9, sizeof(sig) - 9);
+
+ err = send_packet(conn, sig, sizeof(sig));
+ if (err)
+ return err;
+
+ set_state(conn, RTMP_STATE_VERSION_SENT);
+
+ return 0;
+}
+
+
+static void conn_close(struct rtmp_conn *conn, int err)
+{
+ rtmp_close_h *closeh;
+
+ conn->tc = mem_deref(conn->tc);
+ conn->dnsq6 = mem_deref(conn->dnsq6);
+ conn->dnsq4 = mem_deref(conn->dnsq4);
+
+ closeh = conn->closeh;
+ if (closeh) {
+ conn->closeh = NULL;
+ closeh(err, conn->arg);
+ }
+}
+
+
+static void tcp_estab_handler(void *arg)
+{
+ struct rtmp_conn *conn = arg;
+ int err = 0;
+
+ if (conn->is_client) {
+
+ err = handshake_start(conn);
+ }
+
+ if (err)
+ conn_close(conn, err);
+}
+
+
+/* Send AMF0 Command or Data */
+int rtmp_send_amf_command(const struct rtmp_conn *conn,
+ unsigned format, uint32_t chunk_id,
+ uint8_t type_id,
+ uint32_t msg_stream_id,
+ const uint8_t *cmd, size_t len)
+{
+ if (!conn || !cmd || !len)
+ return EINVAL;
+
+ return rtmp_chunker(format, chunk_id, 0, 0, type_id, msg_stream_id,
+ cmd, len, conn->send_chunk_size,
+ conn->tc);
+}
+
+
+static void connect_resp_handler(bool success, const struct odict *msg,
+ void *arg)
+{
+ struct rtmp_conn *conn = arg;
+ rtmp_estab_h *estabh;
+ (void)msg;
+
+ if (!success) {
+ conn_close(conn, EPROTO);
+ return;
+ }
+
+ conn->connected = true;
+
+ estabh = conn->estabh;
+ if (estabh) {
+ conn->estabh = NULL;
+ estabh(conn->arg);
+ }
+}
+
+
+static int send_connect(struct rtmp_conn *conn)
+{
+ const int ac = 0x0400; /* AAC */
+ const int vc = 0x0080; /* H264 */
+
+ return rtmp_amf_request(conn, RTMP_CONTROL_STREAM_ID, "connect",
+ connect_resp_handler, conn,
+ 1,
+ RTMP_AMF_TYPE_OBJECT, 8,
+ RTMP_AMF_TYPE_STRING, "app", conn->app,
+ RTMP_AMF_TYPE_STRING, "flashVer", "LNX 9,0,124,2",
+ RTMP_AMF_TYPE_STRING, "tcUrl", conn->uri,
+ RTMP_AMF_TYPE_BOOLEAN, "fpad", false,
+ RTMP_AMF_TYPE_NUMBER, "capabilities", 15.0,
+ RTMP_AMF_TYPE_NUMBER, "audioCodecs", (double)ac,
+ RTMP_AMF_TYPE_NUMBER, "videoCodecs", (double)vc,
+ RTMP_AMF_TYPE_NUMBER, "videoFunction", 1.0);
+}
+
+
+static int client_handle_packet(struct rtmp_conn *conn, struct mbuf *mb)
+{
+ uint8_t s0;
+ uint8_t s1[RTMP_HANDSHAKE_SIZE];
+ int err = 0;
+
+ switch (conn->state) {
+
+ case RTMP_STATE_VERSION_SENT:
+ if (mbuf_get_left(mb) < (1+RTMP_HANDSHAKE_SIZE))
+ return ENODATA;
+
+ s0 = mbuf_read_u8(mb);
+ if (s0 != RTMP_PROTOCOL_VERSION)
+ return EPROTO;
+
+ (void)mbuf_read_mem(mb, s1, sizeof(s1));
+
+ err = send_packet(conn, s1, sizeof(s1));
+ if (err)
+ return err;
+
+ set_state(conn, RTMP_STATE_ACK_SENT);
+ break;
+
+ case RTMP_STATE_ACK_SENT:
+ if (mbuf_get_left(mb) < RTMP_HANDSHAKE_SIZE)
+ return ENODATA;
+
+ /* S2 (ignored) */
+ mbuf_advance(mb, RTMP_HANDSHAKE_SIZE);
+
+ conn->send_chunk_size = 4096;
+ err = rtmp_control(conn, RTMP_TYPE_SET_CHUNK_SIZE,
+ conn->send_chunk_size);
+ if (err)
+ return err;
+
+ err = send_connect(conn);
+ if (err)
+ return err;
+
+ set_state(conn, RTMP_STATE_HANDSHAKE_DONE);
+ break;
+
+ case RTMP_STATE_HANDSHAKE_DONE:
+ err = rtmp_dechunker_receive(conn->dechunk, mb);
+ if (err)
+ return err;
+ break;
+
+ default:
+ return EPROTO;
+ }
+
+ return 0;
+}
+
+
+static int server_handle_packet(struct rtmp_conn *conn, struct mbuf *mb)
+{
+ uint8_t c0;
+ uint8_t c1[RTMP_HANDSHAKE_SIZE];
+ int err = 0;
+
+ switch (conn->state) {
+
+ case RTMP_STATE_UNINITIALIZED:
+ if (mbuf_get_left(mb) < 1)
+ return ENODATA;
+
+ c0 = mbuf_read_u8(mb);
+ if (c0 != RTMP_PROTOCOL_VERSION)
+ return EPROTO;
+
+ /* Send S0 + S1 */
+ err = handshake_start(conn);
+ if (err)
+ return err;
+ break;
+
+ case RTMP_STATE_VERSION_SENT:
+ if (mbuf_get_left(mb) < RTMP_HANDSHAKE_SIZE)
+ return ENODATA;
+
+ (void)mbuf_read_mem(mb, c1, sizeof(c1));
+
+ /* Copy C1 to S2 */
+ err = send_packet(conn, c1, sizeof(c1));
+ if (err)
+ return err;
+
+ set_state(conn, RTMP_STATE_ACK_SENT);
+ break;
+
+ case RTMP_STATE_ACK_SENT:
+ if (mbuf_get_left(mb) < RTMP_HANDSHAKE_SIZE)
+ return ENODATA;
+
+ /* C2 (ignored) */
+ mbuf_advance(mb, RTMP_HANDSHAKE_SIZE);
+
+ conn->send_chunk_size = 4096;
+ err = rtmp_control(conn, RTMP_TYPE_SET_CHUNK_SIZE,
+ conn->send_chunk_size);
+ if (err)
+ return err;
+
+ set_state(conn, RTMP_STATE_HANDSHAKE_DONE);
+ break;
+
+ case RTMP_STATE_HANDSHAKE_DONE:
+ err = rtmp_dechunker_receive(conn->dechunk, mb);
+ if (err)
+ return err;
+ break;
+
+ default:
+ return EPROTO;
+ }
+
+ return 0;
+}
+
+
+static void tcp_recv_handler(struct mbuf *mb_pkt, void *arg)
+{
+ struct rtmp_conn *conn = arg;
+ int err;
+
+ conn->total_bytes += mbuf_get_left(mb_pkt);
+
+ /* re-assembly of fragments */
+ if (conn->mb) {
+ const size_t len = mbuf_get_left(mb_pkt), pos = conn->mb->pos;
+
+ if ((mbuf_get_left(conn->mb) + len) > RTMP_MESSAGE_LEN_MAX) {
+ err = EOVERFLOW;
+ goto out;
+ }
+
+ conn->mb->pos = conn->mb->end;
+
+ err = mbuf_write_mem(conn->mb,
+ mbuf_buf(mb_pkt), mbuf_get_left(mb_pkt));
+ if (err)
+ goto out;
+
+ conn->mb->pos = pos;
+ }
+ else {
+ conn->mb = mem_ref(mb_pkt);
+ }
+
+ while (mbuf_get_left(conn->mb) > 0) {
+
+ size_t pos;
+ uint32_t nrefs;
+
+ pos = conn->mb->pos;
+
+ mem_ref(conn);
+
+ if (conn->is_client)
+ err = client_handle_packet(conn, conn->mb);
+ else
+ err = server_handle_packet(conn, conn->mb);
+
+ nrefs = mem_nrefs(conn);
+
+ mem_deref(conn);
+
+ if (nrefs == 1)
+ return;
+
+ if (!conn->tc)
+ return;
+
+ if (err) {
+
+ /* rewind */
+ conn->mb->pos = pos;
+
+ if (err == ENODATA)
+ err = 0;
+ break;
+ }
+
+
+ if (conn->mb->pos >= conn->mb->end) {
+ conn->mb = mem_deref(conn->mb);
+ break;
+ }
+ }
+
+ if (err)
+ goto out;
+
+ if (conn->total_bytes >= (conn->last_ack + conn->window_ack_size)) {
+
+ conn->last_ack = conn->total_bytes;
+
+ err = rtmp_control(conn, RTMP_TYPE_ACKNOWLEDGEMENT,
+ (uint32_t)conn->total_bytes);
+ if (err)
+ goto out;
+ }
+
+ out:
+ if (err)
+ conn_close(conn, err);
+}
+
+
+static void tcp_close_handler(int err, void *arg)
+{
+ struct rtmp_conn *conn = arg;
+
+ if (conn->is_client && !conn->connected && conn->srvc > 0) {
+ err = req_connect(conn);
+ if (!err)
+ return;
+ }
+
+ conn_close(conn, err);
+}
+
+
+static int req_connect(struct rtmp_conn *conn)
+{
+ const struct sa *addr;
+ int err = EINVAL;
+
+ while (conn->srvc > 0) {
+
+ --conn->srvc;
+
+ addr = &conn->srvv[conn->srvc];
+
+ conn->send_chunk_size = RTMP_DEFAULT_CHUNKSIZE;
+ conn->window_ack_size = WINDOW_ACK_SIZE;
+ conn->state = RTMP_STATE_UNINITIALIZED;
+ conn->last_ack = 0;
+ conn->total_bytes = 0;
+ conn->mb = mem_deref(conn->mb);
+ conn->tc = mem_deref(conn->tc);
+
+ rtmp_dechunker_set_chunksize(conn->dechunk,
+ RTMP_DEFAULT_CHUNKSIZE);
+
+ err = tcp_connect(&conn->tc, addr, tcp_estab_handler,
+ tcp_recv_handler, tcp_close_handler, conn);
+ if (!err)
+ break;
+ }
+
+ return err;
+}
+
+
+static bool rr_handler(struct dnsrr *rr, void *arg)
+{
+ struct rtmp_conn *conn = arg;
+
+ if (conn->srvc >= ARRAY_SIZE(conn->srvv))
+ return true;
+
+ switch (rr->type) {
+
+ case DNS_TYPE_A:
+ sa_set_in(&conn->srvv[conn->srvc++], rr->rdata.a.addr,
+ conn->port);
+ break;
+
+ case DNS_TYPE_AAAA:
+ sa_set_in6(&conn->srvv[conn->srvc++], rr->rdata.aaaa.addr,
+ conn->port);
+ break;
+ }
+
+ return false;
+}
+
+
+static void query_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg)
+{
+ struct rtmp_conn *conn = arg;
+ (void)hdr;
+ (void)authl;
+ (void)addl;
+
+ dns_rrlist_apply2(ansl, conn->host, DNS_TYPE_A, DNS_TYPE_AAAA,
+ DNS_CLASS_IN, true, rr_handler, conn);
+
+ /* wait for other (A/AAAA) query to complete */
+ if (conn->dnsq4 || conn->dnsq6)
+ return;
+
+ if (conn->srvc == 0) {
+ err = err ? err : EDESTADDRREQ;
+ goto out;
+ }
+
+ err = req_connect(conn);
+ if (err)
+ goto out;
+
+ return;
+
+ out:
+ conn_close(conn, err);
+}
+
+
+/**
+ * Connect to an RTMP server
+ *
+ * @param connp Pointer to allocated RTMP connection object
+ * @param dnsc DNS Client for resolving FQDN uris
+ * @param uri RTMP uri to connect to
+ * @param estabh Established handler
+ * @param cmdh Incoming command handler
+ * @param closeh Close handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * Example URIs:
+ *
+ * rtmp://a.rtmp.youtube.com/live2/my-stream
+ * rtmp://[::1]/vod/mp4:sample.mp4
+ */
+int rtmp_connect(struct rtmp_conn **connp, struct dnsc *dnsc, const char *uri,
+ rtmp_estab_h *estabh, rtmp_command_h *cmdh,
+ rtmp_close_h *closeh, void *arg)
+{
+ struct rtmp_conn *conn;
+ struct pl pl_hostport;
+ struct pl pl_host;
+ struct pl pl_port;
+ struct pl pl_app;
+ struct pl pl_stream;
+ int err;
+
+ if (!connp || !uri)
+ return EINVAL;
+
+ if (re_regex(uri, strlen(uri), "rtmp://[^/]+/[^/]+/[^]+",
+ &pl_hostport, &pl_app, &pl_stream))
+ return EINVAL;
+
+ if (uri_decode_hostport(&pl_hostport, &pl_host, &pl_port))
+ return EINVAL;
+
+ conn = rtmp_conn_alloc(true, estabh, cmdh, closeh, arg);
+ if (!conn)
+ return ENOMEM;
+
+ conn->port = pl_isset(&pl_port) ? pl_u32(&pl_port) : RTMP_PORT;
+
+ err = pl_strdup(&conn->app, &pl_app);
+ err |= pl_strdup(&conn->stream, &pl_stream);
+ err |= str_dup(&conn->uri, uri);
+ if (err)
+ goto out;
+
+ if (0 == sa_set(&conn->srvv[0], &pl_host, conn->port)) {
+
+ conn->srvc = 1;
+
+ err = req_connect(conn);
+ if (err)
+ goto out;
+ }
+ else {
+#ifdef HAVE_INET6
+ struct sa tmp;
+#endif
+
+ if (!dnsc) {
+ err = EINVAL;
+ goto out;
+ }
+
+ err = pl_strdup(&conn->host, &pl_host);
+ if (err)
+ goto out;
+
+ conn->dnsc = mem_ref(dnsc);
+
+ err = dnsc_query(&conn->dnsq4, dnsc, conn->host, DNS_TYPE_A,
+ DNS_CLASS_IN, true, query_handler, conn);
+ if (err)
+ goto out;
+
+#ifdef HAVE_INET6
+ if (0 == net_default_source_addr_get(AF_INET6, &tmp)) {
+
+ err = dnsc_query(&conn->dnsq6, dnsc, conn->host,
+ DNS_TYPE_AAAA, DNS_CLASS_IN,
+ true, query_handler, conn);
+ if (err)
+ goto out;
+ }
+#endif
+ }
+
+ out:
+ if (err)
+ mem_deref(conn);
+ else
+ *connp = conn;
+
+ return err;
+}
+
+
+/**
+ * Accept an incoming TCP connection creating an RTMP Server connection
+ *
+ * @param connp Pointer to allocated RTMP connection object
+ * @param ts TCP socket with pending connection
+ * @param cmdh Incoming command handler
+ * @param closeh Close handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtmp_accept(struct rtmp_conn **connp, struct tcp_sock *ts,
+ rtmp_command_h *cmdh, rtmp_close_h *closeh, void *arg)
+{
+ struct rtmp_conn *conn;
+ int err;
+
+ if (!connp || !ts)
+ return EINVAL;
+
+ conn = rtmp_conn_alloc(false, NULL, cmdh, closeh, arg);
+ if (!conn)
+ return ENOMEM;
+
+ err = tcp_accept(&conn->tc, ts, tcp_estab_handler,
+ tcp_recv_handler, tcp_close_handler, conn);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(conn);
+ else
+ *connp = conn;
+
+ return err;
+}
+
+
+int rtmp_conn_send_msg(const struct rtmp_conn *conn,
+ unsigned format, uint32_t chunk_id,
+ uint32_t timestamp, uint32_t timestamp_delta,
+ uint8_t msg_type_id, uint32_t msg_stream_id,
+ const uint8_t *payload, size_t payload_len)
+{
+ if (!conn || !payload || !payload_len)
+ return EINVAL;
+
+ return rtmp_chunker(format, chunk_id, timestamp, timestamp_delta,
+ msg_type_id, msg_stream_id, payload, payload_len,
+ conn->send_chunk_size,
+ conn->tc);
+}
+
+
+unsigned rtmp_conn_assign_chunkid(struct rtmp_conn *conn)
+{
+ if (!conn)
+ return 0;
+
+ return ++conn->chunk_id_counter;
+}
+
+
+uint64_t rtmp_conn_assign_tid(struct rtmp_conn *conn)
+{
+ if (!conn)
+ return 0;
+
+ return ++conn->tid_counter;
+}
+
+
+/**
+ * Get the underlying TCP connection from an RTMP connection
+ *
+ * @param conn RTMP Connection
+ *
+ * @return TCP-Connection
+ */
+struct tcp_conn *rtmp_conn_tcpconn(const struct rtmp_conn *conn)
+{
+ return conn ? conn->tc : NULL;
+}
+
+
+/**
+ * Get the RTMP connection stream name from rtmp_connect
+ *
+ * @param conn RTMP Connection
+ *
+ * @return RTMP Stream name or NULL
+ */
+const char *rtmp_conn_stream(const struct rtmp_conn *conn)
+{
+ return conn ? conn->stream : NULL;
+}
+
+
+/**
+ * Set callback handlers for the RTMP connection
+ *
+ * @param conn RTMP connection
+ * @param cmdh Incoming command handler
+ * @param closeh Close handler
+ * @param arg Handler argument
+ */
+void rtmp_set_handlers(struct rtmp_conn *conn, rtmp_command_h *cmdh,
+ rtmp_close_h *closeh, void *arg)
+{
+ if (!conn)
+ return;
+
+ conn->cmdh = cmdh;
+ conn->closeh = closeh;
+ conn->arg = arg;
+}
+
+
+static const char *rtmp_handshake_name(enum rtmp_handshake_state state)
+{
+ switch (state) {
+
+ case RTMP_STATE_UNINITIALIZED: return "UNINITIALIZED";
+ case RTMP_STATE_VERSION_SENT: return "VERSION_SENT";
+ case RTMP_STATE_ACK_SENT: return "ACK_SENT";
+ case RTMP_STATE_HANDSHAKE_DONE: return "HANDSHAKE_DONE";
+ default: return "?";
+ }
+}
+
+
+int rtmp_conn_debug(struct re_printf *pf, const struct rtmp_conn *conn)
+{
+ int err = 0;
+
+ if (!conn)
+ return 0;
+
+ err |= re_hprintf(pf, "role: %s\n",
+ conn->is_client ? "Client" : "Server");
+ err |= re_hprintf(pf, "state: %s\n",
+ rtmp_handshake_name(conn->state));
+ err |= re_hprintf(pf, "connected: %d\n", conn->connected);
+ err |= re_hprintf(pf, "chunk_size: send=%u\n",
+ conn->send_chunk_size);
+ err |= re_hprintf(pf, "bytes: %zu\n", conn->total_bytes);
+ err |= re_hprintf(pf, "streams: %u\n",
+ list_count(&conn->streaml));
+
+ if (conn->is_client) {
+ err |= re_hprintf(pf, "uri: %s\n", conn->uri);
+ err |= re_hprintf(pf, "app: %s\n", conn->app);
+ err |= re_hprintf(pf, "stream: %s\n", conn->stream);
+ }
+
+ err |= re_hprintf(pf, "%H\n", rtmp_dechunker_debug, conn->dechunk);
+
+ return err;
+}
diff --git a/src/rtmp/control.c b/src/rtmp/control.c
new file mode 100644
index 0000000..f479a13
--- /dev/null
+++ b/src/rtmp/control.c
@@ -0,0 +1,106 @@
+/**
+ * @file rtmp/control.c Real Time Messaging Protocol (RTMP) -- Control
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_rtmp.h>
+#include "rtmp.h"
+
+
+/**
+ * Send an RTMP control message
+ *
+ * @param conn RTMP connection
+ * @param type RTMP Packet type
+ * @param ... Optional packet arguments
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtmp_control(const struct rtmp_conn *conn, enum rtmp_packet_type type, ...)
+{
+ struct mbuf *mb;
+ uint32_t u32;
+ uint16_t event;
+ va_list ap;
+ int err = 0;
+
+ if (!conn)
+ return EINVAL;
+
+ mb = mbuf_alloc(8);
+ if (!mb)
+ return ENOMEM;
+
+ va_start(ap, type);
+
+ switch (type) {
+
+ case RTMP_TYPE_SET_CHUNK_SIZE:
+ case RTMP_TYPE_WINDOW_ACK_SIZE:
+ case RTMP_TYPE_ACKNOWLEDGEMENT:
+ u32 = va_arg(ap, uint32_t);
+ err = mbuf_write_u32(mb, htonl(u32));
+ break;
+
+ case RTMP_TYPE_USER_CONTROL_MSG:
+ event = va_arg(ap, unsigned);
+ err = mbuf_write_u16(mb, htons(event));
+ err |= mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ break;
+
+ case RTMP_TYPE_SET_PEER_BANDWIDTH:
+ err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ err |= mbuf_write_u8(mb, va_arg(ap, unsigned));
+ break;
+
+ default:
+ err = ENOTSUP;
+ break;
+ }
+
+ va_end(ap);
+
+ if (err)
+ goto out;
+
+ err = rtmp_conn_send_msg(conn, 0, RTMP_CHUNK_ID_CONTROL, 0, 0, type,
+ RTMP_CONTROL_STREAM_ID, mb->buf, mb->end);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Get the event name as a string
+ *
+ * @param event RTMP Event type
+ *
+ * @return Name of the event as a string
+ */
+const char *rtmp_event_name(enum rtmp_event_type event)
+{
+ switch (event) {
+
+ case RTMP_EVENT_STREAM_BEGIN: return "StreamBegin";
+ case RTMP_EVENT_STREAM_EOF: return "StreamEOF";
+ case RTMP_EVENT_STREAM_DRY: return "StreamDry";
+ case RTMP_EVENT_SET_BUFFER_LENGTH: return "SetBufferLength";
+ case RTMP_EVENT_STREAM_IS_RECORDED: return "StreamIsRecorded";
+ case RTMP_EVENT_PING_REQUEST: return "PingRequest";
+ case RTMP_EVENT_PING_RESPONSE: return "PingResponse";
+ default: return "?";
+ }
+}
diff --git a/src/rtmp/ctrans.c b/src/rtmp/ctrans.c
new file mode 100644
index 0000000..6e56f84
--- /dev/null
+++ b/src/rtmp/ctrans.c
@@ -0,0 +1,138 @@
+/**
+ * @file rtmp/ctrans.c Real Time Messaging Protocol -- AMF Client Transactions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_tcp.h>
+#include <re_sys.h>
+#include <re_odict.h>
+#include <re_rtmp.h>
+#include "rtmp.h"
+
+
+struct rtmp_ctrans {
+ struct le le;
+ uint64_t tid;
+ rtmp_resp_h *resph;
+ void *arg;
+};
+
+
+static void ctrans_destructor(void *data)
+{
+ struct rtmp_ctrans *ct = data;
+
+ list_unlink(&ct->le);
+}
+
+
+static struct rtmp_ctrans *rtmp_ctrans_find(const struct list *ctransl,
+ uint64_t tid)
+{
+ struct le *le;
+
+ for (le = list_head(ctransl); le; le = le->next) {
+ struct rtmp_ctrans *ct = le->data;
+
+ if (tid == ct->tid)
+ return ct;
+ }
+
+ return NULL;
+}
+
+
+int rtmp_amf_request(struct rtmp_conn *conn, uint32_t stream_id,
+ const char *command,
+ rtmp_resp_h *resph, void *arg, unsigned body_propc, ...)
+{
+ struct rtmp_ctrans *ct = NULL;
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ if (!conn || !command || !resph)
+ return EINVAL;
+
+ mb = mbuf_alloc(512);
+ if (!mb)
+ return ENOMEM;
+
+ ct = mem_zalloc(sizeof(*ct), ctrans_destructor);
+ if (!ct) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ ct->tid = rtmp_conn_assign_tid(conn);
+ ct->resph = resph;
+ ct->arg = arg;
+
+ err = rtmp_command_header_encode(mb, command, ct->tid);
+ if (err)
+ goto out;
+
+ if (body_propc) {
+ va_start(ap, body_propc);
+ err = rtmp_amf_vencode_object(mb, RTMP_AMF_TYPE_ROOT,
+ body_propc, &ap);
+ va_end(ap);
+ if (err)
+ goto out;
+ }
+
+ err = rtmp_send_amf_command(conn, 0, RTMP_CHUNK_ID_CONN,
+ RTMP_TYPE_AMF0,
+ stream_id, mb->buf, mb->end);
+ if (err)
+ goto out;
+
+ list_append(&conn->ctransl, &ct->le, ct);
+
+ out:
+ mem_deref(mb);
+ if (err)
+ mem_deref(ct);
+
+ return err;
+}
+
+
+int rtmp_ctrans_response(const struct list *ctransl,
+ const struct odict *msg)
+{
+ struct rtmp_ctrans *ct;
+ uint64_t tid;
+ bool success;
+ rtmp_resp_h *resph;
+ void *arg;
+
+ if (!ctransl || !msg)
+ return EINVAL;
+
+ success = (0 == str_casecmp(odict_string(msg, "0"), "_result"));
+
+ if (!odict_get_number(msg, &tid, "1"))
+ return EPROTO;
+
+ ct = rtmp_ctrans_find(ctransl, tid);
+ if (!ct)
+ return ENOENT;
+
+ resph = ct->resph;
+ arg = ct->arg;
+
+ mem_deref(ct);
+
+ resph(success, msg, arg);
+
+ return 0;
+}
diff --git a/src/rtmp/dechunk.c b/src/rtmp/dechunk.c
new file mode 100644
index 0000000..df82d78
--- /dev/null
+++ b/src/rtmp/dechunk.c
@@ -0,0 +1,292 @@
+/**
+ * @file rtmp/dechunk.c Real Time Messaging Protocol (RTMP) -- Dechunking
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_sa.h>
+#include <re_rtmp.h>
+#include "rtmp.h"
+
+
+enum {
+ MAX_CHUNKS = 64,
+};
+
+
+struct rtmp_chunk {
+ struct le le;
+ struct rtmp_header hdr;
+ struct mbuf *mb;
+};
+
+/** Defines the RTMP Dechunker */
+struct rtmp_dechunker {
+ struct list chunkl; /* struct rtmp_chunk */
+ size_t chunk_sz;
+ rtmp_dechunk_h *chunkh;
+ void *arg;
+};
+
+
+static void destructor(void *data)
+{
+ struct rtmp_dechunker *rd = data;
+
+ list_flush(&rd->chunkl);
+}
+
+
+static void chunk_destructor(void *data)
+{
+ struct rtmp_chunk *chunk = data;
+
+ list_unlink(&chunk->le);
+ mem_deref(chunk->mb);
+}
+
+
+static struct rtmp_chunk *create_chunk(struct list *chunkl,
+ const struct rtmp_header *hdr)
+{
+ struct rtmp_chunk *chunk;
+
+ chunk = mem_zalloc(sizeof(*chunk), chunk_destructor);
+ if (!chunk)
+ return NULL;
+
+ chunk->hdr = *hdr;
+
+ list_append(chunkl, &chunk->le, chunk);
+
+ return chunk;
+}
+
+
+static struct rtmp_chunk *find_chunk(const struct list *chunkl,
+ uint32_t chunk_id)
+{
+ struct le *le;
+
+ for (le = list_head(chunkl); le; le = le->next) {
+
+ struct rtmp_chunk *chunk = le->data;
+
+ if (chunk_id == chunk->hdr.chunk_id)
+ return chunk;
+ }
+
+ return NULL;
+}
+
+
+/*
+ * Stateful RTMP de-chunker for receiving complete messages
+ */
+int rtmp_dechunker_alloc(struct rtmp_dechunker **rdp, size_t chunk_sz,
+ rtmp_dechunk_h *chunkh, void *arg)
+{
+ struct rtmp_dechunker *rd;
+
+ if (!rdp || !chunk_sz || !chunkh)
+ return EINVAL;
+
+ rd = mem_zalloc(sizeof(*rd), destructor);
+ if (!rd)
+ return ENOMEM;
+
+ rd->chunk_sz = chunk_sz;
+
+ rd->chunkh = chunkh;
+ rd->arg = arg;
+
+ *rdp = rd;
+
+ return 0;
+}
+
+
+int rtmp_dechunker_receive(struct rtmp_dechunker *rd, struct mbuf *mb)
+{
+ struct rtmp_header hdr;
+ struct rtmp_chunk *chunk;
+ size_t chunk_sz, left, msg_len;
+ int err;
+
+ if (!rd || !mb)
+ return EINVAL;
+
+ err = rtmp_header_decode(&hdr, mb);
+ if (err)
+ return err;
+
+ /* find preceding chunk, from chunk id */
+ chunk = find_chunk(&rd->chunkl, hdr.chunk_id);
+ if (!chunk) {
+
+ /* only type 0 can create a new chunk stream */
+ if (hdr.format == 0) {
+ if (list_count(&rd->chunkl) > MAX_CHUNKS)
+ return EOVERFLOW;
+
+ chunk = create_chunk(&rd->chunkl, &hdr);
+ if (!chunk)
+ return ENOMEM;
+ }
+ else
+ return ENOENT;
+ }
+
+ switch (hdr.format) {
+
+ case 0:
+ case 1:
+ case 2:
+ if (hdr.format == 0) {
+
+ /* copy the whole header */
+ chunk->hdr = hdr;
+ }
+ else if (hdr.format == 1) {
+
+ chunk->hdr.timestamp_delta = hdr.timestamp_delta;
+ chunk->hdr.length = hdr.length;
+ chunk->hdr.type_id = hdr.type_id;
+ }
+ else if (hdr.format == 2) {
+
+ chunk->hdr.timestamp_delta = hdr.timestamp_delta;
+ }
+
+ msg_len = chunk->hdr.length;
+
+ chunk_sz = min(msg_len, rd->chunk_sz);
+
+ if (mbuf_get_left(mb) < chunk_sz)
+ return ENODATA;
+
+ mem_deref(chunk->mb);
+ chunk->mb = mbuf_alloc(msg_len);
+ if (!chunk->mb)
+ return ENOMEM;
+
+ err = mbuf_read_mem(mb, chunk->mb->buf, chunk_sz);
+ if (err)
+ return err;
+
+ chunk->mb->pos = chunk_sz;
+ chunk->mb->end = chunk_sz;
+
+ chunk->hdr.format = hdr.format;
+ chunk->hdr.ext_ts = hdr.ext_ts;
+
+ if (hdr.format == 1 || hdr.format == 2)
+ chunk->hdr.timestamp += hdr.timestamp_delta;
+ break;
+
+ case 3:
+ if (chunk->hdr.ext_ts) {
+
+ uint32_t ext_ts;
+
+ if (mbuf_get_left(mb) < 4)
+ return ENODATA;
+
+ ext_ts = ntohl(mbuf_read_u32(mb));
+
+ if (chunk->hdr.format == 0)
+ chunk->hdr.timestamp = ext_ts;
+ else
+ chunk->hdr.timestamp_delta = ext_ts;
+ }
+
+ if (!chunk->mb) {
+
+ chunk->mb = mbuf_alloc(chunk->hdr.length);
+ if (!chunk->mb)
+ return ENOMEM;
+
+ if (chunk->hdr.format == 0) {
+ chunk->hdr.timestamp_delta =
+ chunk->hdr.timestamp;
+ }
+
+ chunk->hdr.timestamp += chunk->hdr.timestamp_delta;
+ }
+
+ left = mbuf_get_space(chunk->mb);
+
+ chunk_sz = min(left, rd->chunk_sz);
+
+ if (mbuf_get_left(mb) < chunk_sz)
+ return ENODATA;
+
+ err = mbuf_read_mem(mb, mbuf_buf(chunk->mb), chunk_sz);
+ if (err)
+ return err;
+
+ chunk->mb->pos += chunk_sz;
+ chunk->mb->end += chunk_sz;
+ break;
+
+ default:
+ return EPROTO;
+ }
+
+ if (chunk->mb->pos >= chunk->mb->size) {
+
+ struct mbuf *buf;
+
+ chunk->mb->pos = 0;
+
+ buf = chunk->mb;
+ chunk->mb = NULL;
+
+ err = rd->chunkh(&chunk->hdr, buf, rd->arg);
+
+ mem_deref(buf);
+ }
+
+ return err;
+}
+
+
+void rtmp_dechunker_set_chunksize(struct rtmp_dechunker *rd, size_t chunk_sz)
+{
+ if (!rd || !chunk_sz)
+ return;
+
+ rd->chunk_sz = chunk_sz;
+}
+
+
+int rtmp_dechunker_debug(struct re_printf *pf, const struct rtmp_dechunker *rd)
+{
+ struct le *le;
+ int err;
+
+ if (!rd)
+ return 0;
+
+ err = re_hprintf(pf, "Dechunker Debug:\n");
+
+ err |= re_hprintf(pf, "chunk list: (%u)\n", list_count(&rd->chunkl));
+
+ for (le = rd->chunkl.head; le; le = le->next) {
+
+ const struct rtmp_chunk *msg = le->data;
+
+ err |= re_hprintf(pf, ".. %H\n",
+ rtmp_header_print, &msg->hdr);
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
diff --git a/src/rtmp/hdr.c b/src/rtmp/hdr.c
new file mode 100644
index 0000000..38bd370
--- /dev/null
+++ b/src/rtmp/hdr.c
@@ -0,0 +1,279 @@
+/**
+ * @file rtmp/hdr.c Real Time Messaging Protocol (RTMP) -- Headers
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_sys.h>
+#include <re_rtmp.h>
+#include "rtmp.h"
+
+
+enum {
+ RTMP_CHUNK_ID_MIN = 3,
+ RTMP_CHUNK_ID_MAX = 65599, /* 65535 + 64 */
+
+ RTMP_CHUNK_OFFSET = 64,
+ TIMESTAMP_24MAX = 0x00ffffff,
+};
+
+
+static int mbuf_write_u24_hton(struct mbuf *mb, uint32_t u24)
+{
+ int err = 0;
+
+ err |= mbuf_write_u8(mb, u24 >> 16);
+ err |= mbuf_write_u8(mb, u24 >> 8);
+ err |= mbuf_write_u8(mb, u24 >> 0);
+
+ return err;
+}
+
+
+static uint32_t mbuf_read_u24_ntoh(struct mbuf *mb)
+{
+ uint32_t u24;
+
+ u24 = (uint32_t)mbuf_read_u8(mb) << 16;
+ u24 |= (uint32_t)mbuf_read_u8(mb) << 8;
+ u24 |= (uint32_t)mbuf_read_u8(mb) << 0;
+
+ return u24;
+}
+
+
+static int encode_basic_hdr(struct mbuf *mb, unsigned fmt,
+ uint32_t chunk_id)
+{
+ uint8_t v;
+ int err = 0;
+
+ if (chunk_id >= 320) {
+
+ const uint16_t cs_id = chunk_id - RTMP_CHUNK_OFFSET;
+
+ v = fmt<<6 | 1;
+
+ err |= mbuf_write_u8(mb, v);
+ err |= mbuf_write_u16(mb, htons(cs_id));
+ }
+ else if (chunk_id >= RTMP_CHUNK_OFFSET) {
+
+ const uint8_t cs_id = chunk_id - RTMP_CHUNK_OFFSET;
+
+ v = fmt<<6 | 0;
+
+ err |= mbuf_write_u8(mb, v);
+ err |= mbuf_write_u8(mb, cs_id);
+ }
+ else {
+ v = fmt<<6 | chunk_id;
+
+ err |= mbuf_write_u8(mb, v);
+ }
+
+ return err;
+}
+
+
+static int decode_basic_hdr(struct rtmp_header *hdr, struct mbuf *mb)
+{
+ uint8_t cs_id;
+ uint8_t v;
+
+ if (mbuf_get_left(mb) < 1)
+ return ENODATA;
+
+ v = mbuf_read_u8(mb);
+
+ hdr->format = v>>6;
+
+ cs_id = v & 0x3f;
+
+ switch (cs_id) {
+
+ case 0:
+ if (mbuf_get_left(mb) < 1)
+ return ENODATA;
+
+ hdr->chunk_id = mbuf_read_u8(mb) + RTMP_CHUNK_OFFSET;
+ break;
+
+ case 1:
+ if (mbuf_get_left(mb) < 2)
+ return ENODATA;
+
+ hdr->chunk_id = ntohs(mbuf_read_u16(mb)) + RTMP_CHUNK_OFFSET;
+ break;
+
+ default:
+ hdr->chunk_id = cs_id;
+ break;
+ }
+
+ return 0;
+}
+
+
+static uint32_t ts_24(uint32_t ts)
+{
+ return ts >= TIMESTAMP_24MAX ? TIMESTAMP_24MAX : ts;
+}
+
+
+static uint32_t ts_ext(uint32_t ts)
+{
+ return ts >= TIMESTAMP_24MAX ? ts : 0;
+}
+
+
+int rtmp_header_encode(struct mbuf *mb, struct rtmp_header *hdr)
+{
+ int err = 0;
+
+ if (!mb || !hdr)
+ return EINVAL;
+
+ err = encode_basic_hdr(mb, hdr->format, hdr->chunk_id);
+ if (err)
+ return err;
+
+ switch (hdr->format) {
+
+ case 0:
+ hdr->timestamp_ext = ts_ext(hdr->timestamp);
+
+ err |= mbuf_write_u24_hton(mb, ts_24(hdr->timestamp));
+ err |= mbuf_write_u24_hton(mb, hdr->length);
+ err |= mbuf_write_u8(mb, hdr->type_id);
+ err |= mbuf_write_u32(mb, sys_htoll(hdr->stream_id));
+ break;
+
+ case 1:
+ hdr->timestamp_ext = ts_ext(hdr->timestamp_delta);
+
+ err |= mbuf_write_u24_hton(mb, ts_24(hdr->timestamp_delta));
+ err |= mbuf_write_u24_hton(mb, hdr->length);
+ err |= mbuf_write_u8(mb, hdr->type_id);
+ break;
+
+ case 2:
+ hdr->timestamp_ext = ts_ext(hdr->timestamp_delta);
+
+ err |= mbuf_write_u24_hton(mb, ts_24(hdr->timestamp_delta));
+ break;
+
+ case 3:
+ break;
+ }
+
+ if (hdr->timestamp_ext) {
+ err |= mbuf_write_u32(mb, htonl(hdr->timestamp_ext));
+ }
+
+ return err;
+}
+
+
+int rtmp_header_decode(struct rtmp_header *hdr, struct mbuf *mb)
+{
+ uint32_t *timestamp_ext = NULL;
+ int err;
+
+ if (!hdr || !mb)
+ return EINVAL;
+
+ memset(hdr, 0, sizeof(*hdr));
+
+ err = decode_basic_hdr(hdr, mb);
+ if (err)
+ return err;
+
+ switch (hdr->format) {
+
+ case 0:
+ if (mbuf_get_left(mb) < 11)
+ return ENODATA;
+
+ hdr->timestamp = mbuf_read_u24_ntoh(mb);
+ hdr->length = mbuf_read_u24_ntoh(mb);
+ hdr->type_id = mbuf_read_u8(mb);
+ hdr->stream_id = sys_ltohl(mbuf_read_u32(mb));
+ break;
+
+ case 1:
+ if (mbuf_get_left(mb) < 7)
+ return ENODATA;
+
+ hdr->timestamp_delta = mbuf_read_u24_ntoh(mb);
+ hdr->length = mbuf_read_u24_ntoh(mb);
+ hdr->type_id = mbuf_read_u8(mb);
+ break;
+
+ case 2:
+ if (mbuf_get_left(mb) < 3)
+ return ENODATA;
+
+ hdr->timestamp_delta = mbuf_read_u24_ntoh(mb);
+ break;
+
+ case 3:
+ /* no payload */
+ break;
+ }
+
+ if (hdr->timestamp == TIMESTAMP_24MAX)
+ timestamp_ext = &hdr->timestamp;
+ else if (hdr->timestamp_delta == TIMESTAMP_24MAX)
+ timestamp_ext = &hdr->timestamp_delta;
+
+ if (timestamp_ext) {
+ if (mbuf_get_left(mb) < 4)
+ return ENODATA;
+
+ *timestamp_ext = ntohl(mbuf_read_u32(mb));
+ hdr->ext_ts = true;
+ }
+
+ return 0;
+}
+
+
+int rtmp_header_print(struct re_printf *pf, const struct rtmp_header *hdr)
+{
+ if (!hdr)
+ return 0;
+
+ return re_hprintf(pf,
+ "fmt %u, chunk %u, "
+ "timestamp %5u, ts_delta %2u,"
+ " len %3u, type %2u (%-14s) stream_id %u",
+ hdr->format, hdr->chunk_id, hdr->timestamp,
+ hdr->timestamp_delta, hdr->length, hdr->type_id,
+ rtmp_packet_type_name(hdr->type_id), hdr->stream_id);
+}
+
+
+const char *rtmp_packet_type_name(enum rtmp_packet_type type)
+{
+ switch (type) {
+
+ case RTMP_TYPE_SET_CHUNK_SIZE: return "Set Chunk Size";
+ case RTMP_TYPE_ACKNOWLEDGEMENT: return "Acknowledgement";
+ case RTMP_TYPE_USER_CONTROL_MSG: return "User Control Message";
+ case RTMP_TYPE_WINDOW_ACK_SIZE: return "Window Acknowledgement Size";
+ case RTMP_TYPE_SET_PEER_BANDWIDTH:return "Set Peer Bandwidth";
+ case RTMP_TYPE_AUDIO: return "Audio Message";
+ case RTMP_TYPE_VIDEO: return "Video Message";
+ case RTMP_TYPE_DATA: return "Data Message";
+ case RTMP_TYPE_AMF0: return "AMF";
+ default: return "?";
+ }
+}
diff --git a/src/rtmp/mod.mk b/src/rtmp/mod.mk
new file mode 100644
index 0000000..c1b73df
--- /dev/null
+++ b/src/rtmp/mod.mk
@@ -0,0 +1,16 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += rtmp/amf.c
+SRCS += rtmp/amf_dec.c
+SRCS += rtmp/amf_enc.c
+SRCS += rtmp/chunk.c
+SRCS += rtmp/conn.c
+SRCS += rtmp/control.c
+SRCS += rtmp/ctrans.c
+SRCS += rtmp/dechunk.c
+SRCS += rtmp/hdr.c
+SRCS += rtmp/stream.c
diff --git a/src/rtmp/rtmp.h b/src/rtmp/rtmp.h
new file mode 100644
index 0000000..90085e0
--- /dev/null
+++ b/src/rtmp/rtmp.h
@@ -0,0 +1,178 @@
+/**
+ * @file rtmp.h Real Time Messaging Protocol (RTMP) -- Internal API
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+enum {
+ RTMP_PROTOCOL_VERSION = 3,
+ RTMP_DEFAULT_CHUNKSIZE = 128,
+ RTMP_HANDSHAKE_SIZE = 1536,
+ RTMP_MESSAGE_LEN_MAX = 524288,
+};
+
+/* Chunk IDs */
+enum {
+ RTMP_CHUNK_ID_CONTROL = 2,
+ RTMP_CHUNK_ID_CONN = 3,
+};
+
+/** Defines the RTMP Handshake State */
+enum rtmp_handshake_state {
+ RTMP_STATE_UNINITIALIZED = 0,
+ RTMP_STATE_VERSION_SENT,
+ RTMP_STATE_ACK_SENT,
+ RTMP_STATE_HANDSHAKE_DONE
+};
+
+/**
+ * Defines an RTMP Connection
+ */
+struct rtmp_conn {
+ struct list streaml;
+ struct rtmp_dechunker *dechunk;
+ struct tcp_conn *tc;
+ struct mbuf *mb; /* TCP reassembly buffer */
+ enum rtmp_handshake_state state;
+ size_t total_bytes;
+ size_t last_ack;
+ uint32_t window_ack_size;
+ uint32_t send_chunk_size;
+ unsigned chunk_id_counter;
+ bool is_client;
+ bool connected;
+ rtmp_estab_h *estabh;
+ rtmp_command_h *cmdh;
+ rtmp_close_h *closeh;
+ void *arg;
+
+ /* client specific: */
+ struct dnsc *dnsc;
+ struct dns_query *dnsq4;
+ struct dns_query *dnsq6;
+ struct list ctransl;
+ struct sa srvv[16];
+ unsigned srvc;
+ uint64_t tid_counter;
+ uint16_t port;
+ char *app;
+ char *uri;
+ char *stream;
+ char *host;
+};
+
+/**
+ * Defines an RTMP Stream
+ */
+struct rtmp_stream {
+ struct le le;
+ const struct rtmp_conn *conn; /**< Pointer to parent connection */
+ bool created;
+ uint32_t stream_id;
+ unsigned chunk_id_audio;
+ unsigned chunk_id_video;
+ unsigned chunk_id_data;
+ rtmp_audio_h *auh;
+ rtmp_video_h *vidh;
+ rtmp_command_h *datah;
+ rtmp_command_h *cmdh;
+ rtmp_resp_h *resph;
+ rtmp_control_h *ctrlh;
+ void *arg;
+};
+
+struct rtmp_header {
+ unsigned format:2; /* type 0-3 */
+ uint32_t chunk_id; /* from 3-65599 */
+
+ uint32_t timestamp; /* 24-bit or 32-bit */
+ uint32_t timestamp_delta; /* 24-bit */
+ uint32_t timestamp_ext;
+ uint32_t length; /* 24-bit */
+ uint8_t type_id; /* enum rtmp_packet_type */
+ uint32_t stream_id;
+ bool ext_ts;
+};
+
+
+/* Command */
+
+int rtmp_command_header_encode(struct mbuf *mb, const char *name,
+ uint64_t tid);
+
+/* Connection */
+
+int rtmp_conn_send_msg(const struct rtmp_conn *conn, unsigned format,
+ uint32_t chunk_id, uint32_t timestamp,
+ uint32_t timestamp_delta, uint8_t msg_type_id,
+ uint32_t msg_stream_id,
+ const uint8_t *payload, size_t payload_len);
+int rtmp_send_amf_command(const struct rtmp_conn *conn,
+ unsigned format, uint32_t chunk_id,
+ uint8_t type_id,
+ uint32_t msg_stream_id,
+ const uint8_t *cmd, size_t len);
+unsigned rtmp_conn_assign_chunkid(struct rtmp_conn *conn);
+uint64_t rtmp_conn_assign_tid(struct rtmp_conn *conn);
+
+
+/* Client Transaction */
+
+
+struct rtmp_ctrans;
+
+int rtmp_ctrans_response(const struct list *ctransl,
+ const struct odict *msg);
+
+
+/*
+ * RTMP Chunk
+ */
+
+int rtmp_chunker(unsigned format, uint32_t chunk_id,
+ uint32_t timestamp, uint32_t timestamp_delta,
+ uint8_t msg_type_id, uint32_t msg_stream_id,
+ const uint8_t *payload, size_t payload_len,
+ size_t max_chunk_sz, struct tcp_conn *tc);
+
+
+/*
+ * RTMP Header
+ */
+
+int rtmp_header_encode(struct mbuf *mb, struct rtmp_header *hdr);
+int rtmp_header_decode(struct rtmp_header *hdr, struct mbuf *mb);
+int rtmp_header_print(struct re_printf *pf, const struct rtmp_header *hdr);
+const char *rtmp_packet_type_name(enum rtmp_packet_type type);
+
+
+/*
+ * RTMP De-chunker
+ */
+
+struct rtmp_dechunker;
+
+typedef int (rtmp_dechunk_h)(const struct rtmp_header *hdr,
+ struct mbuf *mb, void *arg);
+
+int rtmp_dechunker_alloc(struct rtmp_dechunker **rdp, size_t chunk_sz,
+ rtmp_dechunk_h *chunkh, void *arg);
+int rtmp_dechunker_receive(struct rtmp_dechunker *rd, struct mbuf *mb);
+void rtmp_dechunker_set_chunksize(struct rtmp_dechunker *rd, size_t chunk_sz);
+int rtmp_dechunker_debug(struct re_printf *pf,
+ const struct rtmp_dechunker *rd);
+
+
+/*
+ * AMF (Action Message Format)
+ */
+
+int rtmp_amf_encode_number(struct mbuf *mb, double val);
+int rtmp_amf_encode_boolean(struct mbuf *mb, bool boolean);
+int rtmp_amf_encode_string(struct mbuf *mb, const char *str);
+int rtmp_amf_encode_null(struct mbuf *mb);
+int rtmp_amf_vencode_object(struct mbuf *mb, enum rtmp_amf_type container,
+ unsigned propc, va_list *ap);
+
+int rtmp_amf_decode(struct odict **msgp, struct mbuf *mb);
diff --git a/src/rtmp/stream.c b/src/rtmp/stream.c
new file mode 100644
index 0000000..fe8748d
--- /dev/null
+++ b/src/rtmp/stream.c
@@ -0,0 +1,309 @@
+/**
+ * @file rtmp/stream.c Real Time Messaging Protocol (RTMP) -- NetStream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_net.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_tcp.h>
+#include <re_sys.h>
+#include <re_odict.h>
+#include <re_rtmp.h>
+#include "rtmp.h"
+
+
+static void destructor(void *data)
+{
+ struct rtmp_stream *strm = data;
+
+ list_unlink(&strm->le);
+
+ if (strm->created) {
+
+ rtmp_amf_command(strm->conn, 0, "deleteStream",
+ 3,
+ RTMP_AMF_TYPE_NUMBER, 0.0,
+ RTMP_AMF_TYPE_NULL,
+ RTMP_AMF_TYPE_NUMBER, (double)strm->stream_id);
+ }
+}
+
+
+/**
+ * Allocate a new RTMP Stream object
+ *
+ * @param strmp Pointer to allocated RTMP Stream
+ * @param conn RTMP Connection
+ * @param stream_id Stream id
+ * @param cmdh Command handler
+ * @param ctrlh Control handler
+ * @param auh Audio handler
+ * @param vidh Video handler
+ * @param datah Data handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtmp_stream_alloc(struct rtmp_stream **strmp, struct rtmp_conn *conn,
+ uint32_t stream_id, rtmp_command_h *cmdh,
+ rtmp_control_h *ctrlh, rtmp_audio_h *auh,
+ rtmp_video_h *vidh, rtmp_command_h *datah,
+ void *arg)
+{
+ struct rtmp_stream *strm;
+
+ if (!strmp || !conn)
+ return EINVAL;
+
+ strm = mem_zalloc(sizeof(*strm), destructor);
+ if (!strm)
+ return ENOMEM;
+
+ strm->conn = conn;
+ strm->stream_id = stream_id;
+
+ strm->cmdh = cmdh;
+ strm->ctrlh = ctrlh;
+ strm->auh = auh;
+ strm->vidh = vidh;
+ strm->datah = datah;
+ strm->arg = arg;
+
+ strm->chunk_id_audio = rtmp_conn_assign_chunkid(conn);
+ strm->chunk_id_video = rtmp_conn_assign_chunkid(conn);
+ strm->chunk_id_data = rtmp_conn_assign_chunkid(conn);
+
+ list_append(&conn->streaml, &strm->le, strm);
+
+ *strmp = strm;
+
+ return 0;
+}
+
+
+static void createstream_handler(bool success, const struct odict *msg,
+ void *arg)
+{
+ struct rtmp_stream *strm = arg;
+ uint64_t num;
+
+ if (!success)
+ goto out;
+
+ if (!odict_get_number(msg, &num, "3")) {
+ success = false;
+ goto out;
+ }
+
+ strm->stream_id = (uint32_t)num;
+ if (strm->stream_id == 0) {
+ success = false;
+ goto out;
+ }
+
+ strm->created = true;
+
+ out:
+ if (strm->resph)
+ strm->resph(success, msg, strm->arg);
+}
+
+
+/**
+ * Create a new RTMP Stream by sending "createStream" to the RTMP Server.
+ *
+ * @param strmp Pointer to allocated RTMP Stream
+ * @param conn RTMP Connection
+ * @param resph RTMP Response handler
+ * @param cmdh Command handler
+ * @param ctrlh Control handler
+ * @param auh Audio handler
+ * @param vidh Video handler
+ * @param datah Data handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtmp_stream_create(struct rtmp_stream **strmp, struct rtmp_conn *conn,
+ rtmp_resp_h *resph, rtmp_command_h *cmdh,
+ rtmp_control_h *ctrlh, rtmp_audio_h *auh,
+ rtmp_video_h *vidh, rtmp_command_h *datah,
+ void *arg)
+{
+ struct rtmp_stream *strm;
+ int err;
+
+ if (!strmp || !conn)
+ return EINVAL;
+
+ err = rtmp_stream_alloc(&strm, conn, (uint32_t)-1,
+ cmdh, ctrlh, auh, vidh, datah, arg);
+ if (err)
+ return err;
+
+ strm->resph = resph;
+
+ err = rtmp_amf_request(conn, 0,
+ "createStream", createstream_handler, strm,
+ 1,
+ RTMP_AMF_TYPE_NULL);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(strm);
+ else
+ *strmp = strm;
+
+ return err;
+}
+
+
+/**
+ * Start playing an RTMP Stream by sending "play" to the RTMP Server
+ *
+ * @param strm RTMP Stream
+ * @param name Stream name
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtmp_play(struct rtmp_stream *strm, const char *name)
+{
+ if (!strm || !name)
+ return EINVAL;
+
+ return rtmp_amf_command(strm->conn, strm->stream_id, "play",
+ 4,
+ RTMP_AMF_TYPE_NUMBER, 0.0,
+ RTMP_AMF_TYPE_NULL,
+ RTMP_AMF_TYPE_STRING, name,
+ RTMP_AMF_TYPE_NUMBER, -2000.0);
+}
+
+
+/**
+ * Start publishing an RTMP Stream by sending "publish" to the RTMP Server
+ *
+ * @param strm RTMP Stream
+ * @param name Stream name
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtmp_publish(struct rtmp_stream *strm, const char *name)
+{
+ if (!strm || !name)
+ return EINVAL;
+
+ return rtmp_amf_command(strm->conn, strm->stream_id, "publish",
+ 4,
+ RTMP_AMF_TYPE_NUMBER, 0.0,
+ RTMP_AMF_TYPE_NULL,
+ RTMP_AMF_TYPE_STRING, name,
+ RTMP_AMF_TYPE_STRING, "live");
+}
+
+
+/**
+ * Send metadata on the stream to the RTMP Server
+ *
+ * @param strm RTMP Stream
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtmp_meta(struct rtmp_stream *strm)
+{
+ if (!strm)
+ return EINVAL;
+
+ return rtmp_amf_data(strm->conn, strm->stream_id, "@setDataFrame",
+ 2,
+ RTMP_AMF_TYPE_STRING, "onMetaData",
+ RTMP_AMF_TYPE_ECMA_ARRAY, 2,
+ RTMP_AMF_TYPE_NUMBER, "audiocodecid", 10.0,
+ RTMP_AMF_TYPE_NUMBER, "videocodecid", 7.0);
+}
+
+
+/**
+ * Send audio packet on the RTMP Stream
+ *
+ * @param strm RTMP Stream
+ * @param timestamp Timestamp in [milliseconds]
+ * @param pld Audio payload
+ * @param len Payload length
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtmp_send_audio(struct rtmp_stream *strm, uint32_t timestamp,
+ const uint8_t *pld, size_t len)
+{
+ uint32_t chunk_id;
+
+ if (!strm || !pld || !len)
+ return EINVAL;
+
+ chunk_id = strm->chunk_id_audio;
+
+ return rtmp_conn_send_msg(strm->conn, 0, chunk_id, timestamp, 0,
+ RTMP_TYPE_AUDIO, strm->stream_id, pld, len);
+}
+
+
+/**
+ * Send video packet on the RTMP Stream
+ *
+ * @param strm RTMP Stream
+ * @param timestamp Timestamp in [milliseconds]
+ * @param pld Video payload
+ * @param len Payload length
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtmp_send_video(struct rtmp_stream *strm, uint32_t timestamp,
+ const uint8_t *pld, size_t len)
+{
+ uint32_t chunk_id;
+
+ if (!strm || !pld || !len)
+ return EINVAL;
+
+ chunk_id = strm->chunk_id_video;
+
+ return rtmp_conn_send_msg(strm->conn, 0, chunk_id, timestamp, 0,
+ RTMP_TYPE_VIDEO, strm->stream_id, pld, len);
+}
+
+
+/**
+ * Find an RTMP Stream by stream id
+ *
+ * @param conn RTMP Connection
+ * @param stream_id Stream id
+ *
+ * @return RTMP Stream if found, or NULL if not found
+ */
+struct rtmp_stream *rtmp_stream_find(const struct rtmp_conn *conn,
+ uint32_t stream_id)
+{
+ struct le *le;
+
+ if (!conn)
+ return NULL;
+
+ for (le = list_head(&conn->streaml); le; le = le->next) {
+
+ struct rtmp_stream *strm = le->data;
+
+ if (stream_id == strm->stream_id)
+ return strm;
+ }
+
+ return NULL;
+}
diff --git a/src/rtp/fb.c b/src/rtp/fb.c
new file mode 100644
index 0000000..51b8d44
--- /dev/null
+++ b/src/rtp/fb.c
@@ -0,0 +1,169 @@
+/**
+ * @file fb.c Real-time Transport Control Protocol (RTCP)-Based Feedback
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sys.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+#define DEBUG_MODULE "rtcp_pb"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+enum {
+ GNACK_SIZE = 4,
+ SLI_SIZE = 4
+};
+
+
+/* Encode functions */
+
+
+/**
+ * Encode an RTCP Generic NACK (GNACK) message
+ *
+ * @param mb Buffer to encode into
+ * @param pid Packet ID
+ * @param blp Bitmask of following lost packets (BLP)
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_rtpfb_gnack_encode(struct mbuf *mb, uint16_t pid, uint16_t blp)
+{
+ int err;
+ err = mbuf_write_u16(mb, htons(pid));
+ err |= mbuf_write_u16(mb, htons(blp));
+ return err;
+}
+
+
+/**
+ * Encode an RTCP Slice Loss Indication (SLI) message
+ *
+ * @param mb Buffer to encode into
+ * @param first Macroblock (MB) address of the first lost macroblock
+ * @param number Number of lost macroblocks
+ * @param picid Picture ID
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_psfb_sli_encode(struct mbuf *mb, uint16_t first, uint16_t number,
+ uint8_t picid)
+{
+ const uint32_t v = first<<19 | number<<6 | picid;
+ return mbuf_write_u32(mb, htonl(v));
+}
+
+
+/* Decode functions */
+
+
+/**
+ * Decode an RTCP Transport Layer Feedback Message
+ *
+ * @param mb Buffer to decode
+ * @param msg RTCP Message to decode into
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_rtpfb_decode(struct mbuf *mb, struct rtcp_msg *msg)
+{
+ size_t i, sz;
+
+ if (!msg)
+ return EINVAL;
+
+ switch (msg->hdr.count) {
+
+ case RTCP_RTPFB_GNACK:
+ sz = msg->r.fb.n * sizeof(*msg->r.fb.fci.gnackv);
+ msg->r.fb.fci.gnackv = mem_alloc(sz, NULL);
+ if (!msg->r.fb.fci.gnackv)
+ return ENOMEM;
+
+ if (mbuf_get_left(mb) < msg->r.fb.n * GNACK_SIZE)
+ return EBADMSG;
+ for (i=0; i<msg->r.fb.n; i++) {
+ msg->r.fb.fci.gnackv[i].pid = ntohs(mbuf_read_u16(mb));
+ msg->r.fb.fci.gnackv[i].blp = ntohs(mbuf_read_u16(mb));
+ }
+ break;
+
+ default:
+ DEBUG_NOTICE("unknown RTPFB fmt %d\n", msg->hdr.count);
+ break;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Decode an RTCP Payload-Specific Feedback Message
+ *
+ * @param mb Buffer to decode
+ * @param msg RTCP Message to decode into
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_psfb_decode(struct mbuf *mb, struct rtcp_msg *msg)
+{
+ size_t i, sz;
+
+ if (!msg)
+ return EINVAL;
+
+ switch (msg->hdr.count) {
+
+ case RTCP_PSFB_PLI:
+ /* no params */
+ break;
+
+ case RTCP_PSFB_SLI:
+ sz = msg->r.fb.n * sizeof(*msg->r.fb.fci.sliv);
+ msg->r.fb.fci.sliv = mem_alloc(sz, NULL);
+ if (!msg->r.fb.fci.sliv)
+ return ENOMEM;
+
+ if (mbuf_get_left(mb) < msg->r.fb.n * SLI_SIZE)
+ return EBADMSG;
+ for (i=0; i<msg->r.fb.n; i++) {
+ const uint32_t v = ntohl(mbuf_read_u32(mb));
+
+ msg->r.fb.fci.sliv[i].first = v>>19 & 0x1fff;
+ msg->r.fb.fci.sliv[i].number = v>> 6 & 0x1fff;
+ msg->r.fb.fci.sliv[i].picid = v>> 0 & 0x003f;
+ }
+ break;
+
+ case RTCP_PSFB_AFB:
+ sz = msg->r.fb.n * 4;
+
+ if (mbuf_get_left(mb) < sz)
+ return EBADMSG;
+
+ msg->r.fb.fci.afb = mbuf_alloc_ref(mb);
+ if (!msg->r.fb.fci.afb)
+ return ENOMEM;
+
+ msg->r.fb.fci.afb->end = msg->r.fb.fci.afb->pos + sz;
+ mbuf_advance(mb, sz);
+ break;
+
+ default:
+ DEBUG_NOTICE("unknown PSFB fmt %d\n", msg->hdr.count);
+ break;
+ }
+
+ return 0;
+}
diff --git a/src/rtp/member.c b/src/rtp/member.c
new file mode 100644
index 0000000..1a3bd78
--- /dev/null
+++ b/src/rtp/member.c
@@ -0,0 +1,53 @@
+/**
+ * @file member.c Real-time Transport Control Protocol member
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+static void destructor(void *data)
+{
+ struct rtp_member *mbr = data;
+
+ hash_unlink(&mbr->le);
+ mem_deref(mbr->s);
+}
+
+
+struct rtp_member *member_add(struct hash *ht, uint32_t src)
+{
+ struct rtp_member *mbr;
+
+ mbr = mem_zalloc(sizeof(*mbr), destructor);
+ if (!mbr)
+ return NULL;
+
+ hash_append(ht, src, &mbr->le, mbr);
+ mbr->src = src;
+
+ return mbr;
+}
+
+
+static bool hash_cmp_handler(struct le *le, void *arg)
+{
+ const struct rtp_member *mbr = le->data;
+
+ return mbr->src == *(uint32_t *)arg;
+}
+
+
+struct rtp_member *member_find(struct hash *ht, uint32_t src)
+{
+ return list_ledata(hash_lookup(ht, src, hash_cmp_handler, &src));
+}
diff --git a/src/rtp/mod.mk b/src/rtp/mod.mk
new file mode 100644
index 0000000..c7a7b63
--- /dev/null
+++ b/src/rtp/mod.mk
@@ -0,0 +1,16 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += rtp/fb.c
+SRCS += rtp/member.c
+SRCS += rtp/ntp.c
+SRCS += rtp/pkt.c
+SRCS += rtp/rr.c
+SRCS += rtp/rtcp.c
+SRCS += rtp/rtp.c
+SRCS += rtp/sdes.c
+SRCS += rtp/sess.c
+SRCS += rtp/source.c
diff --git a/src/rtp/ntp.c b/src/rtp/ntp.c
new file mode 100644
index 0000000..ab5494b
--- /dev/null
+++ b/src/rtp/ntp.c
@@ -0,0 +1,103 @@
+/**
+ * @file ntp.c NTP Routines
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+/*
+ * Unix time: seconds relative to 0h January 1, 1970
+ * NTP time: seconds relative to 0h UTC on 1 January 1900
+ */
+
+
+/* Number of seconds from 1900 to 1970 */
+#define UNIX_NTP_OFFSET 0x83aa7e80
+
+
+/**
+ * Convert from Unix time to NTP time
+ *
+ * @param ntp NTP time to convert to (output)
+ * @param tv Unix time to convert from (input)
+ */
+void unix2ntp(struct ntp_time *ntp, const struct timeval *tv)
+{
+ ntp->hi = (uint32_t)(tv->tv_sec + UNIX_NTP_OFFSET);
+ ntp->lo = (uint32_t)((double)tv->tv_usec*(double)(1LL<<32)*1.0e-6);
+}
+
+
+/**
+ * Convert from NTP time to Unix time
+ *
+ * @param tv Unix time to convert to (output)
+ * @param ntp NTP time to convert from (input)
+ */
+void ntp2unix(struct timeval *tv, const struct ntp_time *ntp)
+{
+ tv->tv_sec = ntp->hi - UNIX_NTP_OFFSET;
+ tv->tv_usec = (uint32_t)(1.0e6 * (double) ntp->lo / (1LL<<32));
+}
+
+
+int ntp_time_get(struct ntp_time *ntp)
+{
+ struct timeval tv;
+#ifdef WIN32
+ union {
+ long long ns100;
+ FILETIME ft;
+ } now;
+
+ GetSystemTimeAsFileTime(&now.ft);
+ tv.tv_usec = (long) ((now.ns100 / 10LL) % 1000000LL);
+ tv.tv_sec = (long) ((now.ns100 - 116444736000000000LL) / 10000000LL);
+#else
+ if (gettimeofday(&tv, NULL) != 0)
+ return errno;
+#endif
+ unix2ntp(ntp, &tv);
+
+ return 0;
+}
+
+
+/**
+ * Convert NTP time to middle 32-bits (compact representation)
+ *
+ * @param ntp NTP time
+ *
+ * @return NTP time in compact representation
+ */
+uint32_t ntp_compact(const struct ntp_time *ntp)
+{
+ return ntp ? ((ntp->hi & 0xffff) << 16 | (ntp->lo >> 16)) : 0;
+}
+
+
+/**
+ * Convert NTP compact representation to microseconds
+ *
+ * @param ntpc NTP time in compact representation
+ *
+ * @return NTP time in microseconds
+ */
+uint64_t ntp_compact2us(uint32_t ntpc)
+{
+ const uint32_t hi = (ntpc >> 16) & 0xffff;
+ const uint32_t lo = (ntpc & 0xffff) << 16;
+
+ return (1000000ULL * hi) + ((1000000ULL * lo) >> 32);
+}
diff --git a/src/rtp/pkt.c b/src/rtp/pkt.c
new file mode 100644
index 0000000..cd4880d
--- /dev/null
+++ b/src/rtp/pkt.c
@@ -0,0 +1,463 @@
+/**
+ * @file pkt.c RTCP Packet handling
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sys.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+#define DEBUG_MODULE "rtcp_pkt"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static void rtcp_destructor(void *data)
+{
+ struct rtcp_msg *msg = data;
+ size_t i, j;
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_SR:
+ mem_deref(msg->r.sr.rrv);
+ break;
+
+ case RTCP_RR:
+ mem_deref(msg->r.rr.rrv);
+ break;
+
+ case RTCP_SDES:
+ if (!msg->r.sdesv)
+ break;
+
+ for (i=0; i<msg->hdr.count; i++) {
+ struct rtcp_sdes *sdes = &msg->r.sdesv[i];
+
+ for (j=0; j<sdes->n; j++) {
+
+ mem_deref(sdes->itemv[j].data);
+ }
+ mem_deref(sdes->itemv);
+ }
+ mem_deref(msg->r.sdesv);
+ break;
+
+ case RTCP_BYE:
+ mem_deref(msg->r.bye.srcv);
+ mem_deref(msg->r.bye.reason);
+ break;
+
+ case RTCP_APP:
+ mem_deref(msg->r.app.data);
+ break;
+
+ case RTCP_RTPFB:
+ case RTCP_PSFB:
+ mem_deref(msg->r.fb.fci.p);
+ break;
+
+ default:
+ /* nothing allocated */
+ break;
+ }
+}
+
+
+/**
+ * Encode the RTCP Header
+ *
+ * @param mb Buffer to encode into
+ * @param count Number of sub-elemements
+ * @param type RTCP Packet type
+ * @param length Packet length in words
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_hdr_encode(struct mbuf *mb, uint8_t count, enum rtcp_type type,
+ uint16_t length)
+{
+ int err;
+
+ if (!mb)
+ return EINVAL;
+
+ err = mbuf_write_u8(mb, RTCP_VERSION<<6 | count);
+ err |= mbuf_write_u8(mb, type);
+ err |= mbuf_write_u16(mb, htons(length));
+
+ return err;
+}
+
+
+/**
+ * Decode the RTCP Header
+ *
+ * @param mb Buffer to decode from
+ * @param hdr RTCP Header to decode into
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_hdr_decode(struct mbuf *mb, struct rtcp_hdr *hdr)
+{
+ uint8_t b;
+
+ if (!hdr)
+ return EINVAL;
+ if (mbuf_get_left(mb) < RTCP_HDR_SIZE)
+ return EBADMSG;
+
+ b = mbuf_read_u8(mb);
+ hdr->pt = mbuf_read_u8(mb);
+ hdr->length = ntohs(mbuf_read_u16(mb));
+
+ hdr->version = (b >> 6) & 0x3;
+ hdr->p = (b >> 5) & 0x1;
+ hdr->count = (b >> 0) & 0x1f;
+
+ return 0;
+}
+
+
+int rtcp_vencode(struct mbuf *mb, enum rtcp_type type, uint32_t count,
+ va_list ap)
+{
+ size_t i, pos;
+ uint16_t len;
+ const uint8_t *data;
+ size_t data_len;
+ const uint32_t *srcv;
+ const char *reason;
+ rtcp_encode_h *ench;
+ void *arg;
+ int err = 0;
+
+ if (!mb)
+ return EINVAL;
+
+ pos = mb->pos;
+
+ /* Skip header - encoded last */
+ mb->pos = mb->end = (mb->pos + RTCP_HDR_SIZE);
+
+ switch (type) {
+
+ case RTCP_SR:
+ for (i=0; i<6; i++)
+ err |= mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ ench = va_arg(ap, rtcp_encode_h *);
+ arg = va_arg(ap, void *);
+ if (ench)
+ err |= ench(mb, arg);
+ break;
+
+ case RTCP_RR:
+ err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ ench = va_arg(ap, rtcp_encode_h *);
+ arg = va_arg(ap, void *);
+ if (ench)
+ err |= ench(mb, arg);
+ break;
+
+ case RTCP_SDES:
+ ench = va_arg(ap, rtcp_encode_h *);
+ arg = va_arg(ap, void *);
+ if (ench)
+ err |= ench(mb, arg);
+ break;
+
+ case RTCP_BYE:
+ srcv = va_arg(ap, uint32_t *);
+ reason = va_arg(ap, char *);
+ for (i=0; i<count && !err; i++) {
+ err = mbuf_write_u32(mb, htonl(srcv[i]));
+ }
+ if (reason) {
+ err |= mbuf_write_u8(mb, strlen(reason));
+ err |= mbuf_write_str(mb, reason);
+ }
+ break;
+
+ case RTCP_APP:
+ err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ err |= mbuf_write_mem(mb, va_arg(ap, uint8_t *), 4);
+ data = va_arg(ap, const uint8_t *);
+ data_len = va_arg(ap, size_t);
+ if (data) {
+ if (data_len % 4) {
+ DEBUG_WARNING("not a multiple of 32bits\n");
+ return EBADMSG;
+ }
+ err |= mbuf_write_mem(mb, data, data_len);
+ }
+ break;
+
+ case RTCP_FIR:
+ err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ break;
+
+ case RTCP_NACK:
+ err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ err |= mbuf_write_u16(mb, htons(va_arg(ap, uint32_t)));
+ err |= mbuf_write_u16(mb, htons(va_arg(ap, uint32_t)));
+ break;
+
+ case RTCP_RTPFB:
+ case RTCP_PSFB:
+ err = mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ err |= mbuf_write_u32(mb, htonl(va_arg(ap, uint32_t)));
+ ench = va_arg(ap, rtcp_encode_h *);
+ arg = va_arg(ap, void *);
+ if (ench)
+ err |= ench(mb, arg);
+ break;
+
+ default:
+ return EINVAL;
+ }
+ if (err)
+ return err;
+
+ /* padding to 32 bits */
+ while ((mb->end - pos) & 0x3)
+ err |= mbuf_write_u8(mb, 0x00);
+ if (err)
+ return err;
+
+ /* Encode RTCP Header */
+ mb->pos = pos;
+ len = (mb->end - pos - RTCP_HDR_SIZE)/sizeof(uint32_t);
+ err = rtcp_hdr_encode(mb, count, type, len);
+ if (err)
+ return err;
+
+ mb->pos = mb->end;
+
+ return 0;
+}
+
+
+/**
+ * Encode an RTCP Packet into a buffer
+ *
+ * @param mb Buffer to encode into
+ * @param type RTCP Packet type
+ * @param count Packet-specific count
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_encode(struct mbuf *mb, enum rtcp_type type, uint32_t count, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, count);
+ err = rtcp_vencode(mb, type, count, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Decode one RTCP message from a buffer
+ *
+ * @param msgp Pointer to allocated RTCP Message
+ * @param mb Buffer to decode from
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_decode(struct rtcp_msg **msgp, struct mbuf *mb)
+{
+ struct rtcp_msg *msg = NULL;
+ size_t start, i, sz, count, rem;
+ int err;
+
+ if (!msgp)
+ return EINVAL;
+ if (mbuf_get_left(mb) < RTCP_HDR_SIZE)
+ return EBADMSG;
+
+ msg = mem_zalloc(sizeof(*msg), rtcp_destructor);
+ if (!msg)
+ return ENOMEM;
+
+ start = mb->pos;
+
+ /* decode and check header */
+ err = rtcp_hdr_decode(mb, &msg->hdr);
+ if (err)
+ goto out;
+
+ if (msg->hdr.version != RTCP_VERSION)
+ goto badmsg;
+
+ /* check length and remaining */
+ rem = msg->hdr.length * sizeof(uint32_t);
+ if (mbuf_get_left(mb) < rem)
+ goto badmsg;
+
+ count = msg->hdr.count;
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_SR:
+ if (mbuf_get_left(mb) < (RTCP_SRC_SIZE + RTCP_SR_SIZE))
+ goto badmsg;
+ msg->r.sr.ssrc = ntohl(mbuf_read_u32(mb));
+ msg->r.sr.ntp_sec = ntohl(mbuf_read_u32(mb));
+ msg->r.sr.ntp_frac = ntohl(mbuf_read_u32(mb));
+ msg->r.sr.rtp_ts = ntohl(mbuf_read_u32(mb));
+ msg->r.sr.psent = ntohl(mbuf_read_u32(mb));
+ msg->r.sr.osent = ntohl(mbuf_read_u32(mb));
+
+ err = rtcp_rr_alloc(&msg->r.sr.rrv, count);
+ if (err)
+ goto out;
+ for (i=0; i<count && !err; i++)
+ err = rtcp_rr_decode(mb, &msg->r.sr.rrv[i]);
+ break;
+
+ case RTCP_RR:
+ if (mbuf_get_left(mb) < RTCP_SRC_SIZE)
+ goto badmsg;
+ msg->r.rr.ssrc = ntohl(mbuf_read_u32(mb));
+
+ err = rtcp_rr_alloc(&msg->r.rr.rrv, count);
+ if (err)
+ goto out;
+ for (i=0; i<count && !err; i++)
+ err = rtcp_rr_decode(mb, &msg->r.rr.rrv[i]);
+ break;
+
+ case RTCP_SDES:
+ if (count == 0)
+ break;
+
+ sz = count * sizeof(*msg->r.sdesv);
+ msg->r.sdesv = mem_zalloc(sz, NULL);
+ if (!msg->r.sdesv) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ for (i=0; i<msg->hdr.count && !err; i++)
+ err = rtcp_sdes_decode(mb, &msg->r.sdesv[i]);
+ break;
+
+ case RTCP_BYE:
+ sz = count * sizeof(*msg->r.bye.srcv);
+ msg->r.bye.srcv = mem_alloc(sz, NULL);
+ if (!msg->r.bye.srcv) {
+ err = ENOMEM;
+ goto out;
+ }
+ if (mbuf_get_left(mb) < sz)
+ goto badmsg;
+ for (i=0; i<count; i++)
+ msg->r.bye.srcv[i] = ntohl(mbuf_read_u32(mb));
+
+ /* decode reason (optional) */
+ if (rem > count*sizeof(uint32_t)) {
+ const size_t len = mbuf_read_u8(mb);
+ if (mbuf_get_left(mb) < len)
+ goto badmsg;
+
+ err = mbuf_strdup(mb, &msg->r.bye.reason, len);
+ }
+ break;
+
+ case RTCP_APP:
+ if (mbuf_get_left(mb) < RTCP_APP_SIZE)
+ goto badmsg;
+ msg->r.app.src = ntohl(mbuf_read_u32(mb));
+ (void)mbuf_read_mem(mb, (uint8_t *)msg->r.app.name,
+ sizeof(msg->r.app.name));
+ if (rem > RTCP_APP_SIZE) {
+ msg->r.app.data_len = rem - RTCP_APP_SIZE;
+ msg->r.app.data = mem_alloc(msg->r.app.data_len, NULL);
+ if (!msg->r.app.data) {
+ err = ENOMEM;
+ goto out;
+ }
+ if (mbuf_get_left(mb) < msg->r.app.data_len)
+ goto badmsg;
+ (void)mbuf_read_mem(mb, msg->r.app.data,
+ msg->r.app.data_len);
+ }
+ break;
+
+ case RTCP_FIR:
+ if (mbuf_get_left(mb) < RTCP_FIR_SIZE)
+ goto badmsg;
+ msg->r.fir.ssrc = ntohl(mbuf_read_u32(mb));
+ break;
+
+ case RTCP_NACK:
+ if (mbuf_get_left(mb) < RTCP_NACK_SIZE)
+ goto badmsg;
+ msg->r.nack.ssrc = ntohl(mbuf_read_u32(mb));
+ msg->r.nack.fsn = ntohs(mbuf_read_u16(mb));
+ msg->r.nack.blp = ntohs(mbuf_read_u16(mb));
+ break;
+
+ case RTCP_RTPFB:
+ if (mbuf_get_left(mb) < RTCP_FB_SIZE)
+ goto badmsg;
+
+ if (msg->hdr.length < 2)
+ goto badmsg;
+
+ msg->r.fb.ssrc_packet = ntohl(mbuf_read_u32(mb));
+ msg->r.fb.ssrc_media = ntohl(mbuf_read_u32(mb));
+ msg->r.fb.n = msg->hdr.length - 2;
+
+ err = rtcp_rtpfb_decode(mb, msg);
+ break;
+
+ case RTCP_PSFB:
+ if (mbuf_get_left(mb) < RTCP_FB_SIZE)
+ goto badmsg;
+
+ if (msg->hdr.length < 2)
+ goto badmsg;
+
+ msg->r.fb.ssrc_packet = ntohl(mbuf_read_u32(mb));
+ msg->r.fb.ssrc_media = ntohl(mbuf_read_u32(mb));
+ msg->r.fb.n = msg->hdr.length - 2;
+
+ err = rtcp_psfb_decode(mb, msg);
+ break;
+
+ default:
+ /* unknown message type */
+ mbuf_advance(mb, rem);
+ break;
+ }
+ if (err)
+ goto out;
+
+ /* slurp padding */
+ while ((mb->pos - start) & 0x3 && mbuf_get_left(mb))
+ ++mb->pos;
+
+ out:
+ if (err)
+ mem_deref(msg);
+ else
+ *msgp = msg;
+
+ return err;
+
+ badmsg:
+ mem_deref(msg);
+ return EBADMSG;
+}
diff --git a/src/rtp/rr.c b/src/rtp/rr.c
new file mode 100644
index 0000000..9d39de8
--- /dev/null
+++ b/src/rtp/rr.c
@@ -0,0 +1,72 @@
+/**
+ * @file rtp/rr.c RTCP Reception report
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sys.h>
+#include <re_net.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+int rtcp_rr_alloc(struct rtcp_rr **rrp, size_t count)
+{
+ struct rtcp_rr *rr;
+
+ if (!rrp)
+ return EINVAL;
+
+ rr = mem_alloc(count * sizeof(*rr), NULL);
+ if (!rr)
+ return ENOMEM;
+
+ *rrp = rr;
+ return 0;
+}
+
+
+int rtcp_rr_encode(struct mbuf *mb, const struct rtcp_rr *rr)
+{
+ int err;
+
+ if (!mb || !rr)
+ return EINVAL;
+
+ err = mbuf_write_u32(mb, htonl(rr->ssrc));
+ err |= mbuf_write_u32(mb, htonl(rr->fraction<<24 |
+ (rr->lost & 0x00ffffff)));
+ err |= mbuf_write_u32(mb, htonl(rr->last_seq));
+ err |= mbuf_write_u32(mb, htonl(rr->jitter));
+ err |= mbuf_write_u32(mb, htonl(rr->lsr));
+ err |= mbuf_write_u32(mb, htonl(rr->dlsr));
+
+ return err;
+}
+
+
+int rtcp_rr_decode(struct mbuf *mb, struct rtcp_rr *rr)
+{
+ uint32_t w;
+
+ if (!rr)
+ return EINVAL;
+ if (mbuf_get_left(mb) < RTCP_RR_SIZE)
+ return EBADMSG;
+
+ rr->ssrc = ntohl(mbuf_read_u32(mb));
+ w = ntohl(mbuf_read_u32(mb));
+ rr->fraction = w>>24; rr->lost = w & 0x00ffffffU;
+ rr->last_seq = ntohl(mbuf_read_u32(mb));
+ rr->jitter = ntohl(mbuf_read_u32(mb));
+ rr->lsr = ntohl(mbuf_read_u32(mb));
+ rr->dlsr = ntohl(mbuf_read_u32(mb));
+
+ return 0;
+}
diff --git a/src/rtp/rtcp.c b/src/rtp/rtcp.c
new file mode 100644
index 0000000..6e708de
--- /dev/null
+++ b/src/rtp/rtcp.c
@@ -0,0 +1,285 @@
+/**
+ * @file rtcp.c Real-time Transport Control Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+static int rtcp_quick_send(struct rtp_sock *rs, enum rtcp_type type,
+ uint32_t count, ...)
+{
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ mb = mbuf_alloc(32);
+ if (!mb)
+ return ENOMEM;
+
+ mb->pos = RTCP_HEADROOM;
+
+ va_start(ap, count);
+ err = rtcp_vencode(mb, type, count, ap);
+ va_end(ap);
+
+ mb->pos = RTCP_HEADROOM;
+
+ if (!err)
+ err = rtcp_send(rs, mb);
+
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Send an RTCP Application-Defined (APP) packet
+ *
+ * @param rs RTP Socket
+ * @param name Ascii name (4 octets)
+ * @param data Application-dependent data
+ * @param len Number of bytes of data
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_send_app(struct rtp_sock *rs, const char name[4],
+ const uint8_t *data, size_t len)
+{
+ return rtcp_quick_send(rs, RTCP_APP, 0, rtp_sess_ssrc(rs),
+ name, data, len);
+}
+
+
+/**
+ * Send a Full INTRA-frame Request (FIR) packet
+ *
+ * @param rs RTP Socket
+ * @param ssrc Synchronization source identifier for the sender of this packet
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_send_fir(struct rtp_sock *rs, uint32_t ssrc)
+{
+ return rtcp_quick_send(rs, RTCP_FIR, 0, ssrc);
+}
+
+
+/**
+ * Send an RTCP NACK packet
+ *
+ * @param rs RTP Socket
+ * @param fsn First Sequence Number lost
+ * @param blp Bitmask of lost packets
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_send_nack(struct rtp_sock *rs, uint16_t fsn, uint16_t blp)
+{
+ return rtcp_quick_send(rs, RTCP_NACK, 0, rtp_sess_ssrc(rs), fsn, blp);
+}
+
+
+/**
+ * Send an RTCP Picture Loss Indication (PLI) packet
+ *
+ * @param rs RTP Socket
+ * @param fb_ssrc Feedback SSRC
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_send_pli(struct rtp_sock *rs, uint32_t fb_ssrc)
+{
+ return rtcp_quick_send(rs, RTCP_PSFB, RTCP_PSFB_PLI,
+ rtp_sess_ssrc(rs), fb_ssrc, NULL, NULL);
+}
+
+
+const char *rtcp_type_name(enum rtcp_type type)
+{
+ switch (type) {
+
+ case RTCP_FIR: return "FIR";
+ case RTCP_NACK: return "NACK";
+ case RTCP_SR: return "SR";
+ case RTCP_RR: return "RR";
+ case RTCP_SDES: return "SDES";
+ case RTCP_BYE: return "BYE";
+ case RTCP_APP: return "APP";
+ case RTCP_RTPFB: return "RTPFB";
+ case RTCP_PSFB: return "PSFB";
+ case RTCP_XR: return "XR";
+ case RTCP_AVB: return "AVB";
+ default: return "?";
+ }
+}
+
+
+const char *rtcp_sdes_name(enum rtcp_sdes_type sdes)
+{
+ switch (sdes) {
+
+ case RTCP_SDES_END: return "END";
+ case RTCP_SDES_CNAME: return "CNAME";
+ case RTCP_SDES_NAME: return "NAME";
+ case RTCP_SDES_EMAIL: return "EMAIL";
+ case RTCP_SDES_PHONE: return "PHONE";
+ case RTCP_SDES_LOC: return "LOC";
+ case RTCP_SDES_TOOL: return "TOOL";
+ case RTCP_SDES_NOTE: return "NOTE";
+ case RTCP_SDES_PRIV: return "PRIV";
+ default: return "?";
+ }
+}
+
+
+/**
+ * Print an RTCP Message
+ *
+ * @param pf Print handler for debug output
+ * @param msg RTCP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtcp_msg_print(struct re_printf *pf, const struct rtcp_msg *msg)
+{
+ size_t i, j;
+ int err;
+
+ if (!msg)
+ return 0;
+
+ err = re_hprintf(pf, "%8s pad=%d count=%-2d pt=%-3d len=%u ",
+ rtcp_type_name((enum rtcp_type)msg->hdr.pt),
+ msg->hdr.p,
+ msg->hdr.count, msg->hdr.pt, msg->hdr.length);
+ if (err)
+ return err;
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_SR:
+ err = re_hprintf(pf, "%08x %u %u %u %u %u",
+ msg->r.sr.ssrc,
+ msg->r.sr.ntp_sec,
+ msg->r.sr.ntp_frac,
+ msg->r.sr.rtp_ts,
+ msg->r.sr.psent,
+ msg->r.sr.osent);
+ for (i=0; i<msg->hdr.count && !err; i++) {
+ const struct rtcp_rr *rr = &msg->r.sr.rrv[i];
+ err = re_hprintf(pf, " {%08x %u %d %u %u %u %u}",
+ rr->ssrc, rr->fraction, rr->lost,
+ rr->last_seq, rr->jitter,
+ rr->lsr, rr->dlsr);
+ }
+ break;
+
+ case RTCP_RR:
+ err = re_hprintf(pf, "%08x", msg->r.rr.ssrc);
+ for (i=0; i<msg->hdr.count && !err; i++) {
+ const struct rtcp_rr *rr = &msg->r.rr.rrv[i];
+ err = re_hprintf(pf, " {0x%08x %u %d %u %u %u %u}",
+ rr->ssrc, rr->fraction, rr->lost,
+ rr->last_seq, rr->jitter,
+ rr->lsr, rr->dlsr);
+ }
+ break;
+
+ case RTCP_SDES:
+ for (i=0; i<msg->hdr.count; i++) {
+ const struct rtcp_sdes *sdes = &msg->r.sdesv[i];
+
+ err = re_hprintf(pf, " {0x%08x n=%u",
+ sdes->src, sdes->n);
+ for (j=0; j<sdes->n && !err; j++) {
+ const struct rtcp_sdes_item *item;
+ item = &sdes->itemv[j];
+ err = re_hprintf(pf, " <%s:%b>",
+ rtcp_sdes_name(item->type),
+ item->data,
+ (size_t)item->length);
+ }
+ err |= re_hprintf(pf, "}");
+ }
+ break;
+
+ case RTCP_BYE:
+ err = re_hprintf(pf, "%u srcs:", msg->hdr.count);
+ for (i=0; i<msg->hdr.count && !err; i++) {
+ err = re_hprintf(pf, " %08x",
+ msg->r.bye.srcv[i]);
+ }
+ err |= re_hprintf(pf, " '%s'", msg->r.bye.reason);
+ break;
+
+ case RTCP_APP:
+ err = re_hprintf(pf, "src=%08x '%b' data=%zu",
+ msg->r.app.src,
+ msg->r.app.name, sizeof(msg->r.app.name),
+ msg->r.app.data_len);
+ break;
+
+ case RTCP_FIR:
+ err = re_hprintf(pf, "ssrc=%08x", msg->r.fir.ssrc);
+ break;
+
+ case RTCP_NACK:
+ err = re_hprintf(pf, "ssrc=%08x fsn=%04x blp=%04x",
+ msg->r.nack.ssrc, msg->r.nack.fsn,
+ msg->r.nack.blp);
+ break;
+
+ case RTCP_RTPFB:
+ err = re_hprintf(pf, "pkt=%08x med=%08x n=%u",
+ msg->r.fb.ssrc_packet,
+ msg->r.fb.ssrc_media,
+ msg->r.fb.n);
+ if (msg->hdr.count == RTCP_RTPFB_GNACK) {
+ err |= re_hprintf(pf, " GNACK");
+ for (i=0; i<msg->r.fb.n; i++) {
+ err |= re_hprintf(pf, " {%04x %04x}",
+ msg->r.fb.fci.gnackv[i].pid,
+ msg->r.fb.fci.gnackv[i].blp);
+ }
+ }
+ break;
+
+ case RTCP_PSFB:
+ err = re_hprintf(pf, "pkt=%08x med=%08x n=%u",
+ msg->r.fb.ssrc_packet,
+ msg->r.fb.ssrc_media,
+ msg->r.fb.n);
+ if (msg->hdr.count == RTCP_PSFB_SLI) {
+ err |= re_hprintf(pf, " SLI");
+ for (i=0; i<msg->r.fb.n; i++) {
+ err |= re_hprintf(pf, " {%04x %04x %02x}",
+ msg->r.fb.fci.sliv[i].first,
+ msg->r.fb.fci.sliv[i].number,
+ msg->r.fb.fci.sliv[i].picid);
+ }
+ }
+ else if (msg->hdr.count == RTCP_PSFB_AFB) {
+ err |= re_hprintf(pf, " AFB %u bytes",
+ msg->r.fb.n * 4);
+ }
+ break;
+
+ default:
+ err = re_hprintf(pf, "<len=%u>", msg->hdr.length);
+ break;
+ }
+
+ err |= re_hprintf(pf, "\n");
+
+ return err;
+}
diff --git a/src/rtp/rtcp.h b/src/rtp/rtcp.h
new file mode 100644
index 0000000..3c22c24
--- /dev/null
+++ b/src/rtp/rtcp.h
@@ -0,0 +1,120 @@
+/**
+ * @file rtcp.h Internal interface to RTCP
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** RTCP protocol values */
+enum {
+ RTCP_HDR_SIZE = 4, /**< Size of common RTCP header */
+ RTCP_SRC_SIZE = 4, /**< Size of Source field */
+ RTCP_SR_SIZE = 20, /**< Size of Sender Information */
+ RTCP_RR_SIZE = 24, /**< Size of Report Block */
+ RTCP_APP_SIZE = 8, /**< Size of Application packet */
+ RTCP_FIR_SIZE = 4, /**< Size of FIR packet */
+ RTCP_NACK_SIZE = 8, /**< Size of NACK packet */
+ RTCP_FB_SIZE = 8, /**< Size of Feedback packets */
+ RTCP_MAX_SDES = 255, /**< Maximum text length for SDES */
+ RTCP_HEADROOM = 4, /**< Headroom in RTCP packets */
+};
+
+/** NTP Time */
+struct ntp_time {
+ uint32_t hi; /**< Seconds since 0h UTC on 1 January 1900 */
+ uint32_t lo; /**< Fraction of seconds */
+};
+
+struct hash;
+
+/** Per-source state information */
+struct rtp_source {
+ struct sa rtp_peer; /**< IP-address of the RTP source */
+ uint16_t max_seq; /**< Highest seq. number seen */
+ uint32_t cycles; /**< Shifted count of seq. number cycles */
+ uint32_t base_seq; /**< Base seq number */
+ uint32_t bad_seq; /**< Last 'bad' seq number + 1 */
+ uint32_t probation; /**< Sequ. packets till source is valid */
+ uint32_t received; /**< Packets received */
+ uint32_t expected_prior; /**< Packet expected at last interval */
+ uint32_t received_prior; /**< Packet received at last interval */
+ int transit; /**< Relative trans time for prev pkt */
+ uint32_t jitter; /**< Estimated jitter */
+ size_t rtp_rx_bytes; /**< Number of RTP bytes received */
+ uint64_t sr_recv; /**< When the last SR was received */
+ struct ntp_time last_sr; /**< NTP Timestamp from last SR received */
+ uint32_t rtp_ts; /**< RTP timestamp */
+ uint32_t psent; /**< RTP packets sent */
+ uint32_t osent; /**< RTP octets sent */
+};
+
+/** RTP Member */
+struct rtp_member {
+ struct le le; /**< Hash-table element */
+ struct rtp_source *s; /**< RTP source state */
+ uint32_t src; /**< Source - used for hash-table lookup */
+ int cum_lost; /**< Cumulative number of packets lost */
+ uint32_t jit; /**< Jitter in [us] */
+ uint32_t rtt; /**< Round-trip time in [us] */
+};
+
+
+/* Member */
+struct rtp_member *member_add(struct hash *ht, uint32_t src);
+struct rtp_member *member_find(struct hash *ht, uint32_t src);
+
+/* Source */
+void source_init_seq(struct rtp_source *s, uint16_t seq);
+int source_update_seq(struct rtp_source *s, uint16_t seq);
+void source_calc_jitter(struct rtp_source *s, uint32_t rtp_ts,
+ uint32_t arrival);
+int source_calc_lost(const struct rtp_source *s);
+uint8_t source_calc_fraction_lost(struct rtp_source *s);
+
+/* RR (Reception report) */
+int rtcp_rr_alloc(struct rtcp_rr **rrp, size_t count);
+int rtcp_rr_encode(struct mbuf *mb, const struct rtcp_rr *rr);
+int rtcp_rr_decode(struct mbuf *mb, struct rtcp_rr *rr);
+
+/* SDES (Source Description) */
+int rtcp_sdes_decode(struct mbuf *mb, struct rtcp_sdes *sdes);
+
+/* RTCP Feedback */
+int rtcp_rtpfb_gnack_encode(struct mbuf *mb, uint16_t pid, uint16_t blp);
+int rtcp_psfb_sli_encode(struct mbuf *mb, uint16_t first, uint16_t number,
+ uint8_t picid);
+int rtcp_rtpfb_decode(struct mbuf *mb, struct rtcp_msg *msg);
+int rtcp_psfb_decode(struct mbuf *mb, struct rtcp_msg *msg);
+
+/** NTP Time */
+struct timeval;
+void unix2ntp(struct ntp_time *ntp, const struct timeval *tv);
+void ntp2unix(struct timeval *tv, const struct ntp_time *ntp);
+int ntp_time_get(struct ntp_time *ntp);
+uint32_t ntp_compact(const struct ntp_time *ntp);
+uint64_t ntp_compact2us(uint32_t ntpc);
+
+/* RTP Socket */
+struct rtcp_sess *rtp_rtcp_sess(const struct rtp_sock *rs);
+
+/* RTCP message */
+typedef int (rtcp_encode_h)(struct mbuf *mb, void *arg);
+
+int rtcp_hdr_encode(struct mbuf *mb, uint8_t count, enum rtcp_type type,
+ uint16_t length);
+int rtcp_hdr_decode(struct mbuf *mb, struct rtcp_hdr *hdr);
+int rtcp_vencode(struct mbuf *mb, enum rtcp_type type, uint32_t count,
+ va_list ap);
+
+/* RTCP Session */
+struct rtcp_sess;
+
+int rtcp_sess_alloc(struct rtcp_sess **sessp, struct rtp_sock *rs);
+int rtcp_enable(struct rtcp_sess *sess, bool enabled, const char *cname);
+int rtcp_send(struct rtp_sock *rs, struct mbuf *mb);
+void rtcp_handler(struct rtcp_sess *sess, struct rtcp_msg *msg);
+void rtcp_sess_tx_rtp(struct rtcp_sess *sess, uint32_t ts,
+ size_t payload_size);
+void rtcp_sess_rx_rtp(struct rtcp_sess *sess, uint16_t seq, uint32_t ts,
+ uint32_t src, size_t payload_size,
+ const struct sa *peer);
diff --git a/src/rtp/rtp.c b/src/rtp/rtp.c
new file mode 100644
index 0000000..066199d
--- /dev/null
+++ b/src/rtp/rtp.c
@@ -0,0 +1,600 @@
+/**
+ * @file rtp.c Real-time Transport Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sys.h>
+#include <re_net.h>
+#include <re_udp.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+#define DEBUG_MODULE "rtp"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Defines an RTP Socket */
+struct rtp_sock {
+ /** Encode data */
+ struct {
+ uint16_t seq; /**< Sequence number */
+ uint32_t ssrc; /**< Synchronizing source */
+ } enc;
+ int proto; /**< Transport Protocol */
+ void *sock_rtp; /**< RTP Socket */
+ void *sock_rtcp; /**< RTCP Socket */
+ struct sa local; /**< Local RTP Address */
+ struct sa rtcp_peer; /**< RTCP address of Peer */
+ rtp_recv_h *recvh; /**< RTP Receive handler */
+ rtcp_recv_h *rtcph; /**< RTCP Receive handler */
+ void *arg; /**< Handler argument */
+ struct rtcp_sess *rtcp; /**< RTCP Session */
+ bool rtcp_mux; /**< RTP/RTCP multiplexing */
+};
+
+
+/**
+ * Encode the RTP header into a buffer
+ *
+ * @param mb Buffer to encode into
+ * @param hdr RTP Header to be encoded
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtp_hdr_encode(struct mbuf *mb, const struct rtp_header *hdr)
+{
+ uint8_t buf[2];
+ int err, i;
+
+ if (!mb || !hdr)
+ return EINVAL;
+
+ buf[0] = (hdr->ver & 0x02) << 6;
+ buf[0] |= (hdr->pad & 0x01) << 5;
+ buf[0] |= (hdr->ext & 0x01) << 4;
+ buf[0] |= (hdr->cc & 0x0f) << 0;
+ buf[1] = (hdr->m & 0x01) << 7;
+ buf[1] |= (hdr->pt & 0x7f) << 0;
+
+ err = mbuf_write_mem(mb, buf, sizeof(buf));
+ err |= mbuf_write_u16(mb, htons(hdr->seq));
+ err |= mbuf_write_u32(mb, htonl(hdr->ts));
+ err |= mbuf_write_u32(mb, htonl(hdr->ssrc));
+
+ for (i=0; i<hdr->cc; i++) {
+ err |= mbuf_write_u32(mb, htonl(hdr->csrc[i]));
+ }
+
+ return err;
+}
+
+
+/**
+ * Decode an RTP header from a buffer
+ *
+ * @param hdr RTP Header to decode into
+ * @param mb Buffer to decode from
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtp_hdr_decode(struct rtp_header *hdr, struct mbuf *mb)
+{
+ uint8_t buf[2];
+ int err, i;
+ size_t header_len;
+
+ if (!hdr || !mb)
+ return EINVAL;
+
+ if (mbuf_get_left(mb) < RTP_HEADER_SIZE)
+ return EBADMSG;
+
+ err = mbuf_read_mem(mb, buf, sizeof(buf));
+ if (err)
+ return err;
+
+ hdr->ver = (buf[0] >> 6) & 0x03;
+ hdr->pad = (buf[0] >> 5) & 0x01;
+ hdr->ext = (buf[0] >> 4) & 0x01;
+ hdr->cc = (buf[0] >> 0) & 0x0f;
+ hdr->m = (buf[1] >> 7) & 0x01;
+ hdr->pt = (buf[1] >> 0) & 0x7f;
+
+ hdr->seq = ntohs(mbuf_read_u16(mb));
+ hdr->ts = ntohl(mbuf_read_u32(mb));
+ hdr->ssrc = ntohl(mbuf_read_u32(mb));
+
+ header_len = hdr->cc*sizeof(uint32_t);
+ if (mbuf_get_left(mb) < header_len)
+ return EBADMSG;
+
+ for (i=0; i<hdr->cc; i++) {
+ hdr->csrc[i] = ntohl(mbuf_read_u32(mb));
+ }
+
+ if (hdr->ext) {
+ if (mbuf_get_left(mb) < 4)
+ return EBADMSG;
+
+ hdr->x.type = ntohs(mbuf_read_u16(mb));
+ hdr->x.len = ntohs(mbuf_read_u16(mb));
+
+ if (mbuf_get_left(mb) < hdr->x.len*sizeof(uint32_t))
+ return EBADMSG;
+
+ mb->pos += hdr->x.len*sizeof(uint32_t);
+ }
+
+ return 0;
+}
+
+
+static void destructor(void *data)
+{
+ struct rtp_sock *rs = data;
+
+ switch (rs->proto) {
+
+ case IPPROTO_UDP:
+ udp_handler_set(rs->sock_rtp, NULL, NULL);
+ udp_handler_set(rs->sock_rtcp, NULL, NULL);
+ break;
+
+ default:
+ break;
+ }
+
+ /* Destroy RTCP Session now */
+ mem_deref(rs->rtcp);
+
+ mem_deref(rs->sock_rtp);
+ mem_deref(rs->sock_rtcp);
+}
+
+
+static void rtcp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct rtp_sock *rs = arg;
+ struct rtcp_msg *msg;
+
+ while (0 == rtcp_decode(&msg, mb)) {
+
+ /* handle internally first */
+ rtcp_handler(rs->rtcp, msg);
+
+ /* then relay to application */
+ if (rs->rtcph)
+ rs->rtcph(src, msg, rs->arg);
+
+ mem_deref(msg);
+ }
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct rtp_sock *rs = arg;
+ struct rtp_header hdr;
+ int err;
+
+ /* Handle RTCP multiplexed on RTP-port */
+ if (rs->rtcp_mux) {
+ uint8_t pt;
+
+ if (mbuf_get_left(mb) < 2)
+ return;
+
+ pt = mbuf_buf(mb)[1] & 0x7f;
+
+ if (64 <= pt && pt <= 95) {
+ rtcp_recv_handler(src, mb, arg);
+ return;
+ }
+ }
+
+ err = rtp_decode(rs, mb, &hdr);
+ if (err)
+ return;
+
+ if (rs->rtcp) {
+ rtcp_sess_rx_rtp(rs->rtcp, hdr.seq, hdr.ts,
+ hdr.ssrc, mbuf_get_left(mb), src);
+ }
+
+ if (rs->recvh)
+ rs->recvh(src, &hdr, mb, rs->arg);
+}
+
+
+static int udp_range_listen(struct rtp_sock *rs, const struct sa *ip,
+ uint16_t min_port, uint16_t max_port)
+{
+ struct sa rtcp;
+ int tries = 64;
+ int err = 0;
+
+ rs->local = rtcp = *ip;
+
+ /* try hard */
+ while (tries--) {
+ struct udp_sock *us_rtp, *us_rtcp;
+ uint16_t port;
+
+ port = (min_port + (rand_u16() % (max_port - min_port)));
+ port &= 0xfffe;
+
+ sa_set_port(&rs->local, port);
+ err = udp_listen(&us_rtp, &rs->local, udp_recv_handler, rs);
+ if (err)
+ continue;
+
+ sa_set_port(&rtcp, port + 1);
+ err = udp_listen(&us_rtcp, &rtcp, rtcp_recv_handler, rs);
+ if (err) {
+ mem_deref(us_rtp);
+ continue;
+ }
+
+ /* OK */
+ rs->sock_rtp = us_rtp;
+ rs->sock_rtcp = us_rtcp;
+ break;
+ }
+
+ return err;
+}
+
+
+/**
+ * Allocate a new RTP socket
+ *
+ * @param rsp Pointer to returned RTP socket
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtp_alloc(struct rtp_sock **rsp)
+{
+ struct rtp_sock *rs;
+
+ if (!rsp)
+ return EINVAL;
+
+ rs = mem_zalloc(sizeof(*rs), destructor);
+ if (!rs)
+ return ENOMEM;
+
+ sa_init(&rs->rtcp_peer, AF_UNSPEC);
+
+ rs->enc.seq = rand_u16() & 0x7fff;
+ rs->enc.ssrc = rand_u32();
+
+ *rsp = rs;
+
+ return 0;
+}
+
+
+/**
+ * Listen on an RTP/RTCP Socket
+ *
+ * @param rsp Pointer to returned RTP socket
+ * @param proto Transport protocol
+ * @param ip Local IP address
+ * @param min_port Minimum port range
+ * @param max_port Maximum port range
+ * @param enable_rtcp True to enable RTCP Session
+ * @param recvh RTP Receive handler
+ * @param rtcph RTCP Receive handler
+ * @param arg Handler argument
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtp_listen(struct rtp_sock **rsp, int proto, const struct sa *ip,
+ uint16_t min_port, uint16_t max_port, bool enable_rtcp,
+ rtp_recv_h *recvh, rtcp_recv_h *rtcph, void *arg)
+{
+ struct rtp_sock *rs;
+ int err;
+
+ if (!ip || min_port >= max_port || !recvh)
+ return EINVAL;
+
+ err = rtp_alloc(&rs);
+ if (err)
+ return err;
+
+ rs->proto = proto;
+ rs->recvh = recvh;
+ rs->rtcph = rtcph;
+ rs->arg = arg;
+
+ /* Optional RTCP */
+ if (enable_rtcp) {
+ err = rtcp_sess_alloc(&rs->rtcp, rs);
+ if (err)
+ goto out;
+ }
+
+ switch (proto) {
+
+ case IPPROTO_UDP:
+ err = udp_range_listen(rs, ip, min_port, max_port);
+ break;
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ out:
+ if (err)
+ mem_deref(rs);
+ else
+ *rsp = rs;
+
+ return err;
+}
+
+
+/**
+ * Encode a new RTP header into the beginning of the buffer
+ *
+ * @param rs RTP Socket
+ * @param ext Extension bit
+ * @param marker Marker bit
+ * @param pt Payload type
+ * @param ts Timestamp
+ * @param mb Memory buffer
+ *
+ * @return 0 for success, otherwise errorcode
+ *
+ * @note The buffer must have enough space for the RTP header
+ */
+int rtp_encode(struct rtp_sock *rs, bool ext, bool marker, uint8_t pt,
+ uint32_t ts, struct mbuf *mb)
+{
+ struct rtp_header hdr;
+
+ if (!rs || pt&~0x7f || !mb)
+ return EINVAL;
+
+ hdr.ver = RTP_VERSION;
+ hdr.pad = false;
+ hdr.ext = ext;
+ hdr.cc = 0;
+ hdr.m = marker ? 1 : 0;
+ hdr.pt = pt;
+ hdr.seq = rs->enc.seq++;
+ hdr.ts = ts;
+ hdr.ssrc = rs->enc.ssrc;
+
+ return rtp_hdr_encode(mb, &hdr);
+}
+
+
+/**
+ * Decode an RTP packet and return decoded RTP header and payload
+ *
+ * @param rs RTP Socket
+ * @param mb Memory buffer containing RTP packet
+ * @param hdr RTP header (set on return)
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtp_decode(struct rtp_sock *rs, struct mbuf *mb,
+ struct rtp_header *hdr)
+{
+ int err;
+
+ if (!rs || !mb || !hdr)
+ return EINVAL;
+
+ memset(hdr, 0, sizeof(*hdr));
+ err = rtp_hdr_decode(hdr, mb);
+ if (err)
+ return err;
+
+ if (RTP_VERSION != hdr->ver)
+ return EBADMSG;
+
+ return 0;
+}
+
+
+/**
+ * Send an RTP packet to a peer
+ *
+ * @param rs RTP Socket
+ * @param dst Destination address
+ * @param ext Extension bit
+ * @param marker Marker bit
+ * @param pt Payload type
+ * @param ts Timestamp
+ * @param mb Payload buffer
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtp_send(struct rtp_sock *rs, const struct sa *dst, bool ext,
+ bool marker, uint8_t pt, uint32_t ts, struct mbuf *mb)
+{
+ size_t pos;
+ int err;
+
+ if (!rs || !mb)
+ return EINVAL;
+
+ if (mb->pos < RTP_HEADER_SIZE) {
+ DEBUG_WARNING("rtp_send: buffer must have space for"
+ " rtp header (pos=%u, end=%u)\n",
+ mb->pos, mb->end);
+ return EBADMSG;
+ }
+
+ mbuf_advance(mb, -RTP_HEADER_SIZE);
+
+ pos = mb->pos;
+
+ err = rtp_encode(rs, ext, marker, pt, ts, mb);
+ if (err)
+ return err;
+
+ if (rs->rtcp)
+ rtcp_sess_tx_rtp(rs->rtcp, ts, mbuf_get_left(mb));
+
+ mb->pos = pos;
+
+ return udp_send(rs->sock_rtp, dst, mb);
+}
+
+
+/**
+ * Get the RTP transport socket from an RTP/RTCP Socket
+ *
+ * @param rs RTP Socket
+ *
+ * @return Transport socket for RTP
+ */
+void *rtp_sock(const struct rtp_sock *rs)
+{
+ return rs ? rs->sock_rtp : NULL;
+}
+
+
+/**
+ * Get the RTCP transport socket from an RTP/RTCP Socket
+ *
+ * @param rs RTP Socket
+ *
+ * @return Transport socket for RTCP
+ */
+void *rtcp_sock(const struct rtp_sock *rs)
+{
+ return rs ? rs->sock_rtcp : NULL;
+}
+
+
+/**
+ * Get the local RTP address for an RTP/RTCP Socket
+ *
+ * @param rs RTP Socket
+ *
+ * @return Local RTP address
+ */
+const struct sa *rtp_local(const struct rtp_sock *rs)
+{
+ return rs ? &rs->local : NULL;
+}
+
+
+/**
+ * Get the Synchronizing source for an RTP/RTCP Socket
+ *
+ * @param rs RTP Socket
+ *
+ * @return Synchronizing source
+ */
+uint32_t rtp_sess_ssrc(const struct rtp_sock *rs)
+{
+ return rs ? rs->enc.ssrc : 0;
+}
+
+
+/**
+ * Get the RTCP-Session for an RTP/RTCP Socket
+ *
+ * @param rs RTP Socket
+ *
+ * @return RTCP-Session
+ */
+struct rtcp_sess *rtp_rtcp_sess(const struct rtp_sock *rs)
+{
+ return rs ? rs->rtcp : NULL;
+}
+
+
+/**
+ * Start the RTCP Session
+ *
+ * @param rs RTP Socket
+ * @param cname Canonical Name
+ * @param peer IP-Address of RTCP Peer
+ */
+void rtcp_start(struct rtp_sock *rs, const char *cname,
+ const struct sa *peer)
+{
+ if (!rs)
+ return;
+
+ if (peer)
+ rs->rtcp_peer = *peer;
+
+ (void)rtcp_enable(rs->rtcp, peer != NULL, cname);
+}
+
+
+/**
+ * Enable RTCP-multiplexing on RTP-port
+ *
+ * @param rs RTP Socket
+ * @param enabled True to enable, false to disable
+ */
+void rtcp_enable_mux(struct rtp_sock *rs, bool enabled)
+{
+ if (!rs)
+ return;
+
+ rs->rtcp_mux = enabled;
+}
+
+
+/**
+ * Send RTCP packet(s) to the Peer
+ *
+ * @param rs RTP Socket
+ * @param mb Buffer containing the RTCP Packet(s)
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int rtcp_send(struct rtp_sock *rs, struct mbuf *mb)
+{
+ if (!rs || !rs->sock_rtcp || !sa_isset(&rs->rtcp_peer, SA_ALL))
+ return EINVAL;
+
+ return udp_send(rs->rtcp_mux ? rs->sock_rtp : rs->sock_rtcp,
+ &rs->rtcp_peer, mb);
+}
+
+
+/**
+ * RTP Debug handler, use with fmt %H
+ *
+ * @param pf Print function
+ * @param rs RTP Socket
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtp_debug(struct re_printf *pf, const struct rtp_sock *rs)
+{
+ int err;
+
+ if (!rs || !pf)
+ return EINVAL;
+
+ err = re_hprintf(pf, "RTP debug:\n");
+ err |= re_hprintf(pf, " Encode: seq=%u ssrc=0x%lx\n",
+ rs->enc.seq, rs->enc.ssrc);
+
+ if (rs->rtcp)
+ err |= rtcp_debug(pf, rs);
+
+ return err;
+}
diff --git a/src/rtp/sdes.c b/src/rtp/sdes.c
new file mode 100644
index 0000000..711d8a9
--- /dev/null
+++ b/src/rtp/sdes.c
@@ -0,0 +1,148 @@
+/**
+ * @file sdes.c RTCP Source Description
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+#define DEBUG_MODULE "rtcp_sdes"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+enum {
+ RTCP_SDES_MIN_SIZE = 1,
+};
+
+
+/**
+ * Encode one SDES chunk into mbuffer
+ *
+ * @param mb Buffer to encode into
+ * @param src First SSRC/CSRC
+ * @param itemc Number of SDES items to encode
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtcp_sdes_encode(struct mbuf *mb, uint32_t src, uint32_t itemc, ...)
+{
+ va_list ap;
+ size_t start;
+ int err = 0;
+
+ if (!mb || !itemc)
+ return EINVAL;
+
+ va_start(ap, itemc);
+
+ start = mb->pos;
+ err = mbuf_write_u32(mb, htonl(src));
+
+ /* add all SDES items */
+ while (itemc-- && !err) {
+ const uint8_t type = va_arg(ap, int);
+ const char *v = va_arg(ap, const char *);
+ size_t len;
+ if (!v)
+ continue;
+
+ len = strlen(v); /* note: max 255 chars */
+ if (len > 255) {
+ err = EINVAL;
+ goto out;
+ }
+
+ err = mbuf_write_u8(mb, type);
+ err |= mbuf_write_u8(mb, len & 0xff);
+ err |= mbuf_write_mem(mb, (uint8_t *)v, len);
+ }
+
+ /* END padding */
+ err |= mbuf_write_u8(mb, RTCP_SDES_END);
+ while ((mb->pos - start) & 0x3)
+ err |= mbuf_write_u8(mb, RTCP_SDES_END);
+
+ out:
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Decode SDES items from a buffer
+ *
+ * @param mb Buffer to decode from
+ * @param sdes RTCP SDES to decode into
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtcp_sdes_decode(struct mbuf *mb, struct rtcp_sdes *sdes)
+{
+ size_t start;
+
+ if (!sdes)
+ return EINVAL;
+ if (mbuf_get_left(mb) < RTCP_SRC_SIZE)
+ return EBADMSG;
+
+ start = mb->pos;
+ sdes->src = ntohl(mbuf_read_u32(mb));
+
+ /* Decode all SDES items */
+ while (mbuf_get_left(mb) >= RTCP_SDES_MIN_SIZE) {
+ uint8_t type;
+ struct rtcp_sdes_item *item;
+
+ type = mbuf_read_u8(mb);
+ if (type == RTCP_SDES_END)
+ break;
+
+ if (mbuf_get_left(mb) < 1)
+ return EBADMSG;
+
+ if (!sdes->itemv) {
+ sdes->itemv = mem_alloc(sizeof(*sdes->itemv), NULL);
+ if (!sdes->itemv)
+ return ENOMEM;
+ }
+ else {
+ const size_t sz = (sdes->n + 1) * sizeof(*sdes->itemv);
+ struct rtcp_sdes_item *itemv;
+
+ itemv = mem_realloc(sdes->itemv, sz);
+ if (!itemv)
+ return ENOMEM;
+
+ sdes->itemv = itemv;
+ }
+
+ item = &sdes->itemv[sdes->n];
+
+ item->type = (enum rtcp_sdes_type)type;
+ item->length = mbuf_read_u8(mb);
+ if (mbuf_get_left(mb) < item->length)
+ return EBADMSG;
+ item->data = mem_alloc(item->length, NULL);
+ if (!item->data)
+ return ENOMEM;
+ (void)mbuf_read_mem(mb, (uint8_t *)item->data, item->length);
+
+ sdes->n++;
+ }
+
+ /* slurp padding */
+ while ((mb->pos - start) & 0x3 && mbuf_get_left(mb))
+ ++mb->pos;
+
+ return 0;
+}
diff --git a/src/rtp/sess.c b/src/rtp/sess.c
new file mode 100644
index 0000000..2115210
--- /dev/null
+++ b/src/rtp/sess.c
@@ -0,0 +1,675 @@
+/**
+ * @file rtp/sess.c Real-time Transport Control Protocol Session
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <time.h>
+#ifdef WIN32
+#include <winsock2.h>
+#endif
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_lock.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+#define DEBUG_MODULE "rtcp_sess"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** RTP protocol values */
+enum {
+ RTCP_INTERVAL = 5000, /**< Interval in [ms] between sending reports */
+ MAX_MEMBERS = 8,
+};
+
+/** RTP Transmit stats */
+struct txstat {
+ uint32_t psent; /**< Total number of RTP packets sent */
+ uint32_t osent; /**< Total number of RTP octets sent */
+ uint64_t jfs_ref; /**< Timer ticks at RTP timestamp reference */
+ uint32_t ts_ref; /**< RTP timestamp reference (transmit) */
+ bool ts_synced; /**< RTP timestamp synchronization flag */
+};
+
+/** RTCP Session */
+struct rtcp_sess {
+ struct rtp_sock *rs; /**< RTP Socket */
+ struct hash *members; /**< Member table */
+ struct tmr tmr; /**< Event sender timer */
+ char *cname; /**< Canonical Name */
+ uint32_t memberc; /**< Number of members */
+ uint32_t senderc; /**< Number of senders */
+ uint32_t srate_tx; /**< Transmit sampling rate */
+ uint32_t srate_rx; /**< Receive sampling rate */
+
+ /* stats */
+ struct lock *lock; /**< Lock for txstat */
+ struct txstat txstat; /**< Local transmit statistics */
+};
+
+
+/* Prototypes */
+static void schedule(struct rtcp_sess *sess);
+static int send_bye_packet(struct rtcp_sess *sess);
+
+
+static void sess_destructor(void *data)
+{
+ struct rtcp_sess *sess = data;
+
+ if (sess->cname)
+ (void)send_bye_packet(sess);
+
+ tmr_cancel(&sess->tmr);
+
+ mem_deref(sess->cname);
+ hash_flush(sess->members);
+ mem_deref(sess->members);
+ mem_deref(sess->lock);
+}
+
+
+static struct rtp_member *get_member(struct rtcp_sess *sess, uint32_t src)
+{
+ struct rtp_member *mbr;
+
+ mbr = member_find(sess->members, src);
+ if (mbr)
+ return mbr;
+
+ if (sess->memberc >= MAX_MEMBERS)
+ return NULL;
+
+ mbr = member_add(sess->members, src);
+ if (!mbr)
+ return NULL;
+
+ ++sess->memberc;
+
+ return mbr;
+}
+
+
+/** Calculate Round-Trip Time in [microseconds] */
+static void calc_rtt(uint32_t *rtt, uint32_t lsr, uint32_t dlsr)
+{
+ struct ntp_time ntp_time;
+ uint64_t a_us, lsr_us, dlsr_us;
+ int err;
+
+ err = ntp_time_get(&ntp_time);
+ if (err)
+ return;
+
+ a_us = ntp_compact2us(ntp_compact(&ntp_time));
+ lsr_us = ntp_compact2us(lsr);
+ dlsr_us = 1000000ULL * dlsr / 65536;
+
+ /* RTT delay is (A - LSR - DLSR) */
+ *rtt = MAX((int)(a_us - lsr_us - dlsr_us), 0);
+}
+
+
+/** Decode Reception Report block */
+static void handle_rr_block(struct rtcp_sess *sess, struct rtp_member *mbr,
+ const struct rtcp_rr *rr)
+{
+ /* Lost */
+ mbr->cum_lost = rr->lost;
+
+ /* Interarrival jitter */
+ if (sess->srate_tx)
+ mbr->jit = 1000000 * rr->jitter / sess->srate_tx;
+
+ /* round-trip propagation delay as (A - LSR - DLSR) */
+ if (rr->lsr && rr->dlsr)
+ calc_rtt(&mbr->rtt, rr->lsr, rr->dlsr);
+}
+
+
+/** Handle incoming RR (Receiver Report) packet */
+static void handle_incoming_rr(struct rtcp_sess *sess,
+ const struct rtcp_msg *msg)
+{
+ struct rtp_member *mbr;
+ uint32_t i;
+
+ mbr = get_member(sess, msg->r.rr.ssrc);
+ if (!mbr)
+ return;
+
+ for (i=0; i<msg->hdr.count; i++)
+ handle_rr_block(sess, mbr, &msg->r.rr.rrv[i]);
+}
+
+
+/** Handle incoming SR (Sender Report) packet */
+static void handle_incoming_sr(struct rtcp_sess *sess,
+ const struct rtcp_msg *msg)
+{
+ struct rtp_member *mbr;
+ uint32_t i;
+
+ mbr = get_member(sess, msg->r.sr.ssrc);
+ if (!mbr) {
+ DEBUG_WARNING("0x%08x: could not add member\n",
+ msg->r.sr.ssrc);
+ return;
+ }
+
+ if (mbr->s) {
+ /* Save time when SR was received */
+ mbr->s->sr_recv = tmr_jiffies();
+
+ /* Save NTP timestamp from SR */
+ mbr->s->last_sr.hi = msg->r.sr.ntp_sec;
+ mbr->s->last_sr.lo = msg->r.sr.ntp_frac;
+ mbr->s->rtp_ts = msg->r.sr.rtp_ts;
+ mbr->s->psent = msg->r.sr.psent;
+ mbr->s->osent = msg->r.sr.osent;
+ }
+
+ for (i=0; i<msg->hdr.count; i++)
+ handle_rr_block(sess, mbr, &msg->r.sr.rrv[i]);
+}
+
+
+static void handle_incoming_bye(struct rtcp_sess *sess,
+ const struct rtcp_msg *msg)
+{
+ uint32_t i;
+
+ for (i=0; i<msg->hdr.count; i++) {
+
+ struct rtp_member *mbr;
+
+ mbr = member_find(sess->members, msg->r.bye.srcv[i]);
+ if (mbr) {
+ if (mbr->s)
+ --sess->senderc;
+
+ --sess->memberc;
+ mem_deref(mbr);
+ }
+ }
+}
+
+
+void rtcp_handler(struct rtcp_sess *sess, struct rtcp_msg *msg)
+{
+ if (!sess || !msg)
+ return;
+
+ switch (msg->hdr.pt) {
+
+ case RTCP_SR:
+ handle_incoming_sr(sess, msg);
+ break;
+
+ case RTCP_RR:
+ handle_incoming_rr(sess, msg);
+ break;
+
+ case RTCP_BYE:
+ handle_incoming_bye(sess, msg);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+int rtcp_sess_alloc(struct rtcp_sess **sessp, struct rtp_sock *rs)
+{
+ struct rtcp_sess *sess;
+ int err;
+
+ if (!sessp)
+ return EINVAL;
+
+ sess = mem_zalloc(sizeof(*sess), sess_destructor);
+ if (!sess)
+ return ENOMEM;
+
+ sess->rs = rs;
+ tmr_init(&sess->tmr);
+
+ err = lock_alloc(&sess->lock);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&sess->members, MAX_MEMBERS);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+/**
+ * Set the Sampling-rate on an RTCP Session
+ *
+ * @param rs RTP Socket
+ * @param srate_tx Transmit samplerate
+ * @param srate_rx Receive samplerate
+ */
+void rtcp_set_srate(struct rtp_sock *rs, uint32_t srate_tx, uint32_t srate_rx)
+{
+ struct rtcp_sess *sess = rtp_rtcp_sess(rs);
+ if (!sess)
+ return;
+
+ sess->srate_tx = srate_tx;
+ sess->srate_rx = srate_rx;
+}
+
+
+/**
+ * Set the transmit Sampling-rate on an RTCP Session
+ *
+ * @param rs RTP Socket
+ * @param srate_tx Transmit samplerate
+ */
+void rtcp_set_srate_tx(struct rtp_sock *rs, uint32_t srate_tx)
+{
+ struct rtcp_sess *sess = rtp_rtcp_sess(rs);
+ if (!sess)
+ return;
+
+ sess->srate_tx = srate_tx;
+}
+
+
+/**
+ * Set the receive Sampling-rate on an RTCP Session
+ *
+ * @param rs RTP Socket
+ * @param srate_rx Receive samplerate
+ */
+void rtcp_set_srate_rx(struct rtp_sock *rs, uint32_t srate_rx)
+{
+ struct rtcp_sess *sess = rtp_rtcp_sess(rs);
+ if (!sess)
+ return;
+
+ sess->srate_rx = srate_rx;
+}
+
+
+int rtcp_enable(struct rtcp_sess *sess, bool enabled, const char *cname)
+{
+ int err;
+
+ if (!sess)
+ return EINVAL;
+
+ sess->cname = mem_deref(sess->cname);
+ err = str_dup(&sess->cname, cname);
+ if (err)
+ return err;
+
+ if (enabled)
+ schedule(sess);
+ else
+ tmr_cancel(&sess->tmr);
+
+ return 0;
+}
+
+
+/** Calculate LSR (middle 32 bits out of 64 in the NTP timestamp) */
+static uint32_t calc_lsr(const struct ntp_time *last_sr)
+{
+ return last_sr->hi ? ntp_compact(last_sr) : 0;
+}
+
+
+static uint32_t calc_dlsr(uint64_t sr_recv)
+{
+ if (sr_recv) {
+ const uint64_t diff = tmr_jiffies() - sr_recv;
+ return (uint32_t)((65536 * diff) / 1000);
+ }
+ else {
+ return 0;
+ }
+}
+
+
+static bool sender_apply_handler(struct le *le, void *arg)
+{
+ struct rtp_member *mbr = le->data;
+ struct rtp_source *s = mbr->s;
+ struct mbuf *mb = arg;
+ struct rtcp_rr rr;
+
+ if (!s)
+ return false;
+
+ /* Initialise the members */
+ rr.ssrc = mbr->src;
+ rr.fraction = source_calc_fraction_lost(s);
+ rr.lost = source_calc_lost(s);
+ rr.last_seq = s->cycles | s->max_seq;
+ rr.jitter = s->jitter >> 4;
+ rr.lsr = calc_lsr(&s->last_sr);
+ rr.dlsr = calc_dlsr(s->sr_recv);
+
+ return 0 != rtcp_rr_encode(mb, &rr);
+}
+
+
+static int encode_handler(struct mbuf *mb, void *arg)
+{
+ struct hash *members = arg;
+
+ /* copy all report blocks */
+ if (hash_apply(members, sender_apply_handler, mb))
+ return ENOMEM;
+
+ return 0;
+}
+
+
+/** Create a Sender Report */
+static int mk_sr(struct rtcp_sess *sess, struct mbuf *mb)
+{
+ struct ntp_time ntp = {0, 0};
+ struct txstat txstat;
+ uint32_t dur, rtp_ts = 0;
+ int err;
+
+ err = ntp_time_get(&ntp);
+ if (err)
+ return err;
+
+ lock_write_get(sess->lock);
+ txstat = sess->txstat;
+ sess->txstat.ts_synced = false;
+ lock_rel(sess->lock);
+
+ if (txstat.jfs_ref) {
+ dur = (uint32_t)(tmr_jiffies() - txstat.jfs_ref);
+ rtp_ts = txstat.ts_ref + dur * sess->srate_tx / 1000;
+ }
+
+ err = rtcp_encode(mb, RTCP_SR, sess->senderc, rtp_sess_ssrc(sess->rs),
+ ntp.hi, ntp.lo, rtp_ts, txstat.psent, txstat.osent,
+ encode_handler, sess->members);
+ if (err)
+ return err;
+
+ return err;
+}
+
+
+static int sdes_encode_handler(struct mbuf *mb, void *arg)
+{
+ struct rtcp_sess *sess = arg;
+
+ return rtcp_sdes_encode(mb, rtp_sess_ssrc(sess->rs), 1,
+ RTCP_SDES_CNAME, sess->cname);
+}
+
+
+static int mk_sdes(struct rtcp_sess *sess, struct mbuf *mb)
+{
+ return rtcp_encode(mb, RTCP_SDES, 1, sdes_encode_handler, sess);
+}
+
+
+static int send_rtcp_report(struct rtcp_sess *sess)
+{
+ struct mbuf *mb;
+ int err;
+
+ mb = mbuf_alloc(512);
+ if (!mb)
+ return ENOMEM;
+
+ mb->pos = RTCP_HEADROOM;
+
+ err = mk_sr(sess, mb);
+ err |= mk_sdes(sess, mb);
+ if (err)
+ goto out;
+
+ mb->pos = RTCP_HEADROOM;
+
+ err = rtcp_send(sess->rs, mb);
+
+ out:
+ mem_deref(mb);
+ return err;
+}
+
+
+static int send_bye_packet(struct rtcp_sess *sess)
+{
+ const uint32_t ssrc = rtp_sess_ssrc(sess->rs);
+ struct mbuf *mb;
+ int err;
+
+ mb = mbuf_alloc(512);
+ if (!mb)
+ return ENOMEM;
+
+ mb->pos = RTCP_HEADROOM;
+
+ err = rtcp_encode(mb, RTCP_BYE, 1, &ssrc, "Adjo");
+ err |= mk_sdes(sess, mb);
+ if (err)
+ goto out;
+
+ mb->pos = RTCP_HEADROOM;
+
+ err = rtcp_send(sess->rs, mb);
+
+ out:
+ mem_deref(mb);
+ return err;
+}
+
+
+static void timeout(void *arg)
+{
+ struct rtcp_sess *sess = arg;
+ int err;
+
+ err = send_rtcp_report(sess);
+ if (err) {
+ DEBUG_WARNING("Send RTCP report failed: %m\n", err);
+ }
+
+ schedule(sess);
+}
+
+
+static void schedule(struct rtcp_sess *sess)
+{
+ tmr_start(&sess->tmr, RTCP_INTERVAL, timeout, sess);
+}
+
+
+void rtcp_sess_tx_rtp(struct rtcp_sess *sess, uint32_t ts, size_t payload_size)
+{
+ if (!sess)
+ return;
+
+ lock_write_get(sess->lock);
+
+ sess->txstat.osent += (uint32_t)payload_size;
+ sess->txstat.psent += 1;
+
+ if (!sess->txstat.ts_synced) {
+ sess->txstat.jfs_ref = tmr_jiffies();
+ sess->txstat.ts_ref = ts;
+ sess->txstat.ts_synced = true;
+ }
+
+ lock_rel(sess->lock);
+}
+
+
+void rtcp_sess_rx_rtp(struct rtcp_sess *sess, uint16_t seq, uint32_t ts,
+ uint32_t ssrc, size_t payload_size,
+ const struct sa *peer)
+{
+ struct rtp_member *mbr;
+
+ if (!sess)
+ return;
+
+ mbr = get_member(sess, ssrc);
+ if (!mbr) {
+ DEBUG_NOTICE("could not add member: 0x%08x\n", ssrc);
+ return;
+ }
+
+ if (!mbr->s) {
+ mbr->s = mem_zalloc(sizeof(*mbr->s), NULL);
+ if (!mbr->s) {
+ DEBUG_NOTICE("could not add sender: 0x%08x\n", ssrc);
+ return;
+ }
+
+ /* first packet - init sequence number */
+ source_init_seq(mbr->s, seq);
+ /* probation not used */
+ sa_cpy(&mbr->s->rtp_peer, peer);
+ ++sess->senderc;
+ }
+
+ if (!source_update_seq(mbr->s, seq)) {
+ DEBUG_WARNING("rtp_update_seq() returned 0\n");
+ }
+
+ if (sess->srate_rx) {
+
+ uint64_t ts_arrive;
+
+ /* Convert from wall-clock time to timestamp units */
+ ts_arrive = tmr_jiffies() * sess->srate_rx / 1000;
+
+ source_calc_jitter(mbr->s, ts, (uint32_t)ts_arrive);
+ }
+
+ mbr->s->rtp_rx_bytes += payload_size;
+}
+
+
+/**
+ * Get the RTCP Statistics for a source
+ *
+ * @param rs RTP Socket
+ * @param ssrc Synchronization source
+ * @param stats RTCP Statistics, set on return
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtcp_stats(struct rtp_sock *rs, uint32_t ssrc, struct rtcp_stats *stats)
+{
+ const struct rtcp_sess *sess = rtp_rtcp_sess(rs);
+ struct rtp_member *mbr;
+
+ if (!sess || !stats)
+ return EINVAL;
+
+ mbr = member_find(sess->members, ssrc);
+ if (!mbr)
+ return ENOENT;
+
+ lock_read_get(sess->lock);
+ stats->tx.sent = sess->txstat.psent;
+ lock_rel(sess->lock);
+
+ stats->tx.lost = mbr->cum_lost;
+ stats->tx.jit = mbr->jit;
+
+ stats->rtt = mbr->rtt;
+
+ if (!mbr->s) {
+ memset(&stats->rx, 0, sizeof(stats->rx));
+ return 0;
+ }
+
+ stats->rx.sent = mbr->s->received;
+ stats->rx.lost = source_calc_lost(mbr->s);
+ stats->rx.jit = sess->srate_rx ?
+ 1000000 * (mbr->s->jitter>>4) / sess->srate_rx : 0;
+
+ return 0;
+}
+
+
+static bool debug_handler(struct le *le, void *arg)
+{
+ const struct rtp_member *mbr = le->data;
+ struct re_printf *pf = arg;
+ int err;
+
+ err = re_hprintf(pf, " member 0x%08x: lost=%d Jitter=%.1fms"
+ " RTT=%.1fms\n", mbr->src, mbr->cum_lost,
+ (double)mbr->jit/1000, (double)mbr->rtt/1000);
+ if (mbr->s) {
+ err |= re_hprintf(pf,
+ " IP=%J psent=%u rcvd=%u\n",
+ &mbr->s->rtp_peer, mbr->s->psent,
+ mbr->s->received);
+ }
+
+ return err != 0;
+}
+
+
+/**
+ * RTCP Debug handler, use with fmt %H
+ *
+ * @param pf Print function
+ * @param rs RTP Socket
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int rtcp_debug(struct re_printf *pf, const struct rtp_sock *rs)
+{
+ const struct rtcp_sess *sess = rtp_rtcp_sess(rs);
+ int err = 0;
+
+ if (!sess)
+ return 0;
+
+ err |= re_hprintf(pf, "----- RTCP Session: -----\n");
+ err |= re_hprintf(pf, " cname=%s SSRC=0x%08x/%u rx=%uHz\n",
+ sess->cname,
+ rtp_sess_ssrc(sess->rs), rtp_sess_ssrc(sess->rs),
+ sess->srate_rx);
+
+ hash_apply(sess->members, debug_handler, pf);
+
+ lock_read_get(sess->lock);
+ err |= re_hprintf(pf, " TX: packets=%u, octets=%u\n",
+ sess->txstat.psent, sess->txstat.osent);
+ lock_rel(sess->lock);
+
+ return err;
+}
diff --git a/src/rtp/source.c b/src/rtp/source.c
new file mode 100644
index 0000000..c6a3242
--- /dev/null
+++ b/src/rtp/source.c
@@ -0,0 +1,177 @@
+/**
+ * @file source.c Real-time Transport Control Protocol source
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include "rtcp.h"
+
+
+enum {
+ RTP_SEQ_MOD = 1<<16,
+};
+
+
+void source_init_seq(struct rtp_source *s, uint16_t seq)
+{
+ if (!s)
+ return;
+
+ s->base_seq = seq;
+ s->max_seq = seq;
+ s->bad_seq = RTP_SEQ_MOD + 1; /* so seq == bad_seq is false */
+ s->cycles = 0;
+ s->received = 0;
+ s->received_prior = 0;
+ s->expected_prior = 0;
+ /* other initialization */
+}
+
+
+/*
+ * See RFC 3550 - A.1 RTP Data Header Validity Checks
+ */
+int source_update_seq(struct rtp_source *s, uint16_t seq)
+{
+ uint16_t udelta = seq - s->max_seq;
+ const int MAX_DROPOUT = 3000;
+ const int MAX_MISORDER = 100;
+ const int MIN_SEQUENTIAL = 2;
+
+ /*
+ * Source is not valid until MIN_SEQUENTIAL packets with
+ * sequential sequence numbers have been received.
+ */
+ if (s->probation) {
+
+ /* packet is in sequence */
+ if (seq == s->max_seq + 1) {
+ s->probation--;
+ s->max_seq = seq;
+ if (s->probation == 0) {
+ source_init_seq(s, seq);
+ s->received++;
+ return 1;
+ }
+ }
+ else {
+ s->probation = MIN_SEQUENTIAL - 1;
+ s->max_seq = seq;
+ }
+ return 0;
+ }
+ else if (udelta < MAX_DROPOUT) {
+
+ /* in order, with permissible gap */
+ if (seq < s->max_seq) {
+ /*
+ * Sequence number wrapped - count another 64K cycle.
+ */
+ s->cycles += RTP_SEQ_MOD;
+ }
+ s->max_seq = seq;
+ }
+ else if (udelta <= RTP_SEQ_MOD - MAX_MISORDER) {
+
+ /* the sequence number made a very large jump */
+ if (seq == s->bad_seq) {
+ /*
+ * Two sequential packets -- assume that the other side
+ * restarted without telling us so just re-sync
+ * (i.e., pretend this was the first packet).
+ */
+ source_init_seq(s, seq);
+ }
+ else {
+ s->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1);
+ return 0;
+ }
+ }
+ else {
+ /* duplicate or reordered packet */
+ }
+
+ s->received++;
+ return 1;
+}
+
+
+/* RFC 3550 A.8
+ *
+ * The inputs are:
+ *
+ * rtp_ts: the timestamp from the incoming RTP packet
+ * arrival: the current time in the same units.
+ */
+void source_calc_jitter(struct rtp_source *s, uint32_t rtp_ts,
+ uint32_t arrival)
+{
+ const int transit = arrival - rtp_ts;
+ int d = transit - s->transit;
+
+ if (!s->transit) {
+ s->transit = transit;
+ return;
+ }
+
+ s->transit = transit;
+
+ if (d < 0)
+ d = -d;
+
+ s->jitter += d - ((s->jitter + 8) >> 4);
+}
+
+
+/* A.3 */
+int source_calc_lost(const struct rtp_source *s)
+{
+ int extended_max = s->cycles + s->max_seq;
+ int expected = extended_max - s->base_seq + 1;
+ int lost;
+
+ lost = expected - s->received;
+
+ /* Clamp at 24 bits */
+ if (lost > 0x7fffff)
+ lost = 0x7fffff;
+ else if (lost < -0x7fffff)
+ lost = -0x7fffff;
+
+ return lost;
+}
+
+
+/* A.3 */
+uint8_t source_calc_fraction_lost(struct rtp_source *s)
+{
+ int extended_max = s->cycles + s->max_seq;
+ int expected = extended_max - s->base_seq + 1;
+ int expected_interval = expected - s->expected_prior;
+ int received_interval;
+ int lost_interval;
+ uint8_t fraction;
+
+ s->expected_prior = expected;
+
+ received_interval = s->received - s->received_prior;
+
+ s->received_prior = s->received;
+
+ lost_interval = expected_interval - received_interval;
+
+ if (expected_interval == 0 || lost_interval <= 0)
+ fraction = 0;
+ else
+ fraction = (lost_interval << 8) / expected_interval;
+
+ return fraction;
+}
diff --git a/src/sa/mod.mk b/src/sa/mod.mk
new file mode 100644
index 0000000..78f05e0
--- /dev/null
+++ b/src/sa/mod.mk
@@ -0,0 +1,10 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += sa/ntop.c
+SRCS += sa/printaddr.c
+SRCS += sa/pton.c
+SRCS += sa/sa.c
diff --git a/src/sa/ntop.c b/src/sa/ntop.c
new file mode 100644
index 0000000..30aab58
--- /dev/null
+++ b/src/sa/ntop.c
@@ -0,0 +1,222 @@
+/**
+ * @file ntop.c Network address structure functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#define _BSD_SOURCE 1
+#define _DEFAULT_SOURCE 1
+
+#ifdef HAVE_INET_NTOP
+#ifdef WIN32
+#ifdef _MSC_VER
+#pragma warning (disable: 4090)
+#endif
+#include <windows.h>
+#else
+#define __USE_BSD 1 /**< Use BSD code */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#define __USE_POSIX 1 /**< Use POSIX code */
+#include <netdb.h>
+#endif /* WIN32 */
+#endif
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include "sa.h"
+
+
+#define DEBUG_MODULE "net_ntop"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+#ifndef HAVE_INET_NTOP
+
+
+#define NS_IN6ADDRSZ 16 /**< IPv6 T_AAAA */
+#define NS_INT16SZ 2 /**< #/bytes of data in a u_int16_t */
+
+
+static const char*
+inet_ntop4(const u_char *src, char *dst, size_t size)
+{
+ if (re_snprintf(dst, size, "%u.%u.%u.%u",
+ src[0], src[1], src[2], src[3]) < 0) {
+ errno = ENOSPC;
+ dst[size-1] = 0;
+ return NULL;
+ }
+
+ return dst;
+}
+
+
+#ifdef HAVE_INET6
+/* const char *
+ * inet_ntop6(src, dst, size)
+ * convert IPv6 binary address into presentation (printable) format
+ * author:
+ * Paul Vixie, 1996.
+ */
+
+static const char *
+inet_ntop6(const u_char *src, char *dst, size_t size)
+{
+ /*
+ * Note that int32_t and int16_t need only be "at least" large enough
+ * to contain a value of the specified size. On some systems, like
+ * Crays, there is no such thing as an integer variable with 16 bits.
+ * Keep this in mind if you think this function should have been coded
+ * to use pointer overlays. All the world's not a VAX.
+ */
+ char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp;
+ struct { int base, len; } best, cur;
+ u_int words[NS_IN6ADDRSZ / NS_INT16SZ];
+ int i;
+
+ /*
+ * Preprocess:
+ * Copy the input (bytewise) array into a wordwise array.
+ * Find the longest run of 0x00's in src[] for :: shorthanding.
+ */
+ memset(words, '\0', sizeof words);
+ for (i = 0; i < NS_IN6ADDRSZ; i++)
+ words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3));
+ best.base = -1;
+ best.len = 0;
+ cur.base = -1;
+ cur.len = 0;
+ for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
+ if (words[i] == 0) {
+ if (cur.base == -1)
+ cur.base = i, cur.len = 1;
+ else
+ cur.len++;
+ }
+ else {
+ if (cur.base != -1) {
+ if (best.base == -1 || cur.len > best.len)
+ best = cur;
+ cur.base = -1;
+ }
+ }
+ }
+ if (cur.base != -1) {
+ if (best.base == -1 || cur.len > best.len)
+ best = cur;
+ }
+ if (best.base != -1 && best.len < 2)
+ best.base = -1;
+
+ /*
+ * Format the result.
+ */
+ tp = tmp;
+ for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
+ /* Are we inside the best run of 0x00's? */
+ if (best.base != -1 && i >= best.base &&
+ i < (best.base + best.len)) {
+ if (i == best.base)
+ *tp++ = ':';
+ continue;
+ }
+ /* Are we following an initial run of 0x00s or any real hex?*/
+ if (i != 0)
+ *tp++ = ':';
+ /* Is this address an encapsulated IPv4? */
+ if (i == 6 && best.base == 0 &&
+ (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) {
+ if (!inet_ntop4(src+12, tp, sizeof tmp - (tp - tmp)))
+ return NULL;
+ tp += strlen(tp);
+ break;
+ }
+ tp += sprintf(tp, "%x", words[i]);
+ }
+ /* Was it a trailing run of 0x00's? */
+ if (best.base != -1 && (best.base + best.len) ==
+ (NS_IN6ADDRSZ / NS_INT16SZ))
+ *tp++ = ':';
+ *tp++ = '\0';
+
+ /*
+ * Check for overflow, copy, and we're done.
+ */
+ if ((size_t)(tp - tmp) > size) {
+ errno = ENOSPC;
+ return NULL;
+ }
+ strcpy(dst, tmp);
+
+ return dst;
+}
+#endif
+
+
+/*
+ * Implementation of inet_ntop()
+ */
+const char* inet_ntop(int af, const void *src, char *dst, size_t size);
+const char* inet_ntop(int af, const void *src, char *dst, size_t size)
+{
+ switch (af) {
+
+ case AF_INET:
+ return inet_ntop4(src, dst, size);
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ return inet_ntop6(src, dst, size);
+#endif
+
+ default:
+ DEBUG_WARNING("inet_ntop: unknown address family %d\n", af);
+ return NULL;
+ }
+}
+#endif
+
+
+/*
+ * POSIX 1003.1-2001 marks gethostbyaddr() and gethostbyname() obsolescent.
+ * See getaddrinfo(3), getnameinfo(3), gai_strerror(3).
+ */
+
+
+/**
+ * Convert network address structure to a character string
+ *
+ * @param sa Socket address
+ * @param buf Buffer to return IP address
+ * @param size Size of buffer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_inet_ntop(const struct sa *sa, char *buf, int size)
+{
+ if (!sa || !buf || !size)
+ return EINVAL;
+
+ switch (sa->u.sa.sa_family) {
+
+ case AF_INET:
+ inet_ntop(AF_INET, &sa->u.in.sin_addr, buf, size);
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ inet_ntop(AF_INET6, &sa->u.in6.sin6_addr, buf, size);
+ break;
+#endif
+
+ default:
+ return EAFNOSUPPORT;
+ }
+
+ return 0;
+}
diff --git a/src/sa/printaddr.c b/src/sa/printaddr.c
new file mode 100644
index 0000000..262c336
--- /dev/null
+++ b/src/sa/printaddr.c
@@ -0,0 +1,47 @@
+/**
+ * @file sa/printaddr.c Socket Address printing
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#ifdef HAVE_GETIFADDRS
+#include <sys/types.h>
+#include <sys/socket.h>
+#define __USE_MISC 1 /**< Use MISC code */
+#include <net/if.h>
+#endif
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_sa.h>
+
+
+/**
+ * Print a Socket Address including IPv6 scope identifier
+ *
+ * @param pf Print function
+ * @param sa Socket Address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sa_print_addr(struct re_printf *pf, const struct sa *sa)
+{
+ int err;
+
+ if (!sa)
+ return 0;
+
+ err = re_hprintf(pf, "%j", sa);
+
+#if defined (HAVE_GETIFADDRS) && defined (HAVE_INET6)
+ if (sa_af(sa) == AF_INET6 && sa_is_linklocal(sa)) {
+
+ char ifname[IF_NAMESIZE];
+
+ if (!if_indextoname(sa->u.in6.sin6_scope_id, ifname))
+ return errno;
+
+ err |= re_hprintf(pf, "%%%s", ifname);
+ }
+#endif
+
+ return err;
+}
diff --git a/src/sa/pton.c b/src/sa/pton.c
new file mode 100644
index 0000000..a9b7ae5
--- /dev/null
+++ b/src/sa/pton.c
@@ -0,0 +1,255 @@
+/**
+ * @file pton.c Network address structure functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#define _BSD_SOURCE 1
+#define _DEFAULT_SOURCE 1
+
+#ifdef HAVE_INET_PTON
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#define __USE_POSIX 1 /**< Use POSIX code */
+#include <netdb.h>
+#endif /* WIN32 */
+#endif
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include "sa.h"
+
+
+#define DEBUG_MODULE "net_pton"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+#ifndef HAVE_INET_PTON
+
+
+#define NS_INADDRSZ 4 /**< IPv4 T_A */
+#define NS_IN6ADDRSZ 16 /**< IPv6 T_AAAA */
+#define NS_INT16SZ 2 /**< #/bytes of data in a u_int16_t */
+
+
+/* int
+ * inet_pton4(src, dst)
+ * like inet_aton() but without all the hexadecimal and shorthand.
+ * return:
+ * 1 if `src' is a valid dotted quad, else 0.
+ * notice:
+ * does not touch `dst' unless it's returning 1.
+ * author:
+ * Paul Vixie, 1996.
+ */
+static int
+inet_pton4(const char *src, u_char *dst)
+{
+ static const char digits[] = "0123456789";
+ int saw_digit, octets, ch;
+ u_char tmp[NS_INADDRSZ], *tp;
+
+ saw_digit = 0;
+ octets = 0;
+ *(tp = tmp) = 0;
+ while ((ch = *src++) != '\0') {
+ const char *pch;
+
+ if ((pch = strchr(digits, ch)) != NULL) {
+ u_int newVal = (u_int) (*tp * 10 + (pch - digits));
+
+ if (newVal > 255)
+ return 0;
+ *tp = newVal;
+ if (! saw_digit) {
+ if (++octets > 4)
+ return 0;
+ saw_digit = 1;
+ }
+ }
+ else if (ch == '.' && saw_digit) {
+ if (octets == 4)
+ return 0;
+ *++tp = 0;
+ saw_digit = 0;
+ }
+ else
+ return 0;
+ }
+ if (octets < 4)
+ return 0;
+
+ memcpy(dst, tmp, NS_INADDRSZ);
+ return 1;
+}
+
+
+#ifdef HAVE_INET6
+/* int
+ * inet_pton6(src, dst)
+ * convert presentation level address to network order binary form.
+ * return:
+ * 1 if `src' is a valid [RFC1884 2.2] address, else 0.
+ * notice:
+ * (1) does not touch `dst' unless it's returning 1.
+ * (2) :: in a full address is silently ignored.
+ * credit:
+ * inspired by Mark Andrews.
+ * author:
+ * Paul Vixie, 1996.
+ */
+static int
+inet_pton6(const char *src, u_char *dst)
+{
+ static const char xdigits_l[] = "0123456789abcdef",
+ xdigits_u[] = "0123456789ABCDEF";
+ u_char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp;
+ const char *xdigits, *curtok;
+ int ch, saw_xdigit;
+ u_int val;
+
+ memset((tp = tmp), '\0', NS_IN6ADDRSZ);
+ endp = tp + NS_IN6ADDRSZ;
+ colonp = NULL;
+ /* Leading :: requires some special handling. */
+ if (*src == ':')
+ if (*++src != ':')
+ return 0;
+ curtok = src;
+ saw_xdigit = 0;
+ val = 0;
+ while ((ch = *src++) != '\0') {
+ const char *pch;
+
+ if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL)
+ pch = strchr((xdigits = xdigits_u), ch);
+ if (pch != NULL) {
+ val <<= 4;
+ val |= (u_int)(pch - xdigits);
+ if (val > 0xffff)
+ return 0;
+ saw_xdigit = 1;
+ continue;
+ }
+ if (ch == ':') {
+ curtok = src;
+ if (!saw_xdigit) {
+ if (colonp)
+ return 0;
+ colonp = tp;
+ continue;
+ }
+ if (tp + NS_INT16SZ > endp)
+ return 0;
+ *tp++ = (u_char) (val >> 8) & 0xff;
+ *tp++ = (u_char) val & 0xff;
+ saw_xdigit = 0;
+ val = 0;
+ continue;
+ }
+ if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) &&
+ inet_pton4(curtok, tp) > 0) {
+ tp += NS_INADDRSZ;
+ saw_xdigit = 0;
+ break; /* '\0' was seen by inet_pton4(). */
+ }
+ return 0;
+ }
+ if (saw_xdigit) {
+ if (tp + NS_INT16SZ > endp)
+ return 0;
+ *tp++ = (u_char) (val >> 8) & 0xff;
+ *tp++ = (u_char) val & 0xff;
+ }
+ if (colonp != NULL) {
+ /*
+ * Since some memmove()'s erroneously fail to handle
+ * overlapping regions, we'll do the shift by hand.
+ */
+ const int n = (int)(tp - colonp);
+ int i;
+
+ for (i = 1; i <= n; i++) {
+ endp[- i] = colonp[n - i];
+ colonp[n - i] = 0;
+ }
+ tp = endp;
+ }
+ if (tp != endp)
+ return 0;
+ memcpy(dst, tmp, NS_IN6ADDRSZ);
+
+ return 1;
+}
+#endif
+
+
+/**
+ * Implementation of inet_pton()
+ */
+static int inet_pton(int af, const char *src, void *dst)
+{
+ if (!src || !dst)
+ return 0;
+
+ switch (af) {
+
+ case AF_INET:
+ return inet_pton4(src, (u_char*) dst);
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ return inet_pton6(src, (u_char*) dst);
+#endif
+
+ default:
+ DEBUG_INFO("inet_pton: unknown address family %d\n", af);
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+}
+#endif
+
+
+/**
+ * Convert character string to a network address structure
+ *
+ * @param addr IP address string
+ * @param sa Returned socket address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int net_inet_pton(const char *addr, struct sa *sa)
+{
+ if (!addr)
+ return EINVAL;
+
+ if (inet_pton(AF_INET, addr, &sa->u.in.sin_addr) > 0) {
+ sa->u.in.sin_family = AF_INET;
+ }
+#ifdef HAVE_INET6
+ else if (inet_pton(AF_INET6, addr, &sa->u.in6.sin6_addr) > 0) {
+
+ if (IN6_IS_ADDR_V4MAPPED(&sa->u.in6.sin6_addr)) {
+ const uint8_t *a = &sa->u.in6.sin6_addr.s6_addr[12];
+ sa->u.in.sin_family = AF_INET;
+ memcpy(&sa->u.in.sin_addr.s_addr, a, 4);
+ }
+ else {
+ sa->u.in6.sin6_family = AF_INET6;
+ }
+ }
+#endif
+ else {
+ return EINVAL;
+ }
+
+ return 0;
+}
diff --git a/src/sa/sa.c b/src/sa/sa.c
new file mode 100644
index 0000000..1d293bd
--- /dev/null
+++ b/src/sa/sa.c
@@ -0,0 +1,593 @@
+/**
+ * @file sa.c Socket Address
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#define _BSD_SOURCE 1
+#define _DEFAULT_SOURCE 1
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include "sa.h"
+
+
+#define DEBUG_MODULE "sa"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/**
+ * Initialize a Socket Address
+ *
+ * @param sa Socket Address
+ * @param af Address Family
+ */
+void sa_init(struct sa *sa, int af)
+{
+ if (!sa)
+ return;
+
+ memset(sa, 0, sizeof(*sa));
+ sa->u.sa.sa_family = af;
+ sa->len = sizeof(sa->u);
+}
+
+
+/**
+ * Set a Socket Address from a PL string
+ *
+ * @param sa Socket Address
+ * @param addr IP-address
+ * @param port Port number
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sa_set(struct sa *sa, const struct pl *addr, uint16_t port)
+{
+ char buf[64];
+
+ (void)pl_strcpy(addr, buf, sizeof(buf));
+ return sa_set_str(sa, buf, port);
+}
+
+
+/**
+ * Set a Socket Address from a string
+ *
+ * @param sa Socket Address
+ * @param addr IP-address
+ * @param port Port number
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sa_set_str(struct sa *sa, const char *addr, uint16_t port)
+{
+ int err;
+
+ if (!sa || !addr)
+ return EINVAL;
+
+ err = net_inet_pton(addr, sa);
+ if (err)
+ return err;
+
+ switch (sa->u.sa.sa_family) {
+
+ case AF_INET:
+ sa->u.in.sin_port = htons(port);
+ sa->len = sizeof(struct sockaddr_in);
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ sa->u.in6.sin6_port = htons(port);
+ sa->len = sizeof(struct sockaddr_in6);
+ break;
+#endif
+
+ default:
+ return EAFNOSUPPORT;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Set a Socket Address from an IPv4 address
+ *
+ * @param sa Socket Address
+ * @param addr IPv4 address in host order
+ * @param port Port number
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+void sa_set_in(struct sa *sa, uint32_t addr, uint16_t port)
+{
+ if (!sa)
+ return;
+
+ sa->u.in.sin_family = AF_INET;
+ sa->u.in.sin_addr.s_addr = htonl(addr);
+ sa->u.in.sin_port = htons(port);
+ sa->len = sizeof(struct sockaddr_in);
+}
+
+
+/**
+ * Set a Socket Address from an IPv6 address
+ *
+ * @param sa Socket Address
+ * @param addr IPv6 address
+ * @param port Port number
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+void sa_set_in6(struct sa *sa, const uint8_t *addr, uint16_t port)
+{
+ if (!sa)
+ return;
+
+#ifdef HAVE_INET6
+ sa->u.in6.sin6_family = AF_INET6;
+ memcpy(&sa->u.in6.sin6_addr, addr, 16);
+ sa->u.in6.sin6_port = htons(port);
+ sa->len = sizeof(struct sockaddr_in6);
+#else
+ (void)addr;
+ (void)port;
+#endif
+}
+
+
+/**
+ * Set a Socket Address from a sockaddr
+ *
+ * @param sa Socket Address
+ * @param s Sockaddr
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sa_set_sa(struct sa *sa, const struct sockaddr *s)
+{
+ if (!sa || !s)
+ return EINVAL;
+
+ switch (s->sa_family) {
+
+ case AF_INET:
+ memcpy(&sa->u.in, s, sizeof(struct sockaddr_in));
+ sa->len = sizeof(struct sockaddr_in);
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ memcpy(&sa->u.in6, s, sizeof(struct sockaddr_in6));
+ sa->len = sizeof(struct sockaddr_in6);
+ break;
+#endif
+
+ default:
+ return EAFNOSUPPORT;
+ }
+
+ sa->u.sa.sa_family = s->sa_family;
+
+ return 0;
+}
+
+
+/**
+ * Set the port number on a Socket Address
+ *
+ * @param sa Socket Address
+ * @param port Port number
+ */
+void sa_set_port(struct sa *sa, uint16_t port)
+{
+ if (!sa)
+ return;
+
+ switch (sa->u.sa.sa_family) {
+
+ case AF_INET:
+ sa->u.in.sin_port = htons(port);
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ sa->u.in6.sin6_port = htons(port);
+ break;
+#endif
+
+ default:
+ DEBUG_WARNING("sa_set_port: no af %d (port %u)\n",
+ sa->u.sa.sa_family, port);
+ break;
+ }
+}
+
+
+/**
+ * Set a socket address from a string of type "address:port"
+ * IPv6 addresses must be encapsulated in square brackets.
+ *
+ * @param sa Socket Address
+ * @param str Address and port string
+ * @param len Length of string
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * Example strings:
+ *
+ * <pre>
+ * 1.2.3.4:1234
+ * [::1]:1234
+ * [::]:5060
+ * </pre>
+ */
+int sa_decode(struct sa *sa, const char *str, size_t len)
+{
+ struct pl addr, port, pl;
+ const char *c;
+
+ if (!sa || !str || !len)
+ return EINVAL;
+
+ pl.p = str;
+ pl.l = len;
+
+ if ('[' == str[0] && (c = pl_strchr(&pl, ']'))) {
+ addr.p = str + 1;
+ addr.l = c - str - 1;
+ ++c;
+ }
+ else if (NULL != (c = pl_strchr(&pl, ':'))) {
+ addr.p = str;
+ addr.l = c - str;
+ }
+ else {
+ return EINVAL;
+ }
+
+ if (len < (size_t)(c - str + 2))
+ return EINVAL;
+
+ if (':' != *c)
+ return EINVAL;
+
+ port.p = ++c;
+ port.l = len + str - c;
+
+ return sa_set(sa, &addr, pl_u32(&port));
+}
+
+
+/**
+ * Get the Address Family of a Socket Address
+ *
+ * @param sa Socket Address
+ *
+ * @return Address Family
+ */
+int sa_af(const struct sa *sa)
+{
+ return sa ? sa->u.sa.sa_family : AF_UNSPEC;
+}
+
+
+/**
+ * Get the IPv4-address of a Socket Address
+ *
+ * @param sa Socket Address
+ *
+ * @return IPv4 address in host order
+ */
+uint32_t sa_in(const struct sa *sa)
+{
+ return sa ? ntohl(sa->u.in.sin_addr.s_addr) : 0;
+}
+
+
+/**
+ * Get the IPv6-address of a Socket Address
+ *
+ * @param sa Socket Address
+ * @param addr On return, contains the IPv6-address
+ */
+void sa_in6(const struct sa *sa, uint8_t *addr)
+{
+ if (!sa || !addr)
+ return;
+
+#ifdef HAVE_INET6
+ memcpy(addr, &sa->u.in6.sin6_addr, 16);
+#endif
+}
+
+
+/**
+ * Convert a Socket Address to Presentation format
+ *
+ * @param sa Socket Address
+ * @param buf Buffer to store presentation format
+ * @param size Buffer size
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sa_ntop(const struct sa *sa, char *buf, int size)
+{
+ return net_inet_ntop(sa, buf, size);
+}
+
+
+/**
+ * Get the port number from a Socket Address
+ *
+ * @param sa Socket Address
+ *
+ * @return Port number in host order
+ */
+uint16_t sa_port(const struct sa *sa)
+{
+ if (!sa)
+ return 0;
+
+ switch (sa->u.sa.sa_family) {
+
+ case AF_INET:
+ return ntohs(sa->u.in.sin_port);
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ return ntohs(sa->u.in6.sin6_port);
+#endif
+
+ default:
+ return 0;
+ }
+}
+
+
+/**
+ * Check if a Socket Address is set
+ *
+ * @param sa Socket Address
+ * @param flag Flags specifying which fields to check
+ *
+ * @return true if set, false if not set
+ */
+bool sa_isset(const struct sa *sa, int flag)
+{
+ if (!sa)
+ return false;
+
+ switch (sa->u.sa.sa_family) {
+
+ case AF_INET:
+ if (flag & SA_ADDR)
+ if (INADDR_ANY == sa->u.in.sin_addr.s_addr)
+ return false;
+ if (flag & SA_PORT)
+ if (0 == sa->u.in.sin_port)
+ return false;
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ if (flag & SA_ADDR)
+ if (IN6_IS_ADDR_UNSPECIFIED(&sa->u.in6.sin6_addr))
+ return false;
+ if (flag & SA_PORT)
+ if (0 == sa->u.in6.sin6_port)
+ return false;
+ break;
+#endif
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+
+/**
+ * Calculate the hash value of a Socket Address
+ *
+ * @param sa Socket Address
+ * @param flag Flags specifying which fields to use
+ *
+ * @return Hash value
+ */
+uint32_t sa_hash(const struct sa *sa, int flag)
+{
+ uint32_t v = 0;
+
+ if (!sa)
+ return 0;
+
+ switch (sa->u.sa.sa_family) {
+
+ case AF_INET:
+ if (flag & SA_ADDR)
+ v += ntohl(sa->u.in.sin_addr.s_addr);
+ if (flag & SA_PORT)
+ v += ntohs(sa->u.in.sin_port);
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ if (flag & SA_ADDR) {
+ uint32_t *a = (uint32_t *)&sa->u.in6.sin6_addr;
+ v += a[0] ^ a[1] ^ a[2] ^ a[3];
+ }
+ if (flag & SA_PORT)
+ v += ntohs(sa->u.in6.sin6_port);
+ break;
+#endif
+
+ default:
+ DEBUG_WARNING("sa_hash: unknown af %d\n", sa->u.sa.sa_family);
+ return 0;
+ }
+
+ return v;
+}
+
+
+/**
+ * Copy a Socket Address
+ *
+ * @param dst Socket Address to be written
+ * @param src Socket Address to be copied
+ */
+void sa_cpy(struct sa *dst, const struct sa *src)
+{
+ if (!dst || !src)
+ return;
+
+ memcpy(dst, src, sizeof(*dst));
+}
+
+
+/**
+ * Compare two Socket Address objects
+ *
+ * @param l Socket Address number one
+ * @param r Socket Address number two
+ * @param flag Flags specifying which fields to use
+ *
+ * @return true if match, false if no match
+ */
+bool sa_cmp(const struct sa *l, const struct sa *r, int flag)
+{
+ if (!l || !r)
+ return false;
+
+ if (l == r)
+ return true;
+
+ if (l->u.sa.sa_family != r->u.sa.sa_family)
+ return false;
+
+ switch (l->u.sa.sa_family) {
+
+ case AF_INET:
+ if (flag & SA_ADDR)
+ if (l->u.in.sin_addr.s_addr != r->u.in.sin_addr.s_addr)
+ return false;
+ if (flag & SA_PORT)
+ if (l->u.in.sin_port != r->u.in.sin_port)
+ return false;
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ if (flag & SA_ADDR)
+ if (memcmp(&l->u.in6.sin6_addr,
+ &r->u.in6.sin6_addr, 16))
+ return false;
+ if (flag & SA_PORT)
+ if (l->u.in6.sin6_port != r->u.in6.sin6_port)
+ return false;
+ break;
+#endif
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+
+/** IPv4 Link-local test */
+#define IN_IS_ADDR_LINKLOCAL(a) \
+ (((a) & htonl(0xffff0000)) == htonl (0xa9fe0000))
+
+
+/**
+ * Check if socket address is a link-local address
+ *
+ * @param sa Socket address
+ *
+ * @return true if link-local address, otherwise false
+ */
+bool sa_is_linklocal(const struct sa *sa)
+{
+ if (!sa)
+ return false;
+
+ switch (sa_af(sa)) {
+
+ case AF_INET:
+ return IN_IS_ADDR_LINKLOCAL(sa->u.in.sin_addr.s_addr);
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ return IN6_IS_ADDR_LINKLOCAL(&sa->u.in6.sin6_addr);
+#endif
+
+ default:
+ return false;
+ }
+}
+
+
+/**
+ * Check if socket address is a loopback address
+ *
+ * @param sa Socket address
+ *
+ * @return true if loopback address, otherwise false
+ */
+bool sa_is_loopback(const struct sa *sa)
+{
+ if (!sa)
+ return false;
+
+ switch (sa_af(sa)) {
+
+ case AF_INET:
+ return INADDR_LOOPBACK == ntohl(sa->u.in.sin_addr.s_addr);
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ return IN6_IS_ADDR_LOOPBACK(&sa->u.in6.sin6_addr);
+#endif
+
+ default:
+ return false;
+ }
+}
+
+
+/**
+ * Check if socket address is any/unspecified address
+ *
+ * @param sa Socket address
+ *
+ * @return true if any address, otherwise false
+ */
+bool sa_is_any(const struct sa *sa)
+{
+ if (!sa)
+ return false;
+
+ switch (sa_af(sa)) {
+
+ case AF_INET:
+ return INADDR_ANY == ntohl(sa->u.in.sin_addr.s_addr);
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ return IN6_IS_ADDR_UNSPECIFIED(&sa->u.in6.sin6_addr);
+#endif
+
+ default:
+ return false;
+ }
+}
diff --git a/src/sa/sa.h b/src/sa/sa.h
new file mode 100644
index 0000000..fdc2216
--- /dev/null
+++ b/src/sa/sa.h
@@ -0,0 +1,10 @@
+/**
+ * @file sa.h Internal interface to Socket Address
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/* Net ntop/pton */
+int net_inet_ntop(const struct sa *sa, char *buf, int size);
+int net_inet_pton(const char *addr, struct sa *sa);
diff --git a/src/sdp/attr.c b/src/sdp/attr.c
new file mode 100644
index 0000000..00e6e24
--- /dev/null
+++ b/src/sdp/attr.c
@@ -0,0 +1,140 @@
+/**
+ * @file sdp/attr.c SDP Attributes
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+#include "sdp.h"
+
+
+struct sdp_attr {
+ struct le le;
+ char *name;
+ char *val;
+};
+
+
+static void destructor(void *arg)
+{
+ struct sdp_attr *attr = arg;
+
+ list_unlink(&attr->le);
+ mem_deref(attr->name);
+ mem_deref(attr->val);
+}
+
+
+int sdp_attr_add(struct list *lst, struct pl *name, struct pl *val)
+{
+ struct sdp_attr *attr;
+ int err;
+
+ attr = mem_zalloc(sizeof(*attr), destructor);
+ if (!attr)
+ return ENOMEM;
+
+ list_append(lst, &attr->le, attr);
+
+ err = pl_strdup(&attr->name, name);
+
+ if (pl_isset(val))
+ err |= pl_strdup(&attr->val, val);
+
+ if (err)
+ mem_deref(attr);
+
+ return err;
+}
+
+
+int sdp_attr_addv(struct list *lst, const char *name, const char *val,
+ va_list ap)
+{
+ struct sdp_attr *attr;
+ int err;
+
+ attr = mem_zalloc(sizeof(*attr), destructor);
+ if (!attr)
+ return ENOMEM;
+
+ list_append(lst, &attr->le, attr);
+
+ err = str_dup(&attr->name, name);
+
+ if (str_isset(val))
+ err |= re_vsdprintf(&attr->val, val, ap);
+
+ if (err)
+ mem_deref(attr);
+
+ return err;
+}
+
+
+void sdp_attr_del(const struct list *lst, const char *name)
+{
+ struct le *le = list_head(lst);
+
+ while (le) {
+
+ struct sdp_attr *attr = le->data;
+
+ le = le->next;
+
+ if (0 == str_casecmp(name, attr->name))
+ mem_deref(attr);
+ }
+}
+
+
+const char *sdp_attr_apply(const struct list *lst, const char *name,
+ sdp_attr_h *attrh, void *arg)
+{
+ struct le *le = list_head(lst);
+
+ while (le) {
+
+ const struct sdp_attr *attr = le->data;
+
+ le = le->next;
+
+ if (name && (!attr->name || strcmp(name, attr->name)))
+ continue;
+
+ if (!attrh || attrh(attr->name, attr->val?attr->val : "", arg))
+ return attr->val ? attr->val : "";
+ }
+
+ return NULL;
+}
+
+
+int sdp_attr_print(struct re_printf *pf, const struct sdp_attr *attr)
+{
+ if (!attr)
+ return 0;
+
+ if (attr->val)
+ return re_hprintf(pf, "a=%s:%s\r\n", attr->name, attr->val);
+ else
+ return re_hprintf(pf, "a=%s\r\n", attr->name);
+}
+
+
+int sdp_attr_debug(struct re_printf *pf, const struct sdp_attr *attr)
+{
+ if (!attr)
+ return 0;
+
+ if (attr->val)
+ return re_hprintf(pf, "%s='%s'", attr->name, attr->val);
+ else
+ return re_hprintf(pf, "%s", attr->name);
+}
diff --git a/src/sdp/format.c b/src/sdp/format.c
new file mode 100644
index 0000000..9cc9dd9
--- /dev/null
+++ b/src/sdp/format.c
@@ -0,0 +1,266 @@
+/**
+ * @file sdp/format.c SDP format
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+#include "sdp.h"
+
+
+static void destructor(void *arg)
+{
+ struct sdp_format *fmt = arg;
+
+ list_unlink(&fmt->le);
+
+ if (fmt->ref)
+ mem_deref(fmt->data);
+
+ mem_deref(fmt->id);
+ mem_deref(fmt->params);
+ mem_deref(fmt->rparams);
+ mem_deref(fmt->name);
+}
+
+
+/**
+ * Add an SDP Format to an SDP Media line
+ *
+ * @param fmtp Pointer to allocated SDP Format
+ * @param m SDP Media line
+ * @param prepend True to prepend, False to append
+ * @param id Format identifier
+ * @param name Format name
+ * @param srate Sampling rate
+ * @param ch Number of channels
+ * @param ench Optional format encode handler
+ * @param cmph Optional format comparison handler
+ * @param data Opaque data for handler
+ * @param ref True to mem_ref() data
+ * @param params Formatted parameters
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_format_add(struct sdp_format **fmtp, struct sdp_media *m,
+ bool prepend, const char *id, const char *name,
+ uint32_t srate, uint8_t ch, sdp_fmtp_enc_h *ench,
+ sdp_fmtp_cmp_h *cmph, void *data, bool ref,
+ const char *params, ...)
+{
+ struct sdp_format *fmt;
+ int err;
+
+ if (!m)
+ return EINVAL;
+
+ if (!id && (m->dynpt > RTP_DYNPT_END))
+ return ERANGE;
+
+ fmt = mem_zalloc(sizeof(*fmt), destructor);
+ if (!fmt)
+ return ENOMEM;
+
+ if (prepend)
+ list_prepend(&m->lfmtl, &fmt->le, fmt);
+ else
+ list_append(&m->lfmtl, &fmt->le, fmt);
+
+ if (id)
+ err = str_dup(&fmt->id, id);
+ else
+ err = re_sdprintf(&fmt->id, "%i", m->dynpt++);
+ if (err)
+ goto out;
+
+ if (name) {
+ err = str_dup(&fmt->name, name);
+ if (err)
+ goto out;
+ }
+
+ if (params) {
+ va_list ap;
+
+ va_start(ap, params);
+ err = re_vsdprintf(&fmt->params, params, ap);
+ va_end(ap);
+
+ if (err)
+ goto out;
+ }
+
+ fmt->pt = atoi(fmt->id);
+ fmt->srate = srate;
+ fmt->ch = ch;
+ fmt->ench = ench;
+ fmt->cmph = cmph;
+ fmt->data = ref ? mem_ref(data) : data;
+ fmt->ref = ref;
+ fmt->sup = true;
+
+ out:
+ if (err)
+ mem_deref(fmt);
+ else if (fmtp)
+ *fmtp = fmt;
+
+ return err;
+}
+
+
+int sdp_format_radd(struct sdp_media *m, const struct pl *id)
+{
+ struct sdp_format *fmt;
+ int err;
+
+ if (!m || !id)
+ return EINVAL;
+
+ fmt = mem_zalloc(sizeof(*fmt), destructor);
+ if (!fmt)
+ return ENOMEM;
+
+ list_append(&m->rfmtl, &fmt->le, fmt);
+
+ err = pl_strdup(&fmt->id, id);
+ if (err)
+ goto out;
+
+ fmt->pt = atoi(fmt->id);
+
+ out:
+ if (err)
+ mem_deref(fmt);
+
+ return err;
+}
+
+
+struct sdp_format *sdp_format_find(const struct list *lst, const struct pl *id)
+{
+ struct le *le;
+
+ if (!lst || !id)
+ return NULL;
+
+ for (le=lst->head; le; le=le->next) {
+
+ struct sdp_format *fmt = le->data;
+
+ if (pl_strcmp(id, fmt->id))
+ continue;
+
+ return fmt;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Set the parameters of an SDP format
+ *
+ * @param fmt SDP Format
+ * @param params Formatted parameters
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_format_set_params(struct sdp_format *fmt, const char *params, ...)
+{
+ int err = 0;
+
+ if (!fmt)
+ return EINVAL;
+
+ fmt->params = mem_deref(fmt->params);
+
+ if (params) {
+ va_list ap;
+
+ va_start(ap, params);
+ err = re_vsdprintf(&fmt->params, params, ap);
+ va_end(ap);
+ }
+
+ return err;
+}
+
+
+/**
+ * Compare two SDP Formats
+ *
+ * @param fmt1 First SDP format
+ * @param fmt2 Second SDP format
+ *
+ * @return True if matching, False if not
+ */
+bool sdp_format_cmp(const struct sdp_format *fmt1,
+ const struct sdp_format *fmt2)
+{
+ if (!fmt1 || !fmt2)
+ return false;
+
+ if (fmt1->pt < RTP_DYNPT_START && fmt2->pt < RTP_DYNPT_START) {
+
+ if (!fmt1->id || !fmt2->id)
+ return false;
+
+ return strcmp(fmt1->id, fmt2->id) ? false : true;
+ }
+
+ if (str_casecmp(fmt1->name, fmt2->name))
+ return false;
+
+ if (fmt1->srate != fmt2->srate)
+ return false;
+
+ if (fmt1->ch != fmt2->ch)
+ return false;
+
+ if (fmt1->cmph && !fmt1->cmph(fmt1->params, fmt2->params, fmt1->data))
+ return false;
+
+ if (fmt2->cmph && !fmt2->cmph(fmt2->params, fmt1->params, fmt2->data))
+ return false;
+
+ return true;
+}
+
+
+/**
+ * Print SDP Format debug information
+ *
+ * @param pf Print function for output
+ * @param fmt SDP Format
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_format_debug(struct re_printf *pf, const struct sdp_format *fmt)
+{
+ int err;
+
+ if (!fmt)
+ return 0;
+
+ err = re_hprintf(pf, "%3s", fmt->id);
+
+ if (fmt->name)
+ err |= re_hprintf(pf, " %s/%u/%u",
+ fmt->name, fmt->srate, fmt->ch);
+
+ if (fmt->params)
+ err |= re_hprintf(pf, " (%s)", fmt->params);
+
+ if (fmt->sup)
+ err |= re_hprintf(pf, " *");
+
+ return err;
+}
diff --git a/src/sdp/media.c b/src/sdp/media.c
new file mode 100644
index 0000000..07ce3a2
--- /dev/null
+++ b/src/sdp/media.c
@@ -0,0 +1,958 @@
+/**
+ * @file sdp/media.c SDP Media
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+#include "sdp.h"
+
+
+static void destructor(void *arg)
+{
+ struct sdp_media *m = arg;
+ unsigned i;
+
+ list_flush(&m->lfmtl);
+ list_flush(&m->rfmtl);
+ list_flush(&m->rattrl);
+ list_flush(&m->lattrl);
+
+ if (m->le.list) {
+ m->disabled = true;
+ m->ench = NULL;
+ mem_ref(m);
+ return;
+ }
+
+ for (i=0; i<ARRAY_SIZE(m->protov); i++)
+ mem_deref(m->protov[i]);
+
+ list_unlink(&m->le);
+ mem_deref(m->name);
+ mem_deref(m->proto);
+ mem_deref(m->uproto);
+}
+
+
+static int media_alloc(struct sdp_media **mp, struct list *list)
+{
+ struct sdp_media *m;
+ int i;
+
+ m = mem_zalloc(sizeof(*m), destructor);
+ if (!m)
+ return ENOMEM;
+
+ list_append(list, &m->le, m);
+
+ m->ldir = SDP_SENDRECV;
+ m->rdir = SDP_SENDRECV;
+ m->dynpt = RTP_DYNPT_START;
+
+ sa_init(&m->laddr, AF_INET);
+ sa_init(&m->raddr, AF_INET);
+ sa_init(&m->laddr_rtcp, AF_INET);
+ sa_init(&m->raddr_rtcp, AF_INET);
+
+ for (i=0; i<SDP_BANDWIDTH_MAX; i++) {
+ m->lbwv[i] = -1;
+ m->rbwv[i] = -1;
+ }
+
+ *mp = m;
+
+ return 0;
+}
+
+
+/**
+ * Add a media line to an SDP Session
+ *
+ * @param mp Pointer to allocated SDP Media line object
+ * @param sess SDP Session
+ * @param name Media name
+ * @param port Port number
+ * @param proto Transport protocol
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_media_add(struct sdp_media **mp, struct sdp_session *sess,
+ const char *name, uint16_t port, const char *proto)
+{
+ struct sdp_media *m;
+ int err;
+
+ if (!sess || !name || !proto)
+ return EINVAL;
+
+ err = media_alloc(&m, &sess->lmedial);
+ if (err)
+ return err;
+
+ err = str_dup(&m->name, name);
+ err |= str_dup(&m->proto, proto);
+ if (err)
+ goto out;
+
+ sa_set_port(&m->laddr, port);
+
+ out:
+ if (err)
+ mem_deref(m);
+ else if (mp)
+ *mp = m;
+
+ return err;
+}
+
+
+/**
+ * Add a remote SDP media line to an SDP Session
+ *
+ * @param mp Pointer to allocated SDP Media line object
+ * @param sess SDP Session
+ * @param name Media name
+ * @param proto Transport protocol
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_media_radd(struct sdp_media **mp, struct sdp_session *sess,
+ const struct pl *name, const struct pl *proto)
+{
+ struct sdp_media *m;
+ int err;
+
+ if (!mp || !sess || !name || !proto)
+ return EINVAL;
+
+ err = media_alloc(&m, &sess->medial);
+ if (err)
+ return err;
+
+ m->disabled = true;
+
+ err = pl_strdup(&m->name, name);
+ err |= pl_strdup(&m->proto, proto);
+
+ if (err)
+ mem_deref(m);
+ else
+ *mp = m;
+
+ return err;
+}
+
+
+/**
+ * Reset the remote part of an SDP Media line
+ *
+ * @param m SDP Media line
+ */
+void sdp_media_rreset(struct sdp_media *m)
+{
+ int i;
+
+ if (!m)
+ return;
+
+ sa_init(&m->raddr, AF_INET);
+ sa_init(&m->raddr_rtcp, AF_INET);
+
+ list_flush(&m->rfmtl);
+ list_flush(&m->rattrl);
+
+ m->rdir = SDP_SENDRECV;
+
+ for (i=0; i<SDP_BANDWIDTH_MAX; i++)
+ m->rbwv[i] = -1;
+}
+
+
+/**
+ * Compare media line protocols
+ *
+ * @param m SDP Media line
+ * @param proto Transport protocol
+ * @param update Update media protocol if match is found in alternate set
+ *
+ * @return True if matching, False if not
+ */
+bool sdp_media_proto_cmp(struct sdp_media *m, const struct pl *proto,
+ bool update)
+{
+ unsigned i;
+
+ if (!m || !proto)
+ return false;
+
+ if (!pl_strcmp(proto, m->proto))
+ return true;
+
+ for (i=0; i<ARRAY_SIZE(m->protov); i++) {
+
+ if (!m->protov[i] || pl_strcmp(proto, m->protov[i]))
+ continue;
+
+ if (update) {
+ mem_deref(m->proto);
+ m->proto = mem_ref(m->protov[i]);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Find an SDP Media line from name and transport protocol
+ *
+ * @param sess SDP Session
+ * @param name Media name
+ * @param proto Transport protocol
+ * @param update_proto Update media transport protocol
+ *
+ * @return Matching media line if found, NULL if not found
+ */
+struct sdp_media *sdp_media_find(const struct sdp_session *sess,
+ const struct pl *name,
+ const struct pl *proto,
+ bool update_proto)
+{
+ struct le *le;
+
+ if (!sess || !name || !proto)
+ return NULL;
+
+ for (le=sess->lmedial.head; le; le=le->next) {
+
+ struct sdp_media *m = le->data;
+
+ if (pl_strcmp(name, m->name))
+ continue;
+
+ if (!sdp_media_proto_cmp(m, proto, update_proto))
+ continue;
+
+ return m;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Align the locate/remote formats of an SDP Media line
+ *
+ * @param m SDP Media line
+ * @param offer True if SDP Offer, False if SDP Answer
+ */
+void sdp_media_align_formats(struct sdp_media *m, bool offer)
+{
+ struct sdp_format *rfmt, *lfmt;
+ struct le *rle, *lle;
+
+ if (!m || m->disabled || !sa_port(&m->raddr) || m->fmt_ignore)
+ return;
+
+ for (lle=m->lfmtl.head; lle; lle=lle->next) {
+
+ lfmt = lle->data;
+
+ lfmt->rparams = mem_deref(lfmt->rparams);
+ lfmt->sup = false;
+ }
+
+ for (rle=m->rfmtl.head; rle; rle=rle->next) {
+
+ rfmt = rle->data;
+
+ for (lle=m->lfmtl.head; lle; lle=lle->next) {
+
+ lfmt = lle->data;
+
+ if (sdp_format_cmp(lfmt, rfmt))
+ break;
+ }
+
+ if (!lle) {
+ rfmt->sup = false;
+ continue;
+ }
+
+ mem_deref(lfmt->rparams);
+ lfmt->rparams = mem_ref(rfmt->params);
+
+ lfmt->sup = true;
+ rfmt->sup = true;
+
+ if (rfmt->ref)
+ rfmt->data = mem_deref(rfmt->data);
+ else
+ rfmt->data = NULL;
+
+ if (lfmt->ref)
+ rfmt->data = mem_ref(lfmt->data);
+ else
+ rfmt->data = lfmt->data;
+
+ rfmt->ref = lfmt->ref;
+
+ if (offer) {
+ mem_deref(lfmt->id);
+ lfmt->id = mem_ref(rfmt->id);
+ lfmt->pt = atoi(lfmt->id ? lfmt->id : "");
+
+ list_unlink(&lfmt->le);
+ list_append(&m->lfmtl, &lfmt->le, lfmt);
+ }
+ }
+
+ if (offer) {
+
+ for (lle=m->lfmtl.tail; lle; ) {
+
+ lfmt = lle->data;
+
+ lle = lle->prev;
+
+ if (!lfmt->sup) {
+ list_unlink(&lfmt->le);
+ list_append(&m->lfmtl, &lfmt->le, lfmt);
+ }
+ }
+ }
+}
+
+
+/**
+ * Set alternative protocols for an SDP Media line
+ *
+ * @param m SDP Media line
+ * @param protoc Number of alternative protocols
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_media_set_alt_protos(struct sdp_media *m, unsigned protoc, ...)
+{
+ const char *proto;
+ int err = 0;
+ unsigned i;
+ va_list ap;
+
+ if (!m)
+ return EINVAL;
+
+ va_start(ap, protoc);
+
+ for (i=0; i<ARRAY_SIZE(m->protov); i++) {
+
+ m->protov[i] = mem_deref(m->protov[i]);
+
+ if (i >= protoc)
+ continue;
+
+ proto = va_arg(ap, const char *);
+ if (proto)
+ err |= str_dup(&m->protov[i], proto);
+ }
+
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Set SDP Media line encode handler
+ *
+ * @param m SDP Media line
+ * @param ench Encode handler
+ * @param arg Encode handler argument
+ */
+void sdp_media_set_encode_handler(struct sdp_media *m, sdp_media_enc_h *ench,
+ void *arg)
+{
+ if (!m)
+ return;
+
+ m->ench = ench;
+ m->arg = arg;
+}
+
+
+/**
+ * Set an SDP Media line to ignore formats
+ *
+ * @param m SDP Media line
+ * @param fmt_ignore True for ignore formats, otherwise false
+ */
+void sdp_media_set_fmt_ignore(struct sdp_media *m, bool fmt_ignore)
+{
+ if (!m)
+ return;
+
+ m->fmt_ignore = fmt_ignore;
+}
+
+
+/**
+ * Set an SDP Media line to enabled/disabled
+ *
+ * @param m SDP Media line
+ * @param disabled True for disabled, False for enabled
+ */
+void sdp_media_set_disabled(struct sdp_media *m, bool disabled)
+{
+ if (!m)
+ return;
+
+ m->disabled = disabled;
+}
+
+
+/**
+ * Set the local port number of an SDP Media line
+ *
+ * @param m SDP Media line
+ * @param port Port number
+ */
+void sdp_media_set_lport(struct sdp_media *m, uint16_t port)
+{
+ if (!m)
+ return;
+
+ sa_set_port(&m->laddr, port);
+}
+
+
+/**
+ * Set the local network address of an SDP media line
+ *
+ * @param m SDP Media line
+ * @param laddr Local network address
+ */
+void sdp_media_set_laddr(struct sdp_media *m, const struct sa *laddr)
+{
+ if (!m || !laddr)
+ return;
+
+ m->laddr = *laddr;
+}
+
+
+/**
+ * Set a local bandwidth of an SDP Media line
+ *
+ * @param m SDP Media line
+ * @param type Bandwidth type
+ * @param bw Bandwidth value
+ */
+void sdp_media_set_lbandwidth(struct sdp_media *m, enum sdp_bandwidth type,
+ int32_t bw)
+{
+ if (!m || type >= SDP_BANDWIDTH_MAX)
+ return;
+
+ m->lbwv[type] = bw;
+}
+
+
+/**
+ * Set the local RTCP port number of an SDP Media line
+ *
+ * @param m SDP Media line
+ * @param port RTCP Port number
+ */
+void sdp_media_set_lport_rtcp(struct sdp_media *m, uint16_t port)
+{
+ if (!m)
+ return;
+
+ sa_set_port(&m->laddr_rtcp, port);
+}
+
+
+/**
+ * Set the local RTCP network address of an SDP media line
+ *
+ * @param m SDP Media line
+ * @param laddr Local RTCP network address
+ */
+void sdp_media_set_laddr_rtcp(struct sdp_media *m, const struct sa *laddr)
+{
+ if (!m || !laddr)
+ return;
+
+ m->laddr_rtcp = *laddr;
+}
+
+
+/**
+ * Set the local direction flag of an SDP Media line
+ *
+ * @param m SDP Media line
+ * @param dir Media direction flag
+ */
+void sdp_media_set_ldir(struct sdp_media *m, enum sdp_dir dir)
+{
+ if (!m)
+ return;
+
+ m->ldir = dir;
+}
+
+
+/**
+ * Set a local attribute of an SDP Media line
+ *
+ * @param m SDP Media line
+ * @param replace True to replace attribute, False to append
+ * @param name Attribute name
+ * @param value Formatted attribute value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_media_set_lattr(struct sdp_media *m, bool replace,
+ const char *name, const char *value, ...)
+{
+ va_list ap;
+ int err;
+
+ if (!m || !name)
+ return EINVAL;
+
+ if (replace)
+ sdp_attr_del(&m->lattrl, name);
+
+ va_start(ap, value);
+ err = sdp_attr_addv(&m->lattrl, name, value, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Delete a local attribute of an SDP Media line
+ *
+ * @param m SDP Media line
+ * @param name Attribute name
+ */
+void sdp_media_del_lattr(struct sdp_media *m, const char *name)
+{
+ if (!m || !name)
+ return;
+
+ sdp_attr_del(&m->lattrl, name);
+}
+
+
+const char *sdp_media_proto(const struct sdp_media *m)
+{
+ return m ? m->proto : NULL;
+}
+
+
+/**
+ * Get the remote port number of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return Remote port number
+ */
+uint16_t sdp_media_rport(const struct sdp_media *m)
+{
+ return m ? sa_port(&m->raddr) : 0;
+}
+
+
+/**
+ * Get the remote network address of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return Remote network address
+ */
+const struct sa *sdp_media_raddr(const struct sdp_media *m)
+{
+ return m ? &m->raddr : NULL;
+}
+
+
+/**
+ * Get the local network address of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return Local network address
+ */
+const struct sa *sdp_media_laddr(const struct sdp_media *m)
+{
+ return m ? &m->laddr : NULL;
+}
+
+
+/**
+ * Get the remote RTCP network address of an SDP Media line
+ *
+ * @param m SDP Media line
+ * @param raddr On return, contains remote RTCP network address
+ */
+void sdp_media_raddr_rtcp(const struct sdp_media *m, struct sa *raddr)
+{
+ if (!m || !raddr)
+ return;
+
+ if (sa_isset(&m->raddr_rtcp, SA_ALL)) {
+ *raddr = m->raddr_rtcp;
+ }
+ else if (sa_isset(&m->raddr_rtcp, SA_PORT)) {
+ *raddr = m->raddr;
+ sa_set_port(raddr, sa_port(&m->raddr_rtcp));
+ }
+ else {
+ uint16_t port = sa_port(&m->raddr);
+
+ *raddr = m->raddr;
+ sa_set_port(raddr, port ? port + 1 : 0);
+ }
+}
+
+
+/**
+ * Get a remote bandwidth of an SDP Media line
+ *
+ * @param m SDP Media line
+ * @param type Bandwidth type
+ *
+ * @return Remote bandwidth value
+ */
+int32_t sdp_media_rbandwidth(const struct sdp_media *m,
+ enum sdp_bandwidth type)
+{
+ if (!m || type >= SDP_BANDWIDTH_MAX)
+ return 0;
+
+ return m->rbwv[type];
+}
+
+
+/**
+ * Get the local media direction of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return Local media direction
+ */
+enum sdp_dir sdp_media_ldir(const struct sdp_media *m)
+{
+ return m ? m->ldir : SDP_INACTIVE;
+}
+
+
+/**
+ * Get the remote media direction of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return Remote media direction
+ */
+enum sdp_dir sdp_media_rdir(const struct sdp_media *m)
+{
+ return m ? m->rdir : SDP_INACTIVE;
+}
+
+
+/**
+ * Get the combined media direction of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return Combined media direction
+ */
+enum sdp_dir sdp_media_dir(const struct sdp_media *m)
+{
+ return m ? (enum sdp_dir)(m->ldir & m->rdir) : SDP_INACTIVE;
+}
+
+
+/**
+ * Find a local SDP format from a payload type
+ *
+ * @param m SDP Media line
+ * @param pt Payload type
+ *
+ * @return Local SDP format if found, NULL if not found
+ */
+const struct sdp_format *sdp_media_lformat(const struct sdp_media *m, int pt)
+{
+ struct le *le;
+
+ if (!m)
+ return NULL;
+
+ for (le=m->lfmtl.head; le; le=le->next) {
+
+ const struct sdp_format *fmt = le->data;
+
+ if (pt == fmt->pt)
+ return fmt;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find a remote SDP format from a format name
+ *
+ * @param m SDP Media line
+ * @param name Format name
+ *
+ * @return Remote SDP format if found, NULL if not found
+ */
+const struct sdp_format *sdp_media_rformat(const struct sdp_media *m,
+ const char *name)
+{
+ struct le *le;
+
+ if (!m || !sa_port(&m->raddr))
+ return NULL;
+
+ for (le=m->rfmtl.head; le; le=le->next) {
+
+ const struct sdp_format *fmt = le->data;
+
+ if (!fmt->sup)
+ continue;
+
+ if (name && str_casecmp(name, fmt->name))
+ continue;
+
+ return fmt;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Find an SDP Format of an SDP Media line
+ *
+ * @param m SDP Media line
+ * @param local True if local media, False if remote
+ * @param id SDP format id
+ * @param pt Payload type
+ * @param name Format name
+ * @param srate Sampling rate
+ * @param ch Number of channels
+ *
+ * @return SDP Format if found, NULL if not found
+ */
+struct sdp_format *sdp_media_format(const struct sdp_media *m,
+ bool local, const char *id,
+ int pt, const char *name,
+ int32_t srate, int8_t ch)
+{
+ return sdp_media_format_apply(m, local, id, pt, name, srate, ch,
+ NULL, NULL);
+}
+
+
+/**
+ * Apply a function handler to all matching SDP formats
+ *
+ * @param m SDP Media line
+ * @param local True if local media, False if remote
+ * @param id SDP format id
+ * @param pt Payload type
+ * @param name Format name
+ * @param srate Sampling rate
+ * @param ch Number of channels
+ * @param fmth SDP Format handler
+ * @param arg Handler argument
+ *
+ * @return SDP Format if found, NULL if not found
+ */
+struct sdp_format *sdp_media_format_apply(const struct sdp_media *m,
+ bool local, const char *id,
+ int pt, const char *name,
+ int32_t srate, int8_t ch,
+ sdp_format_h *fmth, void *arg)
+{
+ struct le *le;
+
+ if (!m)
+ return NULL;
+
+ le = local ? m->lfmtl.head : m->rfmtl.head;
+
+ while (le) {
+
+ struct sdp_format *fmt = le->data;
+
+ le = le->next;
+
+ if (id && (!fmt->id || strcmp(id, fmt->id)))
+ continue;
+
+ if (pt >= 0 && pt != fmt->pt)
+ continue;
+
+ if (name && str_casecmp(name, fmt->name))
+ continue;
+
+ if (srate >= 0 && (uint32_t)srate != fmt->srate)
+ continue;
+
+ if (ch >= 0 && (uint8_t)ch != fmt->ch)
+ continue;
+
+ if (!fmth || fmth(fmt, arg))
+ return fmt;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get the list of SDP Formats
+ *
+ * @param m SDP Media line
+ * @param local True if local, False if remote
+ *
+ * @return List of SDP Formats
+ */
+const struct list *sdp_media_format_lst(const struct sdp_media *m, bool local)
+{
+ if (!m)
+ return NULL;
+
+ return local ? &m->lfmtl : &m->rfmtl;
+}
+
+
+/**
+ * Get a remote attribute from an SDP Media line
+ *
+ * @param m SDP Media line
+ * @param name Attribute name
+ *
+ * @return Attribute value, NULL if not found
+ */
+const char *sdp_media_rattr(const struct sdp_media *m, const char *name)
+{
+ if (!m || !name)
+ return NULL;
+
+ return sdp_attr_apply(&m->rattrl, name, NULL, NULL);
+}
+
+
+/**
+ * Get a remote attribute from an SDP Media line or the SDP session
+ *
+ * @param m SDP Media line
+ * @param sess SDP Session
+ * @param name Attribute name
+ *
+ * @return Attribute value, NULL if not found
+ */
+const char *sdp_media_session_rattr(const struct sdp_media *m,
+ const struct sdp_session *sess,
+ const char *name)
+{
+ const char *val;
+
+ val = sdp_media_rattr(m, name);
+ if (!val)
+ val = sdp_session_rattr(sess, name);
+
+ return val;
+}
+
+
+/**
+ * Apply a function handler to all matching remote attributes
+ *
+ * @param m SDP Media line
+ * @param name Attribute name
+ * @param attrh Attribute handler
+ * @param arg Handler argument
+ *
+ * @return Attribute value if match
+ */
+const char *sdp_media_rattr_apply(const struct sdp_media *m, const char *name,
+ sdp_attr_h *attrh, void *arg)
+{
+ if (!m)
+ return NULL;
+
+ return sdp_attr_apply(&m->rattrl, name, attrh, arg);
+}
+
+
+/**
+ * Get the name of an SDP Media line
+ *
+ * @param m SDP Media line
+ *
+ * @return SDP Media line name
+ */
+const char *sdp_media_name(const struct sdp_media *m)
+{
+ return m ? m->name : NULL;
+}
+
+
+/**
+ * Print SDP Media line debug information
+ *
+ * @param pf Print function for output
+ * @param m SDP Media line
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_media_debug(struct re_printf *pf, const struct sdp_media *m)
+{
+ struct le *le;
+ int err;
+
+ if (!m)
+ return 0;
+
+ err = re_hprintf(pf, "%s %s\n", m->name, m->proto);
+
+ err |= re_hprintf(pf, " local formats:\n");
+
+ for (le=m->lfmtl.head; le; le=le->next)
+ err |= re_hprintf(pf, " %H\n", sdp_format_debug, le->data);
+
+ err |= re_hprintf(pf, " remote formats:\n");
+
+ for (le=m->rfmtl.head; le; le=le->next)
+ err |= re_hprintf(pf, " %H\n", sdp_format_debug, le->data);
+
+ err |= re_hprintf(pf, " local attributes:\n");
+
+ for (le=m->lattrl.head; le; le=le->next)
+ err |= re_hprintf(pf, " %H\n", sdp_attr_debug, le->data);
+
+ err |= re_hprintf(pf, " remote attributes:\n");
+
+ for (le=m->rattrl.head; le; le=le->next)
+ err |= re_hprintf(pf, " %H\n", sdp_attr_debug, le->data);
+
+ return err;
+}
diff --git a/src/sdp/mod.mk b/src/sdp/mod.mk
new file mode 100644
index 0000000..6c50025
--- /dev/null
+++ b/src/sdp/mod.mk
@@ -0,0 +1,13 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += sdp/attr.c
+SRCS += sdp/format.c
+SRCS += sdp/media.c
+SRCS += sdp/msg.c
+SRCS += sdp/session.c
+SRCS += sdp/str.c
+SRCS += sdp/util.c
diff --git a/src/sdp/msg.c b/src/sdp/msg.c
new file mode 100644
index 0000000..c1a8bbc
--- /dev/null
+++ b/src/sdp/msg.c
@@ -0,0 +1,531 @@
+/**
+ * @file sdp/msg.c SDP Message processing
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+#include "sdp.h"
+
+
+static int attr_decode_fmtp(struct sdp_media *m, const struct pl *pl)
+{
+ struct sdp_format *fmt;
+ struct pl id, params;
+
+ if (!m)
+ return 0;
+
+ if (re_regex(pl->p, pl->l, "[^ ]+ [^]*", &id, ¶ms))
+ return EBADMSG;
+
+ fmt = sdp_format_find(&m->rfmtl, &id);
+ if (!fmt)
+ return 0;
+
+ fmt->params = mem_deref(fmt->params);
+
+ return pl_strdup(&fmt->params, ¶ms);
+}
+
+
+static int attr_decode_rtcp(struct sdp_media *m, const struct pl *pl)
+{
+ struct pl port, addr;
+ int err = 0;
+
+ if (!m)
+ return 0;
+
+ if (!re_regex(pl->p, pl->l, "[0-9]+ IN IP[46]1 [^ ]+",
+ &port, NULL, &addr)) {
+ (void)sa_set(&m->raddr_rtcp, &addr, pl_u32(&port));
+ }
+ else if (!re_regex(pl->p, pl->l, "[0-9]+", &port)) {
+ sa_set_port(&m->raddr_rtcp, pl_u32(&port));
+ }
+ else
+ err = EBADMSG;
+
+ return err;
+}
+
+
+static int attr_decode_rtpmap(struct sdp_media *m, const struct pl *pl)
+{
+ struct pl id, name, srate, ch;
+ struct sdp_format *fmt;
+ int err;
+
+ if (!m)
+ return 0;
+
+ if (re_regex(pl->p, pl->l, "[^ ]+ [^/]+/[0-9]+[/]*[^]*",
+ &id, &name, &srate, NULL, &ch))
+ return EBADMSG;
+
+ fmt = sdp_format_find(&m->rfmtl, &id);
+ if (!fmt)
+ return 0;
+
+ fmt->name = mem_deref(fmt->name);
+
+ err = pl_strdup(&fmt->name, &name);
+ if (err)
+ return err;
+
+ fmt->srate = pl_u32(&srate);
+ fmt->ch = ch.l ? pl_u32(&ch) : 1;
+
+ return 0;
+}
+
+
+static int attr_decode(struct sdp_session *sess, struct sdp_media *m,
+ enum sdp_dir *dir, const struct pl *pl)
+{
+ struct pl name, val;
+ int err = 0;
+
+ if (re_regex(pl->p, pl->l, "[^:]+:[^]+", &name, &val)) {
+ name = *pl;
+ val = pl_null;
+ }
+
+ if (!pl_strcmp(&name, "fmtp"))
+ err = attr_decode_fmtp(m, &val);
+
+ else if (!pl_strcmp(&name, "inactive"))
+ *dir = SDP_INACTIVE;
+
+ else if (!pl_strcmp(&name, "recvonly"))
+ *dir = SDP_SENDONLY;
+
+ else if (!pl_strcmp(&name, "rtcp"))
+ err = attr_decode_rtcp(m, &val);
+
+ else if (!pl_strcmp(&name, "rtpmap"))
+ err = attr_decode_rtpmap(m, &val);
+
+ else if (!pl_strcmp(&name, "sendonly"))
+ *dir = SDP_RECVONLY;
+
+ else if (!pl_strcmp(&name, "sendrecv"))
+ *dir = SDP_SENDRECV;
+
+ else
+ err = sdp_attr_add(m ? &m->rattrl : &sess->rattrl,
+ &name, &val);
+
+ return err;
+}
+
+
+static int bandwidth_decode(int32_t *bwv, const struct pl *pl)
+{
+ struct pl type, bw;
+
+ if (re_regex(pl->p, pl->l, "[^:]+:[0-9]+", &type, &bw))
+ return EBADMSG;
+
+ if (!pl_strcmp(&type, "CT"))
+ bwv[SDP_BANDWIDTH_CT] = pl_u32(&bw);
+
+ else if (!pl_strcmp(&type, "AS"))
+ bwv[SDP_BANDWIDTH_AS] = pl_u32(&bw);
+
+ else if (!pl_strcmp(&type, "RS"))
+ bwv[SDP_BANDWIDTH_RS] = pl_u32(&bw);
+
+ else if (!pl_strcmp(&type, "RR"))
+ bwv[SDP_BANDWIDTH_RR] = pl_u32(&bw);
+
+ else if (!pl_strcmp(&type, "TIAS"))
+ bwv[SDP_BANDWIDTH_TIAS] = pl_u32(&bw);
+
+ return 0;
+}
+
+
+static int conn_decode(struct sa *sa, const struct pl *pl)
+{
+ struct pl v;
+
+ if (re_regex(pl->p, pl->l, "IN IP[46]1 [^ ]+", NULL, &v))
+ return EBADMSG;
+
+ (void)sa_set(sa, &v, sa_port(sa));
+
+ return 0;
+}
+
+
+static int media_decode(struct sdp_media **mp, struct sdp_session *sess,
+ bool offer, const struct pl *pl)
+{
+ struct pl name, port, proto, fmtv, fmt;
+ struct sdp_media *m;
+ int err;
+
+ if (re_regex(pl->p, pl->l, "[a-z]+ [^ ]+ [^ ]+[^]*",
+ &name, &port, &proto, &fmtv))
+ return EBADMSG;
+
+ m = list_ledata(*mp ? (*mp)->le.next : sess->medial.head);
+ if (!m) {
+ if (!offer)
+ return EPROTO;
+
+ m = sdp_media_find(sess, &name, &proto, true);
+ if (!m) {
+ err = sdp_media_radd(&m, sess, &name, &proto);
+ if (err)
+ return err;
+ }
+ else {
+ list_unlink(&m->le);
+ list_append(&sess->medial, &m->le, m);
+ }
+
+ m->uproto = mem_deref(m->uproto);
+ }
+ else {
+ if (pl_strcmp(&name, m->name))
+ return offer ? ENOTSUP : EPROTO;
+
+ m->uproto = mem_deref(m->uproto);
+
+ if (!sdp_media_proto_cmp(m, &proto, offer)) {
+
+ err = pl_strdup(&m->uproto, &proto);
+ if (err)
+ return err;
+ }
+ }
+
+ while (!re_regex(fmtv.p, fmtv.l, " [^ ]+", &fmt)) {
+
+ pl_advance(&fmtv, fmt.p + fmt.l - fmtv.p);
+
+ err = sdp_format_radd(m, &fmt);
+ if (err)
+ return err;
+ }
+
+ m->raddr = sess->raddr;
+ sa_set_port(&m->raddr, m->uproto ? 0 : pl_u32(&port));
+
+ m->rdir = sess->rdir;
+
+ *mp = m;
+
+ return 0;
+}
+
+
+static int version_decode(const struct pl *pl)
+{
+ return pl_strcmp(pl, "0") ? ENOSYS : 0;
+}
+
+
+/**
+ * Decode an SDP message into an SDP Session
+ *
+ * @param sess SDP Session
+ * @param mb Memory buffer containing SDP message
+ * @param offer True if SDP offer, False if SDP answer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_decode(struct sdp_session *sess, struct mbuf *mb, bool offer)
+{
+ struct sdp_media *m;
+ struct pl pl, val;
+ struct le *le;
+ char type = 0;
+ int err = 0;
+
+ if (!sess || !mb)
+ return EINVAL;
+
+ sdp_session_rreset(sess);
+
+ for (le=sess->medial.head; le; le=le->next) {
+
+ m = le->data;
+
+ sdp_media_rreset(m);
+ }
+
+ pl.p = (const char *)mbuf_buf(mb);
+ pl.l = mbuf_get_left(mb);
+
+ m = NULL;
+
+ for (;pl.l && !err; pl.p++, pl.l--) {
+
+ switch (*pl.p) {
+
+ case '\r':
+ case '\n':
+ if (!type)
+ break;
+
+ switch (type) {
+
+ case 'a':
+ err = attr_decode(sess, m,
+ m ? &m->rdir : &sess->rdir,
+ &val);
+ break;
+
+ case 'b':
+ err = bandwidth_decode(m? m->rbwv : sess->rbwv,
+ &val);
+ break;
+
+ case 'c':
+ err = conn_decode(m ? &m->raddr : &sess->raddr,
+ &val);
+ break;
+
+ case 'm':
+ err = media_decode(&m, sess, offer, &val);
+ break;
+
+ case 'v':
+ err = version_decode(&val);
+ break;
+ }
+
+#if 0
+ if (err)
+ re_printf("** %c='%r': %m\n", type, &val, err);
+#endif
+
+ type = 0;
+ break;
+
+ default:
+ if (type) {
+ val.l++;
+ break;
+ }
+
+ if (pl.l < 2 || *(pl.p + 1) != '=') {
+ err = EBADMSG;
+ break;
+ }
+
+ type = *pl.p;
+ val.p = pl.p + 2;
+ val.l = 0;
+
+ pl.p += 1;
+ pl.l -= 1;
+ break;
+ }
+ }
+
+ if (err)
+ return err;
+
+ if (type)
+ return EBADMSG;
+
+ for (le=sess->medial.head; le; le=le->next)
+ sdp_media_align_formats(le->data, offer);
+
+ return 0;
+}
+
+
+static int media_encode(const struct sdp_media *m, struct mbuf *mb, bool offer)
+{
+ enum sdp_bandwidth i;
+ const char *proto;
+ int err, supc = 0;
+ bool disabled;
+ struct le *le;
+ uint16_t port;
+
+ for (le=m->lfmtl.head; le; le=le->next) {
+
+ const struct sdp_format *fmt = le->data;
+
+ if (fmt->sup)
+ ++supc;
+ }
+
+ if (m->uproto && !offer) {
+ disabled = true;
+ port = 0;
+ proto = m->uproto;
+ }
+ else if (m->disabled || supc == 0 || (!offer && !sa_port(&m->raddr))) {
+ disabled = true;
+ port = 0;
+ proto = m->proto;
+ }
+ else {
+ disabled = false;
+ port = sa_port(&m->laddr);
+ proto = m->proto;
+ }
+
+ err = mbuf_printf(mb, "m=%s %u %s", m->name, port, proto);
+
+ if (disabled) {
+ err |= mbuf_write_str(mb, " 0\r\n");
+ return err;
+ }
+
+ for (le=m->lfmtl.head; le; le=le->next) {
+
+ const struct sdp_format *fmt = le->data;
+
+ if (!fmt->sup)
+ continue;
+
+ err |= mbuf_printf(mb, " %s", fmt->id);
+ }
+
+ err |= mbuf_write_str(mb, "\r\n");
+
+ if (sa_isset(&m->laddr, SA_ADDR)) {
+ const int ipver = sa_af(&m->laddr) == AF_INET ? 4 : 6;
+ err |= mbuf_printf(mb, "c=IN IP%d %j\r\n", ipver, &m->laddr);
+ }
+
+ for (i=SDP_BANDWIDTH_MIN; i<SDP_BANDWIDTH_MAX; i++) {
+
+ if (m->lbwv[i] < 0)
+ continue;
+
+ err |= mbuf_printf(mb, "b=%s:%i\r\n",
+ sdp_bandwidth_name(i), m->lbwv[i]);
+ }
+
+ for (le=m->lfmtl.head; le; le=le->next) {
+
+ const struct sdp_format *fmt = le->data;
+
+ if (!fmt->sup || !str_isset(fmt->name))
+ continue;
+
+ err |= mbuf_printf(mb, "a=rtpmap:%s %s/%u",
+ fmt->id, fmt->name, fmt->srate);
+
+ if (fmt->ch > 1)
+ err |= mbuf_printf(mb, "/%u", fmt->ch);
+
+ err |= mbuf_printf(mb, "\r\n");
+
+ if (str_isset(fmt->params))
+ err |= mbuf_printf(mb, "a=fmtp:%s %s\r\n",
+ fmt->id, fmt->params);
+ if (fmt->ench)
+ err |= fmt->ench(mb, fmt, offer, fmt->data);
+ }
+
+ if (sa_isset(&m->laddr_rtcp, SA_ALL))
+ err |= mbuf_printf(mb, "a=rtcp:%u IN IP%d %j\r\n",
+ sa_port(&m->laddr_rtcp),
+ (AF_INET == sa_af(&m->laddr_rtcp)) ? 4 : 6,
+ &m->laddr_rtcp);
+ else if (sa_isset(&m->laddr_rtcp, SA_PORT))
+ err |= mbuf_printf(mb, "a=rtcp:%u\r\n",
+ sa_port(&m->laddr_rtcp));
+
+ err |= mbuf_printf(mb, "a=%s\r\n",
+ sdp_dir_name(offer ? m->ldir : m->ldir & m->rdir));
+
+ for (le = m->lattrl.head; le; le = le->next)
+ err |= mbuf_printf(mb, "%H", sdp_attr_print, le->data);
+
+ if (m->ench)
+ err |= m->ench(mb, offer, m->arg);
+
+ return err;
+}
+
+
+/**
+ * Encode an SDP Session into a memory buffer
+ *
+ * @param mbp Pointer to allocated memory buffer
+ * @param sess SDP Session
+ * @param offer True if SDP Offer, False if SDP Answer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_encode(struct mbuf **mbp, struct sdp_session *sess, bool offer)
+{
+ const int ipver = sa_af(&sess->laddr) == AF_INET ? 4 : 6;
+ enum sdp_bandwidth i;
+ struct mbuf *mb;
+ struct le *le;
+ int err;
+
+ if (!mbp || !sess)
+ return EINVAL;
+
+ mb = mbuf_alloc(512);
+ if (!mb)
+ return ENOMEM;
+
+ err = mbuf_printf(mb, "v=%u\r\n", SDP_VERSION);
+ err |= mbuf_printf(mb, "o=- %u %u IN IP%d %j\r\n",
+ sess->id, sess->ver++, ipver, &sess->laddr);
+ err |= mbuf_write_str(mb, "s=-\r\n");
+ err |= mbuf_printf(mb, "c=IN IP%d %j\r\n", ipver, &sess->laddr);
+
+ for (i=SDP_BANDWIDTH_MIN; i<SDP_BANDWIDTH_MAX; i++) {
+
+ if (sess->lbwv[i] < 0)
+ continue;
+
+ err |= mbuf_printf(mb, "b=%s:%i\r\n",
+ sdp_bandwidth_name(i), sess->lbwv[i]);
+ }
+
+ err |= mbuf_write_str(mb, "t=0 0\r\n");
+
+ for (le = sess->lattrl.head; le; le = le->next)
+ err |= mbuf_printf(mb, "%H", sdp_attr_print, le->data);
+
+ for (le=sess->lmedial.head; offer && le;) {
+
+ struct sdp_media *m = le->data;
+
+ le = le->next;
+
+ if (m->disabled)
+ continue;
+
+ list_unlink(&m->le);
+ list_append(&sess->medial, &m->le, m);
+ }
+
+ for (le=sess->medial.head; le; le=le->next) {
+
+ struct sdp_media *m = le->data;
+
+ err |= media_encode(m, mb, offer);
+ }
+
+ mb->pos = 0;
+
+ if (err)
+ mem_deref(mb);
+ else
+ *mbp = mb;
+
+ return err;
+}
diff --git a/src/sdp/sdp.h b/src/sdp/sdp.h
new file mode 100644
index 0000000..f0588d1
--- /dev/null
+++ b/src/sdp/sdp.h
@@ -0,0 +1,87 @@
+/**
+ * @file sdp.h Internal SDP interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+enum {
+ RTP_DYNPT_START = 96,
+ RTP_DYNPT_END = 127,
+};
+
+
+struct sdp_session {
+ struct list lmedial;
+ struct list medial;
+ struct list lattrl;
+ struct list rattrl;
+ struct sa laddr;
+ struct sa raddr;
+ int32_t lbwv[SDP_BANDWIDTH_MAX];
+ int32_t rbwv[SDP_BANDWIDTH_MAX];
+ uint32_t id;
+ uint32_t ver;
+ enum sdp_dir rdir;
+};
+
+struct sdp_media {
+ struct le le;
+ struct list lfmtl;
+ struct list rfmtl;
+ struct list lattrl;
+ struct list rattrl;
+ struct sa laddr;
+ struct sa raddr;
+ struct sa laddr_rtcp;
+ struct sa raddr_rtcp;
+ int32_t lbwv[SDP_BANDWIDTH_MAX];
+ int32_t rbwv[SDP_BANDWIDTH_MAX];
+ char *name;
+ char *proto;
+ char *protov[8];
+ char *uproto; /* unsupported protocol */
+ sdp_media_enc_h *ench;
+ void *arg;
+ enum sdp_dir ldir;
+ enum sdp_dir rdir;
+ bool fmt_ignore;
+ bool disabled;
+ int dynpt;
+};
+
+
+/* session */
+void sdp_session_rreset(struct sdp_session *sess);
+
+
+/* media */
+int sdp_media_radd(struct sdp_media **mp, struct sdp_session *sess,
+ const struct pl *name, const struct pl *proto);
+void sdp_media_rreset(struct sdp_media *m);
+bool sdp_media_proto_cmp(struct sdp_media *m, const struct pl *proto,
+ bool update);
+struct sdp_media *sdp_media_find(const struct sdp_session *sess,
+ const struct pl *name,
+ const struct pl *proto,
+ bool update_proto);
+void sdp_media_align_formats(struct sdp_media *m, bool offer);
+
+
+/* format */
+int sdp_format_radd(struct sdp_media *m, const struct pl *id);
+struct sdp_format *sdp_format_find(const struct list *lst,
+ const struct pl *id);
+
+
+/* attribute */
+struct sdp_attr;
+
+int sdp_attr_add(struct list *lst, struct pl *name, struct pl *val);
+int sdp_attr_addv(struct list *lst, const char *name, const char *val,
+ va_list ap);
+void sdp_attr_del(const struct list *lst, const char *name);
+const char *sdp_attr_apply(const struct list *lst, const char *name,
+ sdp_attr_h *attrh, void *arg);
+int sdp_attr_print(struct re_printf *pf, const struct sdp_attr *attr);
+int sdp_attr_debug(struct re_printf *pf, const struct sdp_attr *attr);
diff --git a/src/sdp/session.c b/src/sdp/session.c
new file mode 100644
index 0000000..f74cfe0
--- /dev/null
+++ b/src/sdp/session.c
@@ -0,0 +1,301 @@
+/**
+ * @file sdp/session.c SDP Session
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+#include <re_sys.h>
+#include "sdp.h"
+
+
+static void destructor(void *arg)
+{
+ struct sdp_session *sess = arg;
+
+ list_flush(&sess->lmedial);
+ list_flush(&sess->medial);
+ list_flush(&sess->rattrl);
+ list_flush(&sess->lattrl);
+}
+
+
+/**
+ * Allocate a new SDP Session
+ *
+ * @param sessp Pointer to allocated SDP Session object
+ * @param laddr Local network address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_session_alloc(struct sdp_session **sessp, const struct sa *laddr)
+{
+ struct sdp_session *sess;
+ int err = 0, i;
+
+ if (!sessp || !laddr)
+ return EINVAL;
+
+ sess = mem_zalloc(sizeof(*sess), destructor);
+ if (!sess)
+ return ENOMEM;
+
+ sess->laddr = *laddr;
+ sess->id = rand_u32();
+ sess->ver = rand_u32() & 0x7fffffff;
+ sess->rdir = SDP_SENDRECV;
+
+ sa_init(&sess->raddr, AF_INET);
+
+ for (i=0; i<SDP_BANDWIDTH_MAX; i++) {
+ sess->lbwv[i] = -1;
+ sess->rbwv[i] = -1;
+ }
+
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+/**
+ * Reset the remote side of an SDP Session
+ *
+ * @param sess SDP Session
+ */
+void sdp_session_rreset(struct sdp_session *sess)
+{
+ int i;
+
+ if (!sess)
+ return;
+
+ sa_init(&sess->raddr, AF_INET);
+
+ list_flush(&sess->rattrl);
+
+ sess->rdir = SDP_SENDRECV;
+
+ for (i=0; i<SDP_BANDWIDTH_MAX; i++)
+ sess->rbwv[i] = -1;
+}
+
+
+/**
+ * Set the local network address of an SDP Session
+ *
+ * @param sess SDP Session
+ * @param laddr Local network address
+ */
+void sdp_session_set_laddr(struct sdp_session *sess, const struct sa *laddr)
+{
+ if (!sess || !laddr)
+ return;
+
+ sess->laddr = *laddr;
+}
+
+
+/**
+ * Set the local bandwidth of an SDP Session
+ *
+ * @param sess SDP Session
+ * @param type Bandwidth type
+ * @param bw Bandwidth value
+ */
+void sdp_session_set_lbandwidth(struct sdp_session *sess,
+ enum sdp_bandwidth type, int32_t bw)
+{
+ if (!sess || type >= SDP_BANDWIDTH_MAX)
+ return;
+
+ sess->lbwv[type] = bw;
+}
+
+
+/**
+ * Set a local attribute of an SDP Session
+ *
+ * @param sess SDP Session
+ * @param replace True to replace any existing attributes, false to append
+ * @param name Attribute name
+ * @param value Formatted attribute value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_session_set_lattr(struct sdp_session *sess, bool replace,
+ const char *name, const char *value, ...)
+{
+ va_list ap;
+ int err;
+
+ if (!sess || !name)
+ return EINVAL;
+
+ if (replace)
+ sdp_attr_del(&sess->lattrl, name);
+
+ va_start(ap, value);
+ err = sdp_attr_addv(&sess->lattrl, name, value, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Delete a local attribute of an SDP Session
+ *
+ * @param sess SDP Session
+ * @param name Attribute name
+ */
+void sdp_session_del_lattr(struct sdp_session *sess, const char *name)
+{
+ if (!sess || !name)
+ return;
+
+ sdp_attr_del(&sess->lattrl, name);
+}
+
+
+/**
+ * Get the local bandwidth of an SDP Session
+ *
+ * @param sess SDP Session
+ * @param type Bandwidth type
+ *
+ * @return Bandwidth value
+ */
+int32_t sdp_session_lbandwidth(const struct sdp_session *sess,
+ enum sdp_bandwidth type)
+{
+ if (!sess || type >= SDP_BANDWIDTH_MAX)
+ return 0;
+
+ return sess->lbwv[type];
+}
+
+
+/**
+ * Get the remote bandwidth of an SDP Session
+ *
+ * @param sess SDP Session
+ * @param type Bandwidth type
+ *
+ * @return Bandwidth value
+ */
+int32_t sdp_session_rbandwidth(const struct sdp_session *sess,
+ enum sdp_bandwidth type)
+{
+ if (!sess || type >= SDP_BANDWIDTH_MAX)
+ return 0;
+
+ return sess->rbwv[type];
+}
+
+
+/**
+ * Get a remote attribute of an SDP Session
+ *
+ * @param sess SDP Session
+ * @param name Attribute name
+ *
+ * @return Attribute value if exist, NULL if not exist
+ */
+const char *sdp_session_rattr(const struct sdp_session *sess, const char *name)
+{
+ if (!sess || !name)
+ return NULL;
+
+ return sdp_attr_apply(&sess->rattrl, name, NULL, NULL);
+}
+
+
+/**
+ * Apply a function handler of all matching remote attributes
+ *
+ * @param sess SDP Session
+ * @param name Attribute name
+ * @param attrh Attribute handler
+ * @param arg Handler argument
+ *
+ * @return Attribute value if match
+ */
+const char *sdp_session_rattr_apply(const struct sdp_session *sess,
+ const char *name,
+ sdp_attr_h *attrh, void *arg)
+{
+ if (!sess)
+ return NULL;
+
+ return sdp_attr_apply(&sess->rattrl, name, attrh, arg);
+}
+
+
+/**
+ * Get the list of media-lines from an SDP Session
+ *
+ * @param sess SDP Session
+ * @param local True for local, False for remote
+ *
+ * @return List of media-lines
+ */
+const struct list *sdp_session_medial(const struct sdp_session *sess,
+ bool local)
+{
+ if (!sess)
+ return NULL;
+
+ return local ? &sess->lmedial : &sess->medial;
+}
+
+
+/**
+ * Print SDP Session debug information
+ *
+ * @param pf Print function for output
+ * @param sess SDP Session
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sdp_session_debug(struct re_printf *pf, const struct sdp_session *sess)
+{
+ struct le *le;
+ int err;
+
+ if (!sess)
+ return 0;
+
+ err = re_hprintf(pf, "SDP session\n");
+
+ err |= re_hprintf(pf, " local attributes:\n");
+
+ for (le=sess->lattrl.head; le; le=le->next)
+ err |= re_hprintf(pf, " %H\n", sdp_attr_debug, le->data);
+
+ err |= re_hprintf(pf, " remote attributes:\n");
+
+ for (le=sess->rattrl.head; le; le=le->next)
+ err |= re_hprintf(pf, " %H\n", sdp_attr_debug, le->data);
+
+ err |= re_hprintf(pf, "session media:\n");
+
+ for (le=sess->medial.head; le; le=le->next)
+ err |= sdp_media_debug(pf, le->data);
+
+ err |= re_hprintf(pf, "local media:\n");
+
+ for (le=sess->lmedial.head; le; le=le->next)
+ err |= sdp_media_debug(pf, le->data);
+
+ return err;
+}
diff --git a/src/sdp/str.c b/src/sdp/str.c
new file mode 100644
index 0000000..e50d842
--- /dev/null
+++ b/src/sdp/str.c
@@ -0,0 +1,65 @@
+/**
+ * @file sdp/str.c SDP strings
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+
+
+const char sdp_attr_fmtp[] = "fmtp"; /**< fmtp */
+const char sdp_attr_maxptime[] = "maxptime"; /**< maxptime */
+const char sdp_attr_ptime[] = "ptime"; /**< ptime */
+const char sdp_attr_rtcp[] = "rtcp"; /**< rtcp */
+const char sdp_attr_rtpmap[] = "rtpmap"; /**< rtpmap */
+
+const char sdp_media_audio[] = "audio"; /**< Media type 'audio' */
+const char sdp_media_video[] = "video"; /**< Media type 'video' */
+const char sdp_media_text[] = "text"; /**< Media type 'text' */
+
+const char sdp_proto_rtpavp[] = "RTP/AVP"; /**< RTP Profile */
+const char sdp_proto_rtpsavp[] = "RTP/SAVP"; /**< Secure RTP Profile */
+
+
+/**
+ * Get the SDP media direction name
+ *
+ * @param dir Media direction
+ *
+ * @return Name of media direction
+ */
+const char *sdp_dir_name(enum sdp_dir dir)
+{
+ switch (dir) {
+
+ case SDP_INACTIVE: return "inactive";
+ case SDP_RECVONLY: return "recvonly";
+ case SDP_SENDONLY: return "sendonly";
+ case SDP_SENDRECV: return "sendrecv";
+ default: return "??";
+ }
+}
+
+
+/**
+ * Get the SDP bandwidth name
+ *
+ * @param type Bandwidth type
+ *
+ * @return Bandwidth name
+ */
+const char *sdp_bandwidth_name(enum sdp_bandwidth type)
+{
+ switch (type) {
+
+ case SDP_BANDWIDTH_CT: return "CT";
+ case SDP_BANDWIDTH_AS: return "AS";
+ case SDP_BANDWIDTH_RS: return "RS";
+ case SDP_BANDWIDTH_RR: return "RR";
+ case SDP_BANDWIDTH_TIAS: return "TIAS";
+ default: return "??";
+ }
+}
diff --git a/src/sdp/util.c b/src/sdp/util.c
new file mode 100644
index 0000000..754f5b8
--- /dev/null
+++ b/src/sdp/util.c
@@ -0,0 +1,50 @@
+/**
+ * @file sdp/util.c SDP utility functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sdp.h>
+
+
+/**
+ * Decode RTP Header Extension SDP attribute value
+ *
+ * @param ext Extension-map object
+ * @param val SDP attribute value
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sdp_extmap_decode(struct sdp_extmap *ext, const char *val)
+{
+ struct pl id, dir;
+
+ if (!ext || !val)
+ return EINVAL;
+
+ if (re_regex(val, strlen(val), "[0-9]+[/]*[a-z]* [^ ]+[ ]*[^ ]*",
+ &id, NULL, &dir, &ext->name, NULL, &ext->attrs))
+ return EBADMSG;
+
+ ext->dir_set = false;
+ ext->dir = SDP_SENDRECV;
+
+ if (pl_isset(&dir)) {
+
+ ext->dir_set = true;
+
+ if (!pl_strcmp(&dir, "sendonly")) ext->dir = SDP_SENDONLY;
+ else if (!pl_strcmp(&dir, "sendrecv")) ext->dir = SDP_SENDRECV;
+ else if (!pl_strcmp(&dir, "recvonly")) ext->dir = SDP_RECVONLY;
+ else if (!pl_strcmp(&dir, "inactive")) ext->dir = SDP_INACTIVE;
+ else ext->dir_set = false;
+ }
+
+ ext->id = pl_u32(&id);
+
+ return 0;
+}
diff --git a/src/sha/mod.mk b/src/sha/mod.mk
new file mode 100644
index 0000000..c40fdff
--- /dev/null
+++ b/src/sha/mod.mk
@@ -0,0 +1,9 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+ifeq ($(USE_OPENSSL),)
+SRCS += sha/sha1.c
+endif
diff --git a/src/sha/sha1.c b/src/sha/sha1.c
new file mode 100644
index 0000000..7395a02
--- /dev/null
+++ b/src/sha/sha1.c
@@ -0,0 +1,270 @@
+/**
+ * @file sha1.c SHA-1 in C
+ */
+
+/*
+By Steve Reid <sreid@sea-to-sky.net>
+100% Public Domain
+
+-----------------
+Modified 7/98
+By James H. Brown <jbrown@burgoyne.com>
+Still 100% Public Domain
+
+Corrected a problem which generated improper hash values on 16 bit machines
+Routine SHA1Update changed from
+ void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int
+len)
+to
+ void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned
+long len)
+
+The 'len' parameter was declared an int which works fine on 32 bit machines.
+However, on 16 bit machines an int is too small for the shifts being done
+against
+it. This caused the hash function to generate incorrect values if len was
+greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update().
+
+Since the file IO in main() reads 16K at a time, any file 8K or larger would
+be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million
+"a"s).
+
+I also changed the declaration of variables i & j in SHA1Update to
+unsigned long from unsigned int for the same reason.
+
+These changes should make no difference to any 32 bit implementations since
+an
+int and a long are the same size in those environments.
+
+--
+I also corrected a few compiler warnings generated by Borland C.
+1. Added #include <process.h> for exit() prototype
+2. Removed unused variable 'j' in SHA1Final
+3. Changed exit(0) to return(0) at end of main.
+
+ALL changes I made can be located by searching for comments containing 'JHB'
+-----------------
+Modified 8/98
+By Steve Reid <sreid@sea-to-sky.net>
+Still 100% public domain
+
+1- Removed #include <process.h> and used return() instead of exit()
+2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall)
+3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net
+
+-----------------
+Modified 4/01
+By Saul Kravitz <Saul.Kravitz@celera.com>
+Still 100% PD
+Modified to run on Compaq Alpha hardware.
+
+-----------------
+Modified 07/2002
+By Ralph Giles <giles@artofcode.com>
+Still 100% public domain
+modified for use with stdint types, autoconf
+code cleanup, removed attribution comments
+switched SHA1Final() argument order for consistency
+use SHA1_ prefix for public api
+move public api to sha1.h
+*/
+
+/*
+Test Vectors (from FIPS PUB 180-1)
+"abc"
+ A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+ 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+A million repetitions of "a"
+ 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+*/
+
+#define SHA1HANDSOFF 1
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <re_types.h>
+#include <re_sha.h>
+
+void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64]);
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+#if defined (BYTE_ORDER) && defined(BIG_ENDIAN) && (BYTE_ORDER == BIG_ENDIAN)
+#define WORDS_BIGENDIAN 1
+#endif
+#ifdef _BIG_ENDIAN
+#define WORDS_BIGENDIAN 1
+#endif
+
+
+/* blk0() and blk() perform the initial expand. */
+/* I got the idea of expanding during the round function from SSLeay */
+/* FIXME: can we do this in an endian-proof way? */
+#ifdef WORDS_BIGENDIAN
+#define blk0(i) block->l[i]
+#else
+#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xff00ff00) \
+ |(rol(block->l[i],8)&0x00ff00ff))
+#endif
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+ ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) \
+ z+=((w&(x^y))^y)+blk0(i)+0x5a827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) \
+ z+=((w&(x^y))^y)+blk(i)+0x5a827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) \
+ z+=(w^x^y)+blk(i)+0x6ed9eba1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) \
+ z+=(((w|x)&y)|(w&x))+blk(i)+0x8f1bbcdc+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) \
+ z+=(w^x^y)+blk(i)+0xca62c1d6+rol(v,5);w=rol(w,30);
+
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64])
+{
+ uint32_t a, b, c, d, e;
+ typedef union {
+ uint8_t c[64];
+ uint32_t l[16];
+ } CHAR64LONG16;
+ CHAR64LONG16* block;
+
+#ifdef SHA1HANDSOFF
+ CHAR64LONG16 workspace;
+ block = &workspace;
+ memcpy(block, buffer, 64);
+#else
+ block = (CHAR64LONG16*)buffer;
+#endif
+
+ /* Copy context->state[] to working vars */
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2);R0(c,d,e,a,b, 3);
+ R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6);R0(d,e,a,b,c, 7);
+ R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10);R0(e,a,b,c,d,11);
+ R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14);R0(a,b,c,d,e,15);
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18);R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22);R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26);R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30);R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34);R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38);R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42);R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46);R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50);R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54);R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58);R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62);R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66);R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70);R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74);R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78);R4(b,c,d,e,a,79);
+
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+
+ /* Wipe variables */
+ a = b = c = d = e = 0;
+}
+
+
+/**
+ * Initialize new context
+ *
+ * @param context SHA1-Context
+ */
+void SHA1_Init(SHA1_CTX* context)
+{
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xefcdab89;
+ context->state[2] = 0x98badcfe;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xc3d2e1f0;
+ context->count[0] = context->count[1] = 0;
+}
+
+
+/**
+ * Run your data through this
+ *
+ * @param context SHA1-Context
+ * @param p Buffer to run SHA1 on
+ * @param len Number of bytes
+ */
+void SHA1_Update(SHA1_CTX* context, const void *p, size_t len)
+{
+ const uint8_t* data = p;
+ size_t i, j;
+
+ j = (context->count[0] >> 3) & 63;
+ if ((context->count[0] += (uint32_t)(len << 3)) < (len << 3))
+ context->count[1]++;
+ context->count[1] += (uint32_t)(len >> 29);
+ if ((j + len) > 63) {
+ memcpy(&context->buffer[j], data, (i = 64-j));
+ SHA1_Transform(context->state, context->buffer);
+ for ( ; i + 63 < len; i += 64) {
+ SHA1_Transform(context->state, data + i);
+ }
+ j = 0;
+ }
+ else i = 0;
+ memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+
+/**
+ * Add padding and return the message digest
+ *
+ * @param digest Generated message digest
+ * @param context SHA1-Context
+ */
+void SHA1_Final(uint8_t digest[SHA1_DIGEST_SIZE], SHA1_CTX* context)
+{
+ uint32_t i;
+ uint8_t finalcount[8];
+
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (uint8_t)((context->count[(i >= 4 ? 0 : 1)]
+ >> ((3-(i & 3)) * 8) ) & 255);
+ }
+ SHA1_Update(context, (uint8_t *)"\200", 1);
+ while ((context->count[0] & 504) != 448) {
+ SHA1_Update(context, (uint8_t *)"\0", 1);
+ }
+ SHA1_Update(context, finalcount, 8); /* Should cause SHA1_Transform */
+ for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
+ digest[i] = (uint8_t)
+ ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
+ }
+
+ /* Wipe variables */
+ i = 0;
+ memset(context->buffer, 0, 64);
+ memset(context->state, 0, 20);
+ memset(context->count, 0, 8);
+ memset(finalcount, 0, 8); /* SWR */
+
+#ifdef SHA1HANDSOFF /* make SHA1Transform overwrite its own static vars */
+ SHA1_Transform(context->state, context->buffer);
+#endif
+}
diff --git a/src/sip/addr.c b/src/sip/addr.c
new file mode 100644
index 0000000..72ab21f
--- /dev/null
+++ b/src/sip/addr.c
@@ -0,0 +1,56 @@
+/**
+ * @file sip/addr.c SIP Address decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+
+
+/**
+ * Decode a pointer-length string into a SIP Address object
+ *
+ * @param addr SIP Address object
+ * @param pl Pointer-length string
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sip_addr_decode(struct sip_addr *addr, const struct pl *pl)
+{
+ int err;
+
+ if (!addr || !pl)
+ return EINVAL;
+
+ memset(addr, 0, sizeof(*addr));
+
+ if (0 == re_regex(pl->p, pl->l, "[~ \t\r\n<]*[ \t\r\n]*<[^>]+>[^]*",
+ &addr->dname, NULL, &addr->auri, &addr->params)) {
+
+ if (!addr->dname.l)
+ addr->dname.p = NULL;
+
+ if (!addr->params.l)
+ addr->params.p = NULL;
+ }
+ else {
+ memset(addr, 0, sizeof(*addr));
+
+ if (re_regex(pl->p, pl->l, "[^;]+[^]*",
+ &addr->auri, &addr->params))
+ return EBADMSG;
+ }
+
+ err = uri_decode(&addr->uri, &addr->auri);
+ if (err)
+ memset(addr, 0, sizeof(*addr));
+
+ return err;
+}
diff --git a/src/sip/auth.c b/src/sip/auth.c
new file mode 100644
index 0000000..1357cad
--- /dev/null
+++ b/src/sip/auth.c
@@ -0,0 +1,325 @@
+/**
+ * @file sip/auth.c SIP Authentication
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_sys.h>
+#include <re_md5.h>
+#include <re_httpauth.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+struct sip_auth {
+ struct list realml;
+ sip_auth_h *authh;
+ void *arg;
+ bool ref;
+ int err;
+};
+
+
+struct realm {
+ struct le le;
+ char *realm;
+ char *nonce;
+ char *qop;
+ char *opaque;
+ char *user;
+ char *pass;
+ uint32_t nc;
+ enum sip_hdrid hdr;
+};
+
+
+static int dummy_handler(char **user, char **pass, const char *rlm, void *arg)
+{
+ (void)user;
+ (void)pass;
+ (void)rlm;
+ (void)arg;
+
+ return EAUTH;
+}
+
+
+static void realm_destructor(void *arg)
+{
+ struct realm *realm = arg;
+
+ list_unlink(&realm->le);
+ mem_deref(realm->realm);
+ mem_deref(realm->nonce);
+ mem_deref(realm->qop);
+ mem_deref(realm->opaque);
+ mem_deref(realm->user);
+ mem_deref(realm->pass);
+}
+
+
+static void auth_destructor(void *arg)
+{
+ struct sip_auth *auth = arg;
+
+ if (auth->ref)
+ mem_deref(auth->arg);
+
+ list_flush(&auth->realml);
+}
+
+
+static int mkdigest(uint8_t *digest, const struct realm *realm,
+ const char *met, const char *uri, uint64_t cnonce)
+{
+ uint8_t ha1[MD5_SIZE], ha2[MD5_SIZE];
+ int err;
+
+ err = md5_printf(ha1, "%s:%s:%s",
+ realm->user, realm->realm, realm->pass);
+ if (err)
+ return err;
+
+ err = md5_printf(ha2, "%s:%s", met, uri);
+ if (err)
+ return err;
+
+ if (realm->qop)
+ return md5_printf(digest, "%w:%s:%08x:%016llx:auth:%w",
+ ha1, sizeof(ha1),
+ realm->nonce,
+ realm->nc,
+ cnonce,
+ ha2, sizeof(ha2));
+ else
+ return md5_printf(digest, "%w:%s:%w",
+ ha1, sizeof(ha1),
+ realm->nonce,
+ ha2, sizeof(ha2));
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+ struct realm *realm = le->data;
+ struct pl *chrealm = arg;
+
+ /* handle multiple authenticate headers with equal realm value */
+ if (realm->nc == 1)
+ return false;
+
+ return 0 == pl_strcasecmp(chrealm, realm->realm);
+}
+
+
+static bool auth_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
+ void *arg)
+{
+ struct httpauth_digest_chall ch;
+ struct sip_auth *auth = arg;
+ struct realm *realm = NULL;
+ int err;
+ (void)msg;
+
+ if (httpauth_digest_challenge_decode(&ch, &hdr->val)) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ if (pl_isset(&ch.algorithm) && pl_strcasecmp(&ch.algorithm, "md5")) {
+ err = ENOSYS;
+ goto out;
+ }
+
+ realm = list_ledata(list_apply(&auth->realml, true, cmp_handler,
+ &ch.realm));
+ if (!realm) {
+ realm = mem_zalloc(sizeof(*realm), realm_destructor);
+ if (!realm) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ list_append(&auth->realml, &realm->le, realm);
+
+ err = pl_strdup(&realm->realm, &ch.realm);
+ if (err)
+ goto out;
+
+ err = auth->authh(&realm->user, &realm->pass,
+ realm->realm, auth->arg);
+ if (err)
+ goto out;
+ }
+ else {
+ if (!pl_isset(&ch.stale) || pl_strcasecmp(&ch.stale, "true")) {
+ err = EAUTH;
+ goto out;
+ }
+
+ realm->nonce = mem_deref(realm->nonce);
+ realm->qop = mem_deref(realm->qop);
+ realm->opaque = mem_deref(realm->opaque);
+ }
+
+ realm->hdr = hdr->id;
+ realm->nc = 1;
+
+ err = pl_strdup(&realm->nonce, &ch.nonce);
+
+ if (pl_isset(&ch.qop))
+ err |= pl_strdup(&realm->qop, &ch.qop);
+
+ if (pl_isset(&ch.opaque))
+ err |= pl_strdup(&realm->opaque, &ch.opaque);
+
+ out:
+ if (err) {
+ mem_deref(realm);
+ auth->err = err;
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Update a SIP authentication state from a SIP message
+ *
+ * @param auth SIP Authentication state
+ * @param msg SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_auth_authenticate(struct sip_auth *auth, const struct sip_msg *msg)
+{
+ if (!auth || !msg)
+ return EINVAL;
+
+ if (sip_msg_hdr_apply(msg, true, SIP_HDR_WWW_AUTHENTICATE,
+ auth_handler, auth))
+ return auth->err;
+
+ if (sip_msg_hdr_apply(msg, true, SIP_HDR_PROXY_AUTHENTICATE,
+ auth_handler, auth))
+ return auth->err;
+
+ return 0;
+}
+
+
+int sip_auth_encode(struct mbuf *mb, struct sip_auth *auth, const char *met,
+ const char *uri)
+{
+ struct le *le;
+ int err = 0;
+
+ if (!mb || !auth || !met || !uri)
+ return EINVAL;
+
+ for (le = auth->realml.head; le; le = le->next) {
+
+ const uint64_t cnonce = rand_u64();
+ struct realm *realm = le->data;
+ uint8_t digest[MD5_SIZE];
+
+ err = mkdigest(digest, realm, met, uri, cnonce);
+ if (err)
+ break;
+
+ switch (realm->hdr) {
+
+ case SIP_HDR_WWW_AUTHENTICATE:
+ err = mbuf_write_str(mb, "Authorization: ");
+ break;
+
+ case SIP_HDR_PROXY_AUTHENTICATE:
+ err = mbuf_write_str(mb, "Proxy-Authorization: ");
+ break;
+
+ default:
+ continue;
+ }
+
+ err |= mbuf_printf(mb, "Digest username=\"%s\"", realm->user);
+ err |= mbuf_printf(mb, ", realm=\"%s\"", realm->realm);
+ err |= mbuf_printf(mb, ", nonce=\"%s\"", realm->nonce);
+ err |= mbuf_printf(mb, ", uri=\"%s\"", uri);
+ err |= mbuf_printf(mb, ", response=\"%w\"",
+ digest, sizeof(digest));
+
+ if (realm->opaque)
+ err |= mbuf_printf(mb, ", opaque=\"%s\"",
+ realm->opaque);
+
+ if (realm->qop) {
+ err |= mbuf_printf(mb, ", cnonce=\"%016llx\"", cnonce);
+ err |= mbuf_write_str(mb, ", qop=auth");
+ err |= mbuf_printf(mb, ", nc=%08x", realm->nc);
+ }
+
+ ++realm->nc;
+
+ err |= mbuf_write_str(mb, "\r\n");
+ if (err)
+ break;
+ }
+
+ return err;
+}
+
+
+/**
+ * Allocate a SIP authentication state
+ *
+ * @param authp Pointer to allocated SIP authentication state
+ * @param authh Authentication handler
+ * @param arg Handler argument
+ * @param ref True to mem_ref() argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_auth_alloc(struct sip_auth **authp, sip_auth_h *authh,
+ void *arg, bool ref)
+{
+ struct sip_auth *auth;
+
+ if (!authp)
+ return EINVAL;
+
+ auth = mem_zalloc(sizeof(*auth), auth_destructor);
+ if (!auth)
+ return ENOMEM;
+
+ auth->authh = authh ? authh : dummy_handler;
+ auth->arg = ref ? mem_ref(arg) : arg;
+ auth->ref = ref;
+
+ *authp = auth;
+
+ return 0;
+}
+
+
+/**
+ * Reset a SIP authentication state
+ *
+ * @param auth SIP Authentication state
+ */
+void sip_auth_reset(struct sip_auth *auth)
+{
+ if (!auth)
+ return;
+
+ list_flush(&auth->realml);
+}
diff --git a/src/sip/contact.c b/src/sip/contact.c
new file mode 100644
index 0000000..6703384
--- /dev/null
+++ b/src/sip/contact.c
@@ -0,0 +1,57 @@
+/**
+ * @file sip/contact.c SIP contact functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+
+
+/**
+ * Set contact parameters
+ *
+ * @param contact SIP Contact object
+ * @param uri Username or URI
+ * @param addr IP-address and port
+ * @param tp SIP Transport
+ */
+void sip_contact_set(struct sip_contact *contact, const char *uri,
+ const struct sa *addr, enum sip_transp tp)
+{
+ if (!contact)
+ return;
+
+ contact->uri = uri;
+ contact->addr = addr;
+ contact->tp = tp;
+}
+
+
+/**
+ * Print contact header
+ *
+ * @param pf Print function
+ * @param contact SIP Contact object
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sip_contact_print(struct re_printf *pf, const struct sip_contact *contact)
+{
+ if (!contact)
+ return 0;
+
+ if (contact->uri && strchr(contact->uri, ':'))
+ return re_hprintf(pf, "Contact: <%s>\r\n", contact->uri);
+ else
+ return re_hprintf(pf, "Contact: <sip:%s@%J%s>\r\n",
+ contact->uri,
+ contact->addr,
+ sip_transp_param(contact->tp));
+}
diff --git a/src/sip/cseq.c b/src/sip/cseq.c
new file mode 100644
index 0000000..1be247b
--- /dev/null
+++ b/src/sip/cseq.c
@@ -0,0 +1,40 @@
+/**
+ * @file cseq.c SIP CSeq decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+
+
+/**
+ * Decode a pointer-length string into a SIP CSeq header
+ *
+ * @param cseq SIP CSeq header
+ * @param pl Pointer-length string
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sip_cseq_decode(struct sip_cseq *cseq, const struct pl *pl)
+{
+ struct pl num;
+ int err;
+
+ if (!cseq || !pl)
+ return EINVAL;
+
+ err = re_regex(pl->p, pl->l, "[0-9]+[ \t\r\n]+[^ \t\r\n]+",
+ &num, NULL, &cseq->met);
+ if (err)
+ return err;
+
+ cseq->num = pl_u32(&num);
+
+ return 0;
+}
diff --git a/src/sip/ctrans.c b/src/sip/ctrans.c
new file mode 100644
index 0000000..0ff3266
--- /dev/null
+++ b/src/sip/ctrans.c
@@ -0,0 +1,449 @@
+/**
+ * @file sip/ctrans.c SIP Client Transaction
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum state {
+ TRYING,
+ CALLING,
+ PROCEEDING,
+ COMPLETED,
+};
+
+
+enum {
+ COMPLETE_WAIT = 32000,
+};
+
+
+struct sip_ctrans {
+ struct le he;
+ struct sa dst;
+ struct tmr tmr;
+ struct tmr tmre;
+ struct sip *sip;
+ struct mbuf *mb;
+ struct mbuf *mb_ack;
+ struct sip_msg *req;
+ struct sip_connqent *qent;
+ char *met;
+ char *branch;
+ sip_resp_h *resph;
+ void *arg;
+ enum sip_transp tp;
+ enum state state;
+ uint32_t txc;
+ bool invite;
+};
+
+
+static bool route_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
+ void *arg)
+{
+ (void)msg;
+ return 0 != mbuf_printf(arg, "Route: %r\r\n", &hdr->val);
+}
+
+
+static int request_copy(struct mbuf **mbp, struct sip_ctrans *ct,
+ const char *met, const struct sip_msg *resp)
+{
+ struct mbuf *mb;
+ int err;
+
+ if (!ct->req) {
+ err = sip_msg_decode(&ct->req, ct->mb);
+ if (err)
+ return err;
+ }
+
+ mb = mbuf_alloc(1024);
+ if (!mb)
+ return ENOMEM;
+
+ err = mbuf_printf(mb, "%s %r SIP/2.0\r\n", met, &ct->req->ruri);
+ err |= mbuf_printf(mb, "Via: %r\r\n", &ct->req->via.val);
+ err |= mbuf_write_str(mb, "Max-Forwards: 70\r\n");
+ err |= sip_msg_hdr_apply(ct->req, true, SIP_HDR_ROUTE,
+ route_handler, mb) ? ENOMEM : 0;
+ err |= mbuf_printf(mb, "To: %r\r\n",
+ resp ? &resp->to.val : &ct->req->to.val);
+ err |= mbuf_printf(mb, "From: %r\r\n", &ct->req->from.val);
+ err |= mbuf_printf(mb, "Call-ID: %r\r\n", &ct->req->callid);
+ err |= mbuf_printf(mb, "CSeq: %u %s\r\n", ct->req->cseq.num, met);
+ if (ct->sip->software)
+ err |= mbuf_printf(mb, "User-Agent: %s\r\n",ct->sip->software);
+ err |= mbuf_write_str(mb, "Content-Length: 0\r\n\r\n");
+
+ mb->pos = 0;
+
+ if (err)
+ mem_deref(mb);
+ else
+ *mbp = mb;
+
+ return err;
+}
+
+
+static void destructor(void *arg)
+{
+ struct sip_ctrans *ct = arg;
+
+ hash_unlink(&ct->he);
+ tmr_cancel(&ct->tmr);
+ tmr_cancel(&ct->tmre);
+ mem_deref(ct->met);
+ mem_deref(ct->branch);
+ mem_deref(ct->qent);
+ mem_deref(ct->req);
+ mem_deref(ct->mb);
+ mem_deref(ct->mb_ack);
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+ struct sip_ctrans *ct = le->data;
+ const struct sip_msg *msg = arg;
+
+ if (pl_strcmp(&msg->via.branch, ct->branch))
+ return false;
+
+ if (pl_strcmp(&msg->cseq.met, ct->met))
+ return false;
+
+ return true;
+}
+
+
+static void dummy_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ (void)err;
+ (void)msg;
+ (void)arg;
+}
+
+
+static void terminate(struct sip_ctrans *ct, int err)
+{
+ switch (ct->state) {
+
+ case TRYING:
+ case CALLING:
+ case PROCEEDING:
+ ct->resph(err, NULL, ct->arg);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static void transport_handler(int err, void *arg)
+{
+ struct sip_ctrans *ct = arg;
+
+ terminate(ct, err);
+ mem_deref(ct);
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct sip_ctrans *ct = arg;
+
+ terminate(ct, ETIMEDOUT);
+ mem_deref(ct);
+}
+
+
+static void retransmit_handler(void *arg)
+{
+ struct sip_ctrans *ct = arg;
+ uint32_t timeout;
+ int err;
+
+ ct->txc++;
+
+ switch (ct->state) {
+
+ case TRYING:
+ timeout = MIN(SIP_T1<<ct->txc, SIP_T2);
+ break;
+
+ case CALLING:
+ timeout = SIP_T1<<ct->txc;
+ break;
+
+ case PROCEEDING:
+ timeout = SIP_T2;
+ break;
+
+ default:
+ return;
+ }
+
+ tmr_start(&ct->tmre, timeout, retransmit_handler, ct);
+
+ err = sip_transp_send(&ct->qent, ct->sip, NULL, ct->tp, &ct->dst,
+ ct->mb, transport_handler, ct);
+ if (err) {
+ terminate(ct, err);
+ mem_deref(ct);
+ }
+}
+
+
+static void invite_response(struct sip_ctrans *ct, const struct sip_msg *msg)
+{
+ switch (ct->state) {
+
+ case CALLING:
+ tmr_cancel(&ct->tmr);
+ tmr_cancel(&ct->tmre);
+ /*@fallthrough@*/
+ case PROCEEDING:
+ if (msg->scode < 200) {
+ ct->state = PROCEEDING;
+ ct->resph(0, msg, ct->arg);
+ }
+ else if (msg->scode < 300) {
+ ct->resph(0, msg, ct->arg);
+ mem_deref(ct);
+ }
+ else {
+ ct->state = COMPLETED;
+
+ (void)request_copy(&ct->mb_ack, ct, "ACK", msg);
+ (void)sip_send(ct->sip, NULL, ct->tp, &ct->dst,
+ ct->mb_ack);
+
+ ct->resph(0, msg, ct->arg);
+
+ if (sip_transp_reliable(ct->tp)) {
+ mem_deref(ct);
+ break;
+ }
+
+ tmr_start(&ct->tmr, COMPLETE_WAIT, tmr_handler, ct);
+ }
+ break;
+
+ case COMPLETED:
+ if (msg->scode < 300)
+ break;
+
+ (void)sip_send(ct->sip, NULL, ct->tp, &ct->dst, ct->mb_ack);
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static bool response_handler(const struct sip_msg *msg, void *arg)
+{
+ struct sip_ctrans *ct;
+ struct sip *sip = arg;
+
+ ct = list_ledata(hash_lookup(sip->ht_ctrans,
+ hash_joaat_pl(&msg->via.branch),
+ cmp_handler, (void *)msg));
+ if (!ct)
+ return false;
+
+ if (ct->invite) {
+ invite_response(ct, msg);
+ return true;
+ }
+
+ switch (ct->state) {
+
+ case TRYING:
+ case PROCEEDING:
+ if (msg->scode < 200) {
+ ct->state = PROCEEDING;
+ ct->resph(0, msg, ct->arg);
+ }
+ else {
+ ct->state = COMPLETED;
+ ct->resph(0, msg, ct->arg);
+
+ if (sip_transp_reliable(ct->tp)) {
+ mem_deref(ct);
+ break;
+ }
+
+ tmr_start(&ct->tmr, SIP_T4, tmr_handler, ct);
+ tmr_cancel(&ct->tmre);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+
+int sip_ctrans_request(struct sip_ctrans **ctp, struct sip *sip,
+ enum sip_transp tp, const struct sa *dst, char *met,
+ char *branch, struct mbuf *mb,
+ sip_resp_h *resph, void *arg)
+{
+ struct sip_ctrans *ct;
+ int err;
+
+ if (!sip || !dst || !met || !branch || !mb)
+ return EINVAL;
+
+ ct = mem_zalloc(sizeof(*ct), destructor);
+ if (!ct)
+ return ENOMEM;
+
+ hash_append(sip->ht_ctrans, hash_joaat_str(branch), &ct->he, ct);
+
+ ct->invite = !strcmp(met, "INVITE");
+ ct->branch = mem_ref(branch);
+ ct->met = mem_ref(met);
+ ct->mb = mem_ref(mb);
+ ct->dst = *dst;
+ ct->tp = tp;
+ ct->sip = sip;
+ ct->state = ct->invite ? CALLING : TRYING;
+ ct->resph = resph ? resph : dummy_handler;
+ ct->arg = arg;
+
+ err = sip_transp_send(&ct->qent, sip, NULL, tp, dst, mb,
+ transport_handler, ct);
+ if (err)
+ goto out;
+
+ tmr_start(&ct->tmr, 64 * SIP_T1, tmr_handler, ct);
+
+ if (!sip_transp_reliable(ct->tp))
+ tmr_start(&ct->tmre, SIP_T1, retransmit_handler, ct);
+
+ out:
+ if (err)
+ mem_deref(ct);
+ else if (ctp)
+ *ctp = ct;
+
+ return err;
+}
+
+
+int sip_ctrans_cancel(struct sip_ctrans *ct)
+{
+ struct mbuf *mb = NULL;
+ char *cancel = NULL;
+ int err;
+
+ if (!ct)
+ return EINVAL;
+
+ if (!ct->invite)
+ return 0;
+
+ switch (ct->state) {
+
+ case PROCEEDING:
+ tmr_start(&ct->tmr, 64 * SIP_T1, tmr_handler, ct);
+ break;
+
+ default:
+ return EPROTO;
+ }
+
+ err = str_dup(&cancel, "CANCEL");
+ if (err)
+ goto out;
+
+ err = request_copy(&mb, ct, cancel, NULL);
+ if (err)
+ goto out;
+
+ err = sip_ctrans_request(NULL, ct->sip, ct->tp, &ct->dst, cancel,
+ ct->branch, mb, NULL, NULL);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(cancel);
+ mem_deref(mb);
+
+ return err;
+}
+
+
+int sip_ctrans_init(struct sip *sip, uint32_t sz)
+{
+ int err;
+
+ err = sip_listen(NULL, sip, false, response_handler, sip);
+ if (err)
+ return err;
+
+ return hash_alloc(&sip->ht_ctrans, sz);
+}
+
+
+static const char *statename(enum state state)
+{
+ switch (state) {
+
+ case TRYING: return "TRYING";
+ case CALLING: return "CALLING";
+ case PROCEEDING: return "PROCEEDING";
+ case COMPLETED: return "COMPLETED";
+ default: return "???";
+ }
+}
+
+
+static bool debug_handler(struct le *le, void *arg)
+{
+ struct sip_ctrans *ct = le->data;
+ struct re_printf *pf = arg;
+
+ (void)re_hprintf(pf, " %-10s %-10s %2llus (%s)\n",
+ ct->met,
+ statename(ct->state),
+ tmr_get_expire(&ct->tmr)/1000,
+ ct->branch);
+
+ return false;
+}
+
+
+int sip_ctrans_debug(struct re_printf *pf, const struct sip *sip)
+{
+ int err;
+
+ err = re_hprintf(pf, "client transactions:\n");
+ hash_apply(sip->ht_ctrans, debug_handler, pf);
+
+ return err;
+}
diff --git a/src/sip/dialog.c b/src/sip/dialog.c
new file mode 100644
index 0000000..5863e20
--- /dev/null
+++ b/src/sip/dialog.c
@@ -0,0 +1,659 @@
+/**
+ * @file dialog.c SIP Dialog
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum {
+ ROUTE_OFFSET = 7,
+ X64_STRSIZE = 17,
+};
+
+struct sip_dialog {
+ struct uri route;
+ struct mbuf *mb;
+ char *callid;
+ char *ltag;
+ char *rtag;
+ char *uri;
+ uint32_t hash;
+ uint32_t lseq;
+ uint32_t rseq;
+ size_t cpos;
+};
+
+
+struct route_enc {
+ struct mbuf *mb;
+ size_t end;
+};
+
+
+static int x64_strdup(char **strp, uint64_t val)
+{
+ char *str;
+
+ str = mem_alloc(X64_STRSIZE, NULL);
+ if (!str)
+ return ENOMEM;
+
+ (void)re_snprintf(str, X64_STRSIZE, "%016llx", val);
+
+ *strp = str;
+
+ return 0;
+}
+
+
+static void destructor(void *arg)
+{
+ struct sip_dialog *dlg = arg;
+
+ mem_deref(dlg->callid);
+ mem_deref(dlg->ltag);
+ mem_deref(dlg->rtag);
+ mem_deref(dlg->uri);
+ mem_deref(dlg->mb);
+}
+
+
+/**
+ * Allocate a SIP Dialog
+ *
+ * @param dlgp Pointer to allocated SIP Dialog
+ * @param uri Target URI
+ * @param to_uri To URI
+ * @param from_name From displayname (optional)
+ * @param from_uri From URI
+ * @param routev Route vector
+ * @param routec Route count
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_alloc(struct sip_dialog **dlgp,
+ const char *uri, const char *to_uri,
+ const char *from_name, const char *from_uri,
+ const char *routev[], uint32_t routec)
+{
+ const uint64_t ltag = rand_u64();
+ struct sip_dialog *dlg;
+ struct sip_addr addr;
+ size_t rend = 0;
+ struct pl pl;
+ uint32_t i;
+ int err;
+
+ if (!dlgp || !uri || !to_uri || !from_uri)
+ return EINVAL;
+
+ dlg = mem_zalloc(sizeof(*dlg), destructor);
+ if (!dlg)
+ return ENOMEM;
+
+ dlg->hash = hash_fast_str(from_uri);
+ dlg->lseq = rand_u16();
+
+ err = str_dup(&dlg->uri, uri);
+ if (err)
+ goto out;
+
+ err = x64_strdup(&dlg->callid, rand_u64());
+ if (err)
+ goto out;
+
+ err = x64_strdup(&dlg->ltag, ltag);
+ if (err)
+ goto out;
+
+ dlg->mb = mbuf_alloc(512);
+ if (!dlg->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ for (i=0; i<routec; i++) {
+ err |= mbuf_printf(dlg->mb, "Route: <%s;lr>\r\n", routev[i]);
+ if (i == 0)
+ rend = dlg->mb->pos - 2;
+ }
+ err |= mbuf_printf(dlg->mb, "To: <%s>\r\n", to_uri);
+ dlg->cpos = dlg->mb->pos;
+ err |= mbuf_printf(dlg->mb, "From: %s%s%s<%s>;tag=%016llx\r\n",
+ from_name ? "\"" : "", from_name,
+ from_name ? "\" " : "",
+ from_uri, ltag);
+ if (err)
+ goto out;
+
+ dlg->mb->pos = 0;
+
+ if (rend) {
+ pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET;
+ pl.l = rend - ROUTE_OFFSET;
+ err = sip_addr_decode(&addr, &pl);
+ dlg->route = addr.uri;
+ }
+ else {
+ pl_set_str(&pl, dlg->uri);
+ err = uri_decode(&dlg->route, &pl);
+ }
+
+ out:
+ if (err)
+ mem_deref(dlg);
+ else
+ *dlgp = dlg;
+
+ return err;
+}
+
+
+static bool record_route_handler(const struct sip_hdr *hdr,
+ const struct sip_msg *msg,
+ void *arg)
+{
+ struct route_enc *renc = arg;
+ (void)msg;
+
+ if (mbuf_printf(renc->mb, "Route: %r\r\n", &hdr->val))
+ return true;
+
+ if (!renc->end)
+ renc->end = renc->mb->pos - 2;
+
+ return false;
+}
+
+
+/**
+ * Accept and create a SIP Dialog from an incoming SIP Message
+ *
+ * @param dlgp Pointer to allocated SIP Dialog
+ * @param msg SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_accept(struct sip_dialog **dlgp, const struct sip_msg *msg)
+{
+ const struct sip_hdr *contact;
+ struct sip_dialog *dlg;
+ struct route_enc renc;
+ struct sip_addr addr;
+ struct pl pl;
+ int err;
+
+ if (!dlgp || !msg || !msg->req)
+ return EINVAL;
+
+ contact = sip_msg_hdr(msg, SIP_HDR_CONTACT);
+
+ if (!contact || !msg->callid.p)
+ return EBADMSG;
+
+ if (sip_addr_decode(&addr, &contact->val))
+ return EBADMSG;
+
+ dlg = mem_zalloc(sizeof(*dlg), destructor);
+ if (!dlg)
+ return ENOMEM;
+
+ dlg->hash = rand_u32();
+ dlg->lseq = rand_u16();
+ dlg->rseq = msg->cseq.num;
+
+ err = pl_strdup(&dlg->uri, &addr.auri);
+ if (err)
+ goto out;
+
+ err = pl_strdup(&dlg->callid, &msg->callid);
+ if (err)
+ goto out;
+
+ err = x64_strdup(&dlg->ltag, msg->tag);
+ if (err)
+ goto out;
+
+ err = pl_strdup(&dlg->rtag, &msg->from.tag);
+ if (err)
+ goto out;
+
+ dlg->mb = mbuf_alloc(512);
+ if (!dlg->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ renc.mb = dlg->mb;
+ renc.end = 0;
+
+ err |= sip_msg_hdr_apply(msg, true, SIP_HDR_RECORD_ROUTE,
+ record_route_handler, &renc) ? ENOMEM : 0;
+ err |= mbuf_printf(dlg->mb, "To: %r\r\n", &msg->from.val);
+ err |= mbuf_printf(dlg->mb, "From: %r;tag=%016llx\r\n", &msg->to.val,
+ msg->tag);
+ if (err)
+ goto out;
+
+ dlg->mb->pos = 0;
+
+ if (renc.end) {
+ pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET;
+ pl.l = renc.end - ROUTE_OFFSET;
+ err = sip_addr_decode(&addr, &pl);
+ dlg->route = addr.uri;
+ }
+ else {
+ pl_set_str(&pl, dlg->uri);
+ err = uri_decode(&dlg->route, &pl);
+ }
+
+ out:
+ if (err)
+ mem_deref(dlg);
+ else
+ *dlgp = dlg;
+
+ return err;
+}
+
+
+/**
+ * Initialize a SIP Dialog from an incoming SIP Message
+ *
+ * @param dlg SIP Dialog to initialize
+ * @param msg SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_create(struct sip_dialog *dlg, const struct sip_msg *msg)
+{
+ char *uri = NULL, *rtag = NULL;
+ const struct sip_hdr *contact;
+ struct route_enc renc;
+ struct sip_addr addr;
+ struct pl pl;
+ int err;
+
+ if (!dlg || dlg->rtag || !dlg->cpos || !msg)
+ return EINVAL;
+
+ contact = sip_msg_hdr(msg, SIP_HDR_CONTACT);
+
+ if (!contact)
+ return EBADMSG;
+
+ if (sip_addr_decode(&addr, &contact->val))
+ return EBADMSG;
+
+ renc.mb = mbuf_alloc(512);
+ if (!renc.mb)
+ return ENOMEM;
+
+ err = pl_strdup(&uri, &addr.auri);
+ if (err)
+ goto out;
+
+ err = pl_strdup(&rtag, msg->req ? &msg->from.tag : &msg->to.tag);
+ if (err)
+ goto out;
+
+ renc.end = 0;
+
+ err |= sip_msg_hdr_apply(msg, msg->req, SIP_HDR_RECORD_ROUTE,
+ record_route_handler, &renc) ? ENOMEM : 0;
+ err |= mbuf_printf(renc.mb, "To: %r\r\n",
+ msg->req ? &msg->from.val : &msg->to.val);
+
+ dlg->mb->pos = dlg->cpos;
+ err |= mbuf_write_mem(renc.mb, mbuf_buf(dlg->mb),
+ mbuf_get_left(dlg->mb));
+ dlg->mb->pos = 0;
+
+ if (err)
+ goto out;
+
+ renc.mb->pos = 0;
+
+ if (renc.end) {
+ pl.p = (const char *)mbuf_buf(renc.mb) + ROUTE_OFFSET;
+ pl.l = renc.end - ROUTE_OFFSET;
+ err = sip_addr_decode(&addr, &pl);
+ if (err)
+ goto out;
+
+ dlg->route = addr.uri;
+ }
+ else {
+ struct uri tmp;
+
+ pl_set_str(&pl, uri);
+ err = uri_decode(&tmp, &pl);
+ if (err)
+ goto out;
+
+ dlg->route = tmp;
+ }
+
+ mem_deref(dlg->mb);
+ mem_deref(dlg->uri);
+
+ dlg->mb = mem_ref(renc.mb);
+ dlg->rtag = mem_ref(rtag);
+ dlg->uri = mem_ref(uri);
+ dlg->rseq = msg->req ? msg->cseq.num : 0;
+ dlg->cpos = 0;
+
+ out:
+ mem_deref(renc.mb);
+ mem_deref(rtag);
+ mem_deref(uri);
+
+ return err;
+}
+
+
+/**
+ * Fork a SIP Dialog from an incoming SIP Message
+ *
+ * @param dlgp Pointer to allocated SIP Dialog
+ * @param odlg Original SIP Dialog
+ * @param msg SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_fork(struct sip_dialog **dlgp, struct sip_dialog *odlg,
+ const struct sip_msg *msg)
+{
+ const struct sip_hdr *contact;
+ struct sip_dialog *dlg;
+ struct route_enc renc;
+ struct sip_addr addr;
+ struct pl pl;
+ int err;
+
+ if (!dlgp || !odlg || !odlg->cpos || !msg)
+ return EINVAL;
+
+ contact = sip_msg_hdr(msg, SIP_HDR_CONTACT);
+
+ if (!contact || !msg->callid.p)
+ return EBADMSG;
+
+ if (sip_addr_decode(&addr, &contact->val))
+ return EBADMSG;
+
+ dlg = mem_zalloc(sizeof(*dlg), destructor);
+ if (!dlg)
+ return ENOMEM;
+
+ dlg->callid = mem_ref(odlg->callid);
+ dlg->ltag = mem_ref(odlg->ltag);
+ dlg->hash = odlg->hash;
+ dlg->lseq = odlg->lseq;
+ dlg->rseq = msg->req ? msg->cseq.num : 0;
+
+ err = pl_strdup(&dlg->uri, &addr.auri);
+ if (err)
+ goto out;
+
+ err = pl_strdup(&dlg->rtag, msg->req ? &msg->from.tag : &msg->to.tag);
+ if (err)
+ goto out;
+
+ dlg->mb = mbuf_alloc(512);
+ if (!dlg->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ renc.mb = dlg->mb;
+ renc.end = 0;
+
+ err |= sip_msg_hdr_apply(msg, msg->req, SIP_HDR_RECORD_ROUTE,
+ record_route_handler, &renc) ? ENOMEM : 0;
+ err |= mbuf_printf(dlg->mb, "To: %r\r\n",
+ msg->req ? &msg->from.val : &msg->to.val);
+
+ odlg->mb->pos = odlg->cpos;
+ err |= mbuf_write_mem(dlg->mb, mbuf_buf(odlg->mb),
+ mbuf_get_left(odlg->mb));
+ odlg->mb->pos = 0;
+
+ if (err)
+ goto out;
+
+ dlg->mb->pos = 0;
+
+ if (renc.end) {
+ pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET;
+ pl.l = renc.end - ROUTE_OFFSET;
+ err = sip_addr_decode(&addr, &pl);
+ dlg->route = addr.uri;
+ }
+ else {
+ pl_set_str(&pl, dlg->uri);
+ err = uri_decode(&dlg->route, &pl);
+ }
+
+ out:
+ if (err)
+ mem_deref(dlg);
+ else
+ *dlgp = dlg;
+
+ return err;
+}
+
+
+/**
+ * Update an existing SIP Dialog from a SIP Message
+ *
+ * @param dlg SIP Dialog to update
+ * @param msg SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_dialog_update(struct sip_dialog *dlg, const struct sip_msg *msg)
+{
+ const struct sip_hdr *contact;
+ struct sip_addr addr;
+ char *uri;
+ int err;
+
+ if (!dlg || !msg)
+ return EINVAL;
+
+ contact = sip_msg_hdr(msg, SIP_HDR_CONTACT);
+ if (!contact)
+ return EBADMSG;
+
+ if (sip_addr_decode(&addr, &contact->val))
+ return EBADMSG;
+
+ err = pl_strdup(&uri, &addr.auri);
+ if (err)
+ return err;
+
+ if (dlg->route.scheme.p == dlg->uri) {
+
+ struct uri tmp;
+ struct pl pl;
+
+ pl_set_str(&pl, uri);
+ err = uri_decode(&tmp, &pl);
+ if (err)
+ goto out;
+
+ dlg->route = tmp;
+ }
+
+ mem_deref(dlg->uri);
+ dlg->uri = mem_ref(uri);
+
+ out:
+ mem_deref(uri);
+
+ return err;
+}
+
+
+/**
+ * Check if a remote sequence number is valid
+ *
+ * @param dlg SIP Dialog
+ * @param msg SIP Message
+ *
+ * @return True if valid, False if invalid
+ */
+bool sip_dialog_rseq_valid(struct sip_dialog *dlg, const struct sip_msg *msg)
+{
+ if (!dlg || !msg || !msg->req)
+ return false;
+
+ if (msg->cseq.num < dlg->rseq)
+ return false;
+
+ dlg->rseq = msg->cseq.num;
+
+ return true;
+}
+
+
+int sip_dialog_encode(struct mbuf *mb, struct sip_dialog *dlg, uint32_t cseq,
+ const char *met)
+{
+ int err = 0;
+
+ if (!mb || !dlg || !met)
+ return EINVAL;
+
+ err |= mbuf_write_mem(mb, mbuf_buf(dlg->mb), mbuf_get_left(dlg->mb));
+ err |= mbuf_printf(mb, "Call-ID: %s\r\n", dlg->callid);
+ err |= mbuf_printf(mb, "CSeq: %u %s\r\n", strcmp(met, "ACK") ?
+ dlg->lseq++ : cseq, met);
+
+ return err;
+}
+
+
+const char *sip_dialog_uri(const struct sip_dialog *dlg)
+{
+ return dlg ? dlg->uri : NULL;
+}
+
+
+const struct uri *sip_dialog_route(const struct sip_dialog *dlg)
+{
+ return dlg ? &dlg->route : NULL;
+}
+
+
+uint32_t sip_dialog_hash(const struct sip_dialog *dlg)
+{
+ return dlg ? dlg->hash : 0;
+}
+
+
+/**
+ * Get the Call-ID from a SIP Dialog
+ *
+ * @param dlg SIP Dialog
+ *
+ * @return Call-ID string
+ */
+const char *sip_dialog_callid(const struct sip_dialog *dlg)
+{
+ return dlg ? dlg->callid : NULL;
+}
+
+
+/**
+ * Get the local sequence number from a SIP Dialog
+ *
+ * @param dlg SIP Dialog
+ *
+ * @return Local sequence number
+ */
+uint32_t sip_dialog_lseq(const struct sip_dialog *dlg)
+{
+ return dlg ? dlg->lseq : 0;
+}
+
+
+/**
+ * Check if a SIP Dialog is established
+ *
+ * @param dlg SIP Dialog
+ *
+ * @return True if established, False if not
+ */
+bool sip_dialog_established(const struct sip_dialog *dlg)
+{
+ return dlg && dlg->rtag;
+}
+
+
+/**
+ * Compare a SIP Dialog against a SIP Message
+ *
+ * @param dlg SIP Dialog
+ * @param msg SIP Message
+ *
+ * @return True if match, False if no match
+ */
+bool sip_dialog_cmp(const struct sip_dialog *dlg, const struct sip_msg *msg)
+{
+ if (!dlg || !msg)
+ return false;
+
+ if (pl_strcmp(&msg->callid, dlg->callid))
+ return false;
+
+ if (pl_strcmp(msg->req ? &msg->to.tag : &msg->from.tag, dlg->ltag))
+ return false;
+
+ if (pl_strcmp(msg->req ? &msg->from.tag : &msg->to.tag, dlg->rtag))
+ return false;
+
+ return true;
+}
+
+
+/**
+ * Compare a half SIP Dialog against a SIP Message
+ *
+ * @param dlg SIP Dialog
+ * @param msg SIP Message
+ *
+ * @return True if match, False if no match
+ */
+bool sip_dialog_cmp_half(const struct sip_dialog *dlg,
+ const struct sip_msg *msg)
+{
+ if (!dlg || !msg)
+ return false;
+
+ if (pl_strcmp(&msg->callid, dlg->callid))
+ return false;
+
+ if (pl_strcmp(msg->req ? &msg->to.tag : &msg->from.tag, dlg->ltag))
+ return false;
+
+ return true;
+}
diff --git a/src/sip/keepalive.c b/src/sip/keepalive.c
new file mode 100644
index 0000000..1e1594f
--- /dev/null
+++ b/src/sip/keepalive.c
@@ -0,0 +1,115 @@
+/**
+ * @file sip/keepalive.c SIP Keepalive
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_sys.h>
+#include <re_uri.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+static void destructor(void *arg)
+{
+ struct sip_keepalive *ka = arg;
+
+ if (ka->kap)
+ *ka->kap = NULL;
+
+ list_unlink(&ka->le);
+}
+
+
+void sip_keepalive_signal(struct list *kal, int err)
+{
+ struct le *le = list_head(kal);
+
+ while (le) {
+
+ struct sip_keepalive *ka = le->data;
+ sip_keepalive_h *kah = ka->kah;
+ void *arg = ka->arg;
+
+ le = le->next;
+
+ list_unlink(&ka->le);
+ mem_deref(ka);
+
+ kah(err, arg);
+ }
+}
+
+
+uint64_t sip_keepalive_wait(uint32_t interval)
+{
+ return interval * (800 + rand_u16() % 201);
+}
+
+
+/**
+ * Start a keepalive handler on a SIP transport
+ *
+ * @param kap Pointer to allocated keepalive object
+ * @param sip SIP Stack instance
+ * @param msg SIP Message
+ * @param interval Keepalive interval in seconds
+ * @param kah Keepalive handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_keepalive_start(struct sip_keepalive **kap, struct sip *sip,
+ const struct sip_msg *msg, uint32_t interval,
+ sip_keepalive_h *kah, void *arg)
+{
+ struct sip_keepalive *ka;
+ int err;
+
+ if (!kap || !sip || !msg || !kah)
+ return EINVAL;
+
+ ka = mem_zalloc(sizeof(*ka), destructor);
+ if (!ka)
+ return ENOMEM;
+
+ ka->kah = kah;
+ ka->arg = arg;
+
+ switch (msg->tp) {
+
+ case SIP_TRANSP_UDP:
+ err = sip_keepalive_udp(ka, sip, (struct udp_sock *)msg->sock,
+ &msg->src, interval);
+ break;
+
+ case SIP_TRANSP_TCP:
+ case SIP_TRANSP_TLS:
+ err = sip_keepalive_tcp(ka, (struct sip_conn *)msg->sock,
+ interval);
+ break;
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ if (err) {
+ mem_deref(ka);
+ }
+ else {
+ ka->kap = kap;
+ *kap = ka;
+ }
+
+ return err;
+}
diff --git a/src/sip/keepalive_udp.c b/src/sip/keepalive_udp.c
new file mode 100644
index 0000000..39efa89
--- /dev/null
+++ b/src/sip/keepalive_udp.c
@@ -0,0 +1,188 @@
+/**
+ * @file keepalive_udp.c SIP UDP Keepalive
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum {
+ UDP_KEEPALIVE_INTVAL = 29,
+};
+
+
+struct sip_udpconn {
+ struct le he;
+ struct list kal;
+ struct tmr tmr_ka;
+ struct sa maddr;
+ struct sa paddr;
+ struct udp_sock *us;
+ struct stun_ctrans *ct;
+ struct stun *stun;
+ uint32_t ka_interval;
+};
+
+
+static void udpconn_keepalive_handler(void *arg);
+
+
+static void destructor(void *arg)
+{
+ struct sip_udpconn *uc = arg;
+
+ list_flush(&uc->kal);
+ hash_unlink(&uc->he);
+ tmr_cancel(&uc->tmr_ka);
+ mem_deref(uc->ct);
+ mem_deref(uc->us);
+ mem_deref(uc->stun);
+}
+
+
+static void udpconn_close(struct sip_udpconn *uc, int err)
+{
+ sip_keepalive_signal(&uc->kal, err);
+ hash_unlink(&uc->he);
+ tmr_cancel(&uc->tmr_ka);
+ uc->ct = mem_deref(uc->ct);
+ uc->us = mem_deref(uc->us);
+ uc->stun = mem_deref(uc->stun);
+}
+
+
+static void stun_response_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct sip_udpconn *uc = arg;
+ struct stun_attr *attr;
+ (void)reason;
+
+ if (err || scode) {
+ err = err ? err : EPROTO;
+ goto out;
+ }
+
+ attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ if (!attr) {
+ attr = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
+ if (!attr) {
+ err = EPROTO;
+ goto out;
+ }
+ }
+
+ if (!sa_isset(&uc->maddr, SA_ALL)) {
+ uc->maddr = attr->v.sa;
+ }
+ else if (!sa_cmp(&uc->maddr, &attr->v.sa, SA_ALL)) {
+ err = ENOTCONN;
+ goto out;
+ }
+
+ out:
+ if (err) {
+ udpconn_close(uc, err);
+ mem_deref(uc);
+ }
+ else {
+ tmr_start(&uc->tmr_ka, sip_keepalive_wait(uc->ka_interval),
+ udpconn_keepalive_handler, uc);
+ }
+}
+
+
+static void udpconn_keepalive_handler(void *arg)
+{
+ struct sip_udpconn *uc = arg;
+ int err;
+
+ if (!uc->kal.head) {
+ /* no need for us anymore */
+ udpconn_close(uc, 0);
+ mem_deref(uc);
+ return;
+ }
+
+ err = stun_request(&uc->ct, uc->stun, IPPROTO_UDP, uc->us,
+ &uc->paddr, 0, STUN_METHOD_BINDING, NULL, 0,
+ false, stun_response_handler, uc, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+ if (err) {
+ udpconn_close(uc, err);
+ mem_deref(uc);
+ }
+}
+
+
+static struct sip_udpconn *udpconn_find(struct sip *sip, struct udp_sock *us,
+ const struct sa *paddr)
+{
+ struct le *le;
+
+ le = list_head(hash_list(sip->ht_udpconn, sa_hash(paddr, SA_ALL)));
+
+ for (; le; le = le->next) {
+
+ struct sip_udpconn *uc = le->data;
+
+ if (!sa_cmp(&uc->paddr, paddr, SA_ALL))
+ continue;
+
+ if (uc->us != us)
+ continue;
+
+ return uc;
+ }
+
+ return NULL;
+}
+
+
+int sip_keepalive_udp(struct sip_keepalive *ka, struct sip *sip,
+ struct udp_sock *us, const struct sa *paddr,
+ uint32_t interval)
+{
+ struct sip_udpconn *uc;
+
+ if (!ka || !sip || !us || !paddr)
+ return EINVAL;
+
+ uc = udpconn_find(sip, us, paddr);
+ if (!uc) {
+ uc = mem_zalloc(sizeof(*uc), destructor);
+ if (!uc)
+ return ENOMEM;
+
+ hash_append(sip->ht_udpconn, sa_hash(paddr, SA_ALL),
+ &uc->he, uc);
+
+ uc->paddr = *paddr;
+ uc->stun = mem_ref(sip->stun);
+ uc->us = mem_ref(us);
+ uc->ka_interval = interval ? interval : UDP_KEEPALIVE_INTVAL;
+
+ /* learn mapped address immediately */
+ tmr_start(&uc->tmr_ka, 0, udpconn_keepalive_handler, uc);
+ }
+
+ list_append(&uc->kal, &ka->le, ka);
+
+ return 0;
+}
diff --git a/src/sip/mod.mk b/src/sip/mod.mk
new file mode 100644
index 0000000..446c028
--- /dev/null
+++ b/src/sip/mod.mk
@@ -0,0 +1,21 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += sip/addr.c
+SRCS += sip/auth.c
+SRCS += sip/contact.c
+SRCS += sip/cseq.c
+SRCS += sip/ctrans.c
+SRCS += sip/dialog.c
+SRCS += sip/keepalive.c
+SRCS += sip/keepalive_udp.c
+SRCS += sip/msg.c
+SRCS += sip/reply.c
+SRCS += sip/request.c
+SRCS += sip/sip.c
+SRCS += sip/strans.c
+SRCS += sip/transp.c
+SRCS += sip/via.c
diff --git a/src/sip/msg.c b/src/sip/msg.c
new file mode 100644
index 0000000..2647e0b
--- /dev/null
+++ b/src/sip/msg.c
@@ -0,0 +1,682 @@
+/**
+ * @file sip/msg.c SIP Message decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <ctype.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_sys.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum {
+ HDR_HASH_SIZE = 32,
+ STARTLINE_MAX = 8192,
+};
+
+
+static void hdr_destructor(void *arg)
+{
+ struct sip_hdr *hdr = arg;
+
+ list_unlink(&hdr->le);
+ hash_unlink(&hdr->he);
+}
+
+
+static void destructor(void *arg)
+{
+ struct sip_msg *msg = arg;
+
+ list_flush(&msg->hdrl);
+ hash_flush(msg->hdrht);
+ mem_deref(msg->hdrht);
+ mem_deref(msg->sock);
+ mem_deref(msg->mb);
+}
+
+
+static enum sip_hdrid hdr_hash(const struct pl *name)
+{
+ if (!name->l)
+ return SIP_HDR_NONE;
+
+ if (name->l > 1) {
+ switch (name->p[0]) {
+
+ case 'x':
+ case 'X':
+ if (name->p[1] == '-')
+ return SIP_HDR_NONE;
+
+ /*@fallthrough@*/
+
+ default:
+ return (enum sip_hdrid)
+ (hash_joaat_ci(name->p, name->l) & 0xfff);
+ }
+ }
+
+ /* compact headers */
+ switch (tolower(name->p[0])) {
+
+ case 'a': return SIP_HDR_ACCEPT_CONTACT;
+ case 'b': return SIP_HDR_REFERRED_BY;
+ case 'c': return SIP_HDR_CONTENT_TYPE;
+ case 'd': return SIP_HDR_REQUEST_DISPOSITION;
+ case 'e': return SIP_HDR_CONTENT_ENCODING;
+ case 'f': return SIP_HDR_FROM;
+ case 'i': return SIP_HDR_CALL_ID;
+ case 'j': return SIP_HDR_REJECT_CONTACT;
+ case 'k': return SIP_HDR_SUPPORTED;
+ case 'l': return SIP_HDR_CONTENT_LENGTH;
+ case 'm': return SIP_HDR_CONTACT;
+ case 'n': return SIP_HDR_IDENTITY_INFO;
+ case 'o': return SIP_HDR_EVENT;
+ case 'r': return SIP_HDR_REFER_TO;
+ case 's': return SIP_HDR_SUBJECT;
+ case 't': return SIP_HDR_TO;
+ case 'u': return SIP_HDR_ALLOW_EVENTS;
+ case 'v': return SIP_HDR_VIA;
+ case 'x': return SIP_HDR_SESSION_EXPIRES;
+ case 'y': return SIP_HDR_IDENTITY;
+ default: return SIP_HDR_NONE;
+ }
+}
+
+
+static inline bool hdr_comma_separated(enum sip_hdrid id)
+{
+ switch (id) {
+
+ case SIP_HDR_ACCEPT:
+ case SIP_HDR_ACCEPT_CONTACT:
+ case SIP_HDR_ACCEPT_ENCODING:
+ case SIP_HDR_ACCEPT_LANGUAGE:
+ case SIP_HDR_ACCEPT_RESOURCE_PRIORITY:
+ case SIP_HDR_ALERT_INFO:
+ case SIP_HDR_ALLOW:
+ case SIP_HDR_ALLOW_EVENTS:
+ case SIP_HDR_AUTHENTICATION_INFO:
+ case SIP_HDR_CALL_INFO:
+ case SIP_HDR_CONTACT:
+ case SIP_HDR_CONTENT_ENCODING:
+ case SIP_HDR_CONTENT_LANGUAGE:
+ case SIP_HDR_ERROR_INFO:
+ case SIP_HDR_HISTORY_INFO:
+ case SIP_HDR_IN_REPLY_TO:
+ case SIP_HDR_P_ASSERTED_IDENTITY:
+ case SIP_HDR_P_ASSOCIATED_URI:
+ case SIP_HDR_P_EARLY_MEDIA:
+ case SIP_HDR_P_MEDIA_AUTHORIZATION:
+ case SIP_HDR_P_PREFERRED_IDENTITY:
+ case SIP_HDR_P_REFUSED_URI_LIST:
+ case SIP_HDR_P_VISITED_NETWORK_ID:
+ case SIP_HDR_PATH:
+ case SIP_HDR_PERMISSION_MISSING:
+ case SIP_HDR_PROXY_REQUIRE:
+ case SIP_HDR_REASON:
+ case SIP_HDR_RECORD_ROUTE:
+ case SIP_HDR_REJECT_CONTACT:
+ case SIP_HDR_REQUEST_DISPOSITION:
+ case SIP_HDR_REQUIRE:
+ case SIP_HDR_RESOURCE_PRIORITY:
+ case SIP_HDR_ROUTE:
+ case SIP_HDR_SECURITY_CLIENT:
+ case SIP_HDR_SECURITY_SERVER:
+ case SIP_HDR_SECURITY_VERIFY:
+ case SIP_HDR_SERVICE_ROUTE:
+ case SIP_HDR_SUPPORTED:
+ case SIP_HDR_TRIGGER_CONSENT:
+ case SIP_HDR_UNSUPPORTED:
+ case SIP_HDR_VIA:
+ case SIP_HDR_WARNING:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+
+static inline int hdr_add(struct sip_msg *msg, const struct pl *name,
+ enum sip_hdrid id, const char *p, ssize_t l,
+ bool atomic, bool line)
+{
+ struct sip_hdr *hdr;
+ int err = 0;
+
+ hdr = mem_zalloc(sizeof(*hdr), hdr_destructor);
+ if (!hdr)
+ return ENOMEM;
+
+ hdr->name = *name;
+ hdr->val.p = p;
+ hdr->val.l = MAX(l, 0);
+ hdr->id = id;
+
+ switch (id) {
+
+ case SIP_HDR_VIA:
+ case SIP_HDR_ROUTE:
+ if (!atomic)
+ break;
+
+ hash_append(msg->hdrht, id, &hdr->he, mem_ref(hdr));
+ list_append(&msg->hdrl, &hdr->le, mem_ref(hdr));
+ break;
+
+ default:
+ if (atomic)
+ hash_append(msg->hdrht, id, &hdr->he, mem_ref(hdr));
+ if (line)
+ list_append(&msg->hdrl, &hdr->le, mem_ref(hdr));
+ break;
+ }
+
+ /* parse common headers */
+ switch (id) {
+
+ case SIP_HDR_VIA:
+ if (!atomic || pl_isset(&msg->via.sentby))
+ break;
+
+ err = sip_via_decode(&msg->via, &hdr->val);
+ break;
+
+ case SIP_HDR_TO:
+ err = sip_addr_decode((struct sip_addr *)&msg->to, &hdr->val);
+ if (err)
+ break;
+
+ (void)msg_param_decode(&msg->to.params, "tag", &msg->to.tag);
+ msg->to.val = hdr->val;
+ break;
+
+ case SIP_HDR_FROM:
+ err = sip_addr_decode((struct sip_addr *)&msg->from,
+ &hdr->val);
+ if (err)
+ break;
+
+ (void)msg_param_decode(&msg->from.params, "tag",
+ &msg->from.tag);
+ msg->from.val = hdr->val;
+ break;
+
+ case SIP_HDR_CALL_ID:
+ msg->callid = hdr->val;
+ break;
+
+ case SIP_HDR_CSEQ:
+ err = sip_cseq_decode(&msg->cseq, &hdr->val);
+ break;
+
+ case SIP_HDR_MAX_FORWARDS:
+ msg->maxfwd = hdr->val;
+ break;
+
+ case SIP_HDR_CONTENT_TYPE:
+ err = msg_ctype_decode(&msg->ctyp, &hdr->val);
+ break;
+
+ case SIP_HDR_CONTENT_LENGTH:
+ msg->clen = hdr->val;
+ break;
+
+ case SIP_HDR_EXPIRES:
+ msg->expires = hdr->val;
+ break;
+
+ default:
+ /* re_printf("%r = %u\n", &hdr->name, id); */
+ break;
+ }
+
+ mem_deref(hdr);
+
+ return err;
+}
+
+
+/**
+ * Decode a SIP message
+ *
+ * @param msgp Pointer to allocated SIP Message
+ * @param mb Buffer containing SIP Message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_msg_decode(struct sip_msg **msgp, struct mbuf *mb)
+{
+ struct pl x, y, z, e, name;
+ const char *p, *v, *cv;
+ struct sip_msg *msg;
+ bool comsep, quote;
+ enum sip_hdrid id = SIP_HDR_NONE;
+ uint32_t ws, lf;
+ size_t l;
+ int err;
+
+ if (!msgp || !mb)
+ return EINVAL;
+
+ p = (const char *)mbuf_buf(mb);
+ l = mbuf_get_left(mb);
+
+ if (re_regex(p, l, "[^ \t\r\n]+ [^ \t\r\n]+ [^\r\n]*[\r]*[\n]1",
+ &x, &y, &z, NULL, &e) || x.p != (char *)mbuf_buf(mb))
+ return (l > STARTLINE_MAX) ? EBADMSG : ENODATA;
+
+ msg = mem_zalloc(sizeof(*msg), destructor);
+ if (!msg)
+ return ENOMEM;
+
+ err = hash_alloc(&msg->hdrht, HDR_HASH_SIZE);
+ if (err)
+ goto out;
+
+ msg->tag = rand_u64();
+ msg->mb = mem_ref(mb);
+ msg->req = (0 == pl_strcmp(&z, "SIP/2.0"));
+
+ if (msg->req) {
+
+ msg->met = x;
+ msg->ruri = y;
+ msg->ver = z;
+
+ if (uri_decode(&msg->uri, &y)) {
+ err = EBADMSG;
+ goto out;
+ }
+ }
+ else {
+ msg->ver = x;
+ msg->scode = pl_u32(&y);
+ msg->reason = z;
+
+ if (!msg->scode) {
+ err = EBADMSG;
+ goto out;
+ }
+ }
+
+ l -= e.p + e.l - p;
+ p = e.p + e.l;
+
+ name.p = v = cv = NULL;
+ name.l = ws = lf = 0;
+ comsep = false;
+ quote = false;
+
+ for (; l > 0; p++, l--) {
+
+ switch (*p) {
+
+ case ' ':
+ case '\t':
+ lf = 0; /* folding */
+ ++ws;
+ break;
+
+ case '\r':
+ ++ws;
+ break;
+
+ case '\n':
+ ++ws;
+
+ if (!lf++)
+ break;
+
+ ++p; --l; /* eoh */
+
+ /*@fallthrough@*/
+
+ default:
+ if (lf || (*p == ',' && comsep && !quote)) {
+
+ if (!name.l) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ err = hdr_add(msg, &name, id, cv ? cv : p,
+ cv ? p - cv - ws : 0,
+ true, cv == v && lf);
+ if (err)
+ goto out;
+
+ if (!lf) { /* comma separated */
+ cv = NULL;
+ break;
+ }
+
+ if (cv != v) {
+ err = hdr_add(msg, &name, id,
+ v ? v : p,
+ v ? p - v - ws : 0,
+ false, true);
+ if (err)
+ goto out;
+ }
+
+ if (lf > 1) { /* eoh */
+ err = 0;
+ goto out;
+ }
+
+ comsep = false;
+ name.p = NULL;
+ cv = v = NULL;
+ lf = 0;
+ }
+
+ if (!name.p) {
+ name.p = p;
+ name.l = 0;
+ ws = 0;
+ }
+
+ if (!name.l) {
+ if (*p != ':') {
+ ws = 0;
+ break;
+ }
+
+ name.l = MAX((int)(p - name.p - ws), 0);
+ if (!name.l) {
+ err = EBADMSG;
+ goto out;
+ }
+
+ id = hdr_hash(&name);
+ comsep = hdr_comma_separated(id);
+ break;
+ }
+
+ if (!cv) {
+ quote = false;
+ cv = p;
+ }
+
+ if (!v) {
+ v = p;
+ }
+
+ if (*p == '"')
+ quote = !quote;
+
+ ws = 0;
+ break;
+ }
+ }
+
+ err = ENODATA;
+
+ out:
+ if (err)
+ mem_deref(msg);
+ else {
+ *msgp = msg;
+ mb->pos = mb->end - l;
+ }
+
+ return err;
+}
+
+
+/**
+ * Get a SIP Header from a SIP Message
+ *
+ * @param msg SIP Message
+ * @param id SIP Header ID
+ *
+ * @return SIP Header if found, NULL if not found
+ */
+const struct sip_hdr *sip_msg_hdr(const struct sip_msg *msg, enum sip_hdrid id)
+{
+ return sip_msg_hdr_apply(msg, true, id, NULL, NULL);
+}
+
+
+/**
+ * Apply a function handler to certain SIP Headers
+ *
+ * @param msg SIP Message
+ * @param fwd True to traverse forwards, false to traverse backwards
+ * @param id SIP Header ID
+ * @param h Function handler
+ * @param arg Handler argument
+ *
+ * @return SIP Header if handler returns true, otherwise NULL
+ */
+const struct sip_hdr *sip_msg_hdr_apply(const struct sip_msg *msg,
+ bool fwd, enum sip_hdrid id,
+ sip_hdr_h *h, void *arg)
+{
+ struct list *lst;
+ struct le *le;
+
+ if (!msg)
+ return NULL;
+
+ lst = hash_list(msg->hdrht, id);
+
+ le = fwd ? list_head(lst) : list_tail(lst);
+
+ while (le) {
+ const struct sip_hdr *hdr = le->data;
+
+ le = fwd ? le->next : le->prev;
+
+ if (hdr->id != id)
+ continue;
+
+ if (!h || h(hdr, msg, arg))
+ return hdr;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Get an unknown SIP Header from a SIP Message
+ *
+ * @param msg SIP Message
+ * @param name Header name
+ *
+ * @return SIP Header if found, NULL if not found
+ */
+const struct sip_hdr *sip_msg_xhdr(const struct sip_msg *msg, const char *name)
+{
+ return sip_msg_xhdr_apply(msg, true, name, NULL, NULL);
+}
+
+
+/**
+ * Apply a function handler to certain unknown SIP Headers
+ *
+ * @param msg SIP Message
+ * @param fwd True to traverse forwards, false to traverse backwards
+ * @param name SIP Header name
+ * @param h Function handler
+ * @param arg Handler argument
+ *
+ * @return SIP Header if handler returns true, otherwise NULL
+ */
+const struct sip_hdr *sip_msg_xhdr_apply(const struct sip_msg *msg,
+ bool fwd, const char *name,
+ sip_hdr_h *h, void *arg)
+{
+ struct list *lst;
+ struct le *le;
+ struct pl pl;
+
+ if (!msg || !name)
+ return NULL;
+
+ pl_set_str(&pl, name);
+
+ lst = hash_list(msg->hdrht, hdr_hash(&pl));
+
+ le = fwd ? list_head(lst) : list_tail(lst);
+
+ while (le) {
+ const struct sip_hdr *hdr = le->data;
+
+ le = fwd ? le->next : le->prev;
+
+ if (pl_casecmp(&hdr->name, &pl))
+ continue;
+
+ if (!h || h(hdr, msg, arg))
+ return hdr;
+ }
+
+ return NULL;
+}
+
+
+static bool count_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
+ void *arg)
+{
+ uint32_t *n = arg;
+ (void)hdr;
+ (void)msg;
+
+ ++(*n);
+
+ return false;
+}
+
+
+/**
+ * Count the number of SIP Headers
+ *
+ * @param msg SIP Message
+ * @param id SIP Header ID
+ *
+ * @return Number of SIP Headers
+ */
+uint32_t sip_msg_hdr_count(const struct sip_msg *msg, enum sip_hdrid id)
+{
+ uint32_t n = 0;
+
+ sip_msg_hdr_apply(msg, true, id, count_handler, &n);
+
+ return n;
+}
+
+
+/**
+ * Count the number of unknown SIP Headers
+ *
+ * @param msg SIP Message
+ * @param name SIP Header name
+ *
+ * @return Number of SIP Headers
+ */
+uint32_t sip_msg_xhdr_count(const struct sip_msg *msg, const char *name)
+{
+ uint32_t n = 0;
+
+ sip_msg_xhdr_apply(msg, true, name, count_handler, &n);
+
+ return n;
+}
+
+
+static bool value_handler(const struct sip_hdr *hdr, const struct sip_msg *msg,
+ void *arg)
+{
+ (void)msg;
+
+ return 0 == pl_strcasecmp(&hdr->val, (const char *)arg);
+}
+
+
+/**
+ * Check if a SIP Header matches a certain value
+ *
+ * @param msg SIP Message
+ * @param id SIP Header ID
+ * @param value Header value to check
+ *
+ * @return True if value matches, false if not
+ */
+bool sip_msg_hdr_has_value(const struct sip_msg *msg, enum sip_hdrid id,
+ const char *value)
+{
+ return NULL != sip_msg_hdr_apply(msg, true, id, value_handler,
+ (void *)value);
+}
+
+
+/**
+ * Check if an unknown SIP Header matches a certain value
+ *
+ * @param msg SIP Message
+ * @param name SIP Header name
+ * @param value Header value to check
+ *
+ * @return True if value matches, false if not
+ */
+bool sip_msg_xhdr_has_value(const struct sip_msg *msg, const char *name,
+ const char *value)
+{
+ return NULL != sip_msg_xhdr_apply(msg, true, name, value_handler,
+ (void *)value);
+}
+
+
+/**
+ * Print a SIP Message to stdout
+ *
+ * @param msg SIP Message
+ */
+void sip_msg_dump(const struct sip_msg *msg)
+{
+ struct le *le;
+ uint32_t i;
+
+ if (!msg)
+ return;
+
+ for (i=0; i<HDR_HASH_SIZE; i++) {
+
+ le = list_head(hash_list(msg->hdrht, i));
+
+ while (le) {
+ const struct sip_hdr *hdr = le->data;
+
+ le = le->next;
+
+ (void)re_printf("%02u '%r'='%r'\n", i, &hdr->name,
+ &hdr->val);
+ }
+ }
+
+ le = list_head(&msg->hdrl);
+
+ while (le) {
+ const struct sip_hdr *hdr = le->data;
+
+ le = le->next;
+
+ (void)re_printf("%02u '%r'='%r'\n", hdr->id, &hdr->name,
+ &hdr->val);
+ }
+}
diff --git a/src/sip/reply.c b/src/sip/reply.c
new file mode 100644
index 0000000..eb2e26f
--- /dev/null
+++ b/src/sip/reply.c
@@ -0,0 +1,268 @@
+/**
+ * @file sip/reply.c SIP Reply
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+static int vreplyf(struct sip_strans **stp, struct mbuf **mbp, bool trans,
+ struct sip *sip, const struct sip_msg *msg, bool rec_route,
+ uint16_t scode, const char *reason,
+ const char *fmt, va_list ap)
+{
+ bool rport = false;
+ uint32_t viac = 0;
+ struct mbuf *mb;
+ struct sa dst;
+ struct le *le;
+ int err;
+
+ if (!sip || !msg || !reason)
+ return EINVAL;
+
+ if (!pl_strcmp(&msg->met, "ACK"))
+ return 0;
+
+ mb = mbuf_alloc(1024);
+ if (!mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = mbuf_printf(mb, "SIP/2.0 %u %s\r\n", scode, reason);
+
+ for (le = msg->hdrl.head; le; le = le->next) {
+
+ struct sip_hdr *hdr = le->data;
+ struct pl rp;
+
+ switch (hdr->id) {
+
+ case SIP_HDR_VIA:
+ err |= mbuf_printf(mb, "%r: ", &hdr->name);
+ if (viac++) {
+ err |= mbuf_printf(mb, "%r\r\n", &hdr->val);
+ break;
+ }
+
+ if (!msg_param_exists(&msg->via.params, "rport", &rp)){
+ err |= mbuf_write_pl_skip(mb, &hdr->val, &rp);
+ err |= mbuf_printf(mb, ";rport=%u",
+ sa_port(&msg->src));
+ rport = true;
+ }
+ else
+ err |= mbuf_write_pl(mb, &hdr->val);
+
+ if (rport || !sa_cmp(&msg->src, &msg->via.addr,
+ SA_ADDR))
+ err |= mbuf_printf(mb, ";received=%j",
+ &msg->src);
+
+ err |= mbuf_write_str(mb, "\r\n");
+ break;
+
+ case SIP_HDR_TO:
+ err |= mbuf_printf(mb, "%r: %r", &hdr->name,
+ &hdr->val);
+ if (!pl_isset(&msg->to.tag) && scode > 100)
+ err |= mbuf_printf(mb, ";tag=%016llx",
+ msg->tag);
+ err |= mbuf_write_str(mb, "\r\n");
+ break;
+
+ case SIP_HDR_RECORD_ROUTE:
+ if (!rec_route)
+ break;
+
+ /*@fallthrough@*/
+
+ case SIP_HDR_FROM:
+ case SIP_HDR_CALL_ID:
+ case SIP_HDR_CSEQ:
+ err |= mbuf_printf(mb, "%r: %r\r\n",
+ &hdr->name, &hdr->val);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (sip->software)
+ err |= mbuf_printf(mb, "Server: %s\r\n", sip->software);
+
+ if (fmt)
+ err |= mbuf_vprintf(mb, fmt, ap);
+ else
+ err |= mbuf_printf(mb, "Content-Length: 0\r\n\r\n");
+
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ sip_reply_addr(&dst, msg, rport);
+
+ if (trans) {
+ err = sip_strans_reply(stp, sip, msg, &dst, scode, mb);
+ }
+ else {
+ err = sip_send(sip, msg->sock, msg->tp, &dst, mb);
+ }
+
+ out:
+ if (err && stp)
+ *stp = mem_deref(*stp);
+
+ if (!err && mbp)
+ *mbp = mb;
+ else
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Formatted reply using Server Transaction
+ *
+ * @param stp Pointer to allocated SIP Server Transaction (optional)
+ * @param mbp Pointer to allocated SIP message buffer (optional)
+ * @param sip SIP Stack instance
+ * @param msg Incoming SIP message
+ * @param rec_route True to copy Record-Route headers
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ * @param fmt Additional formatted SIP headers and body, otherwise NULL
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_treplyf(struct sip_strans **stp, struct mbuf **mbp, struct sip *sip,
+ const struct sip_msg *msg, bool rec_route, uint16_t scode,
+ const char *reason, const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, fmt);
+ err = vreplyf(stp, mbp, true, sip, msg, rec_route, scode, reason,
+ fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Reply using Server Transaction
+ *
+ * @param stp Pointer to allocated SIP Server Transaction (optional)
+ * @param sip SIP Stack instance
+ * @param msg Incoming SIP message
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_treply(struct sip_strans **stp, struct sip *sip,
+ const struct sip_msg *msg, uint16_t scode, const char *reason)
+{
+ return sip_treplyf(stp, NULL, sip, msg, false, scode, reason, NULL);
+}
+
+
+/**
+ * Stateless formatted reply
+ *
+ * @param sip SIP Stack instance
+ * @param msg Incoming SIP message
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ * @param fmt Additional formatted SIP headers and body, otherwise NULL
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_replyf(struct sip *sip, const struct sip_msg *msg, uint16_t scode,
+ const char *reason, const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, fmt);
+ err = vreplyf(NULL, NULL, false, sip, msg, false, scode, reason,
+ fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Stateless reply
+ *
+ * @param sip SIP Stack instance
+ * @param msg Incoming SIP message
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_reply(struct sip *sip, const struct sip_msg *msg, uint16_t scode,
+ const char *reason)
+{
+ return sip_replyf(sip, msg, scode, reason, NULL);
+}
+
+
+/**
+ * Get the reply address from a SIP message
+ *
+ * @param addr Network address, set on return
+ * @param msg SIP message
+ * @param rport Rport value
+ */
+void sip_reply_addr(struct sa *addr, const struct sip_msg *msg, bool rport)
+{
+ uint16_t port;
+ struct pl pl;
+
+ if (!addr || !msg)
+ return;
+
+ port = sa_port(&msg->via.addr);
+ *addr = msg->src;
+
+ switch (msg->tp) {
+
+ case SIP_TRANSP_UDP:
+ if (!msg_param_decode(&msg->via.params, "maddr", &pl)) {
+ (void)sa_set(addr, &pl,sip_transp_port(msg->tp, port));
+ break;
+ }
+
+ if (rport)
+ break;
+
+ /*@fallthrough@*/
+
+ case SIP_TRANSP_TCP:
+ case SIP_TRANSP_TLS:
+ sa_set_port(addr, sip_transp_port(msg->tp, port));
+ break;
+
+ default:
+ break;
+ }
+}
diff --git a/src/sip/request.c b/src/sip/request.c
new file mode 100644
index 0000000..aca2935
--- /dev/null
+++ b/src/sip/request.c
@@ -0,0 +1,936 @@
+/**
+ * @file sip/request.c SIP Request
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_fmt.h>
+#include <re_dns.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+struct sip_request {
+ struct le le;
+ struct list cachel;
+ struct list addrl;
+ struct list srvl;
+ struct sip_request **reqp;
+ struct sip_ctrans *ct;
+ struct dns_query *dnsq;
+ struct dns_query *dnsq2;
+ struct sip *sip;
+ char *met;
+ char *uri;
+ char *host;
+ struct mbuf *mb;
+ sip_send_h *sendh;
+ sip_resp_h *resph;
+ void *arg;
+ size_t sortkey;
+ enum sip_transp tp;
+ bool tp_selected;
+ bool stateful;
+ bool canceled;
+ bool provrecv;
+ uint16_t port;
+};
+
+
+static int request_next(struct sip_request *req);
+static bool rr_append_handler(struct dnsrr *rr, void *arg);
+static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg);
+static int srv_lookup(struct sip_request *req, const char *domain);
+static int addr_lookup(struct sip_request *req, const char *name);
+
+
+static int str_ldup(char **dst, const char *src, int len)
+{
+ struct pl pl;
+
+ pl.p = src;
+ pl.l = len < 0 ? str_len(src) : (size_t)len;
+
+ return pl_strdup(dst, &pl);
+}
+
+
+static void destructor(void *arg)
+{
+ struct sip_request *req = arg;
+
+ if (req->reqp && req->stateful) {
+ /* user does deref before request has completed */
+ *req->reqp = NULL;
+ req->reqp = NULL;
+ req->sendh = NULL;
+ req->resph = NULL;
+ sip_request_cancel(mem_ref(req));
+ return;
+ }
+
+ list_flush(&req->cachel);
+ list_flush(&req->addrl);
+ list_flush(&req->srvl);
+ list_unlink(&req->le);
+ mem_deref(req->dnsq);
+ mem_deref(req->dnsq2);
+ mem_deref(req->ct);
+ mem_deref(req->met);
+ mem_deref(req->uri);
+ mem_deref(req->host);
+ mem_deref(req->mb);
+}
+
+
+static void terminate(struct sip_request *req, int err,
+ const struct sip_msg *msg)
+{
+ if (req->reqp) {
+ *req->reqp = NULL;
+ req->reqp = NULL;
+ }
+
+ list_unlink(&req->le);
+ req->sendh = NULL;
+
+ if (req->resph) {
+ req->resph(err, msg, req->arg);
+ req->resph = NULL;
+ }
+}
+
+
+static bool close_handler(struct le *le, void *arg)
+{
+ struct sip_request *req = le->data;
+ (void)arg;
+
+ req->dnsq = mem_deref(req->dnsq);
+ req->dnsq2 = mem_deref(req->dnsq2);
+ req->ct = mem_deref(req->ct);
+
+ terminate(req, ECONNABORTED, NULL);
+ mem_deref(req);
+
+ return false;
+}
+
+
+static void response_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct sip_request *req = arg;
+
+ if (msg && msg->scode < 200) {
+ if (!req->provrecv) {
+ req->provrecv = true;
+ if (req->canceled)
+ (void)sip_ctrans_cancel(req->ct);
+ }
+
+ if (req->resph)
+ req->resph(err, msg, req->arg);
+
+ return;
+ }
+
+ req->ct = NULL;
+
+ if (!req->canceled && (err || msg->scode == 503) &&
+ (req->addrl.head || req->srvl.head)) {
+
+ err = request_next(req);
+ if (!err)
+ return;
+ }
+
+ terminate(req, err, msg);
+ mem_deref(req);
+}
+
+
+static int request(struct sip_request *req, enum sip_transp tp,
+ const struct sa *dst)
+{
+ struct mbuf *mb = NULL;
+ char *branch = NULL;
+ int err = ENOMEM;
+ struct sa laddr;
+
+ req->provrecv = false;
+
+ branch = mem_alloc(24, NULL);
+ mb = mbuf_alloc(1024);
+
+ if (!branch || !mb)
+ goto out;
+
+ (void)re_snprintf(branch, 24, "z9hG4bK%016llx", rand_u64());
+
+ err = sip_transp_laddr(req->sip, &laddr, tp, dst);
+ if (err)
+ goto out;
+
+ err = mbuf_printf(mb, "%s %s SIP/2.0\r\n", req->met, req->uri);
+ err |= mbuf_printf(mb, "Via: SIP/2.0/%s %J;branch=%s;rport\r\n",
+ sip_transp_name(tp), &laddr, branch);
+ err |= req->sendh ? req->sendh(tp, &laddr, dst, mb, req->arg) : 0;
+ err |= mbuf_write_mem(mb, mbuf_buf(req->mb), mbuf_get_left(req->mb));
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ if (!req->stateful)
+ err = sip_send(req->sip, NULL, tp, dst, mb);
+ else
+ err = sip_ctrans_request(&req->ct, req->sip, tp, dst, req->met,
+ branch, mb, response_handler, req);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(branch);
+ mem_deref(mb);
+
+ return err;
+}
+
+
+static int request_next(struct sip_request *req)
+{
+ struct dnsrr *rr;
+ struct sa dst;
+ int err;
+
+ again:
+ rr = list_ledata(req->addrl.head);
+ if (!rr) {
+ rr = list_ledata(req->srvl.head);
+ if (!rr)
+ return ENOENT;
+
+ req->port = rr->rdata.srv.port;
+
+ dns_rrlist_apply2(&req->cachel, rr->rdata.srv.target,
+ DNS_TYPE_A, DNS_TYPE_AAAA, DNS_CLASS_IN,
+ true, rr_append_handler, &req->addrl);
+
+ list_unlink(&rr->le);
+
+ if (req->addrl.head) {
+ dns_rrlist_sort_addr(&req->addrl, req->sortkey);
+ mem_deref(rr);
+ goto again;
+ }
+
+ err = addr_lookup(req, rr->rdata.srv.target);
+ mem_deref(rr);
+
+ return err;
+ }
+
+ switch (rr->type) {
+
+ case DNS_TYPE_A:
+ sa_set_in(&dst, rr->rdata.a.addr, req->port);
+ break;
+
+ case DNS_TYPE_AAAA:
+ sa_set_in6(&dst, rr->rdata.aaaa.addr, req->port);
+ break;
+
+ default:
+ return EINVAL;
+ }
+
+ list_unlink(&rr->le);
+ mem_deref(rr);
+
+ err = request(req, req->tp, &dst);
+ if (err) {
+ if (req->addrl.head || req->srvl.head)
+ goto again;
+ }
+ else if (!req->stateful) {
+ req->resph = NULL;
+ terminate(req, 0, NULL);
+ mem_deref(req);
+ }
+
+ return err;
+}
+
+
+static bool transp_next(struct sip *sip, enum sip_transp *tp)
+{
+ enum sip_transp i;
+
+ for (i=(enum sip_transp)(*tp+1); i<SIP_TRANSPC; i++) {
+
+ if (!sip_transp_supported(sip, i, AF_UNSPEC))
+ continue;
+
+ *tp = i;
+ return true;
+ }
+
+ return false;
+}
+
+
+static bool transp_next_srv(struct sip *sip, enum sip_transp *tp)
+{
+ enum sip_transp i;
+
+ for (i=(enum sip_transp)(*tp-1); i>SIP_TRANSP_NONE; i--) {
+
+ if (!sip_transp_supported(sip, i, AF_UNSPEC))
+ continue;
+
+ *tp = i;
+ return true;
+ }
+
+ return false;
+}
+
+
+static bool rr_append_handler(struct dnsrr *rr, void *arg)
+{
+ struct list *lst = arg;
+
+ switch (rr->type) {
+
+ case DNS_TYPE_A:
+ case DNS_TYPE_AAAA:
+ case DNS_TYPE_SRV:
+ if (rr->le.list)
+ break;
+
+ list_append(lst, &rr->le, mem_ref(rr));
+ break;
+ }
+
+ return false;
+}
+
+
+static bool rr_cache_handler(struct dnsrr *rr, void *arg)
+{
+ struct sip_request *req = arg;
+
+ switch (rr->type) {
+
+ case DNS_TYPE_A:
+ if (!sip_transp_supported(req->sip, req->tp, AF_INET))
+ break;
+
+ list_unlink(&rr->le_priv);
+ list_append(&req->cachel, &rr->le_priv, rr);
+ break;
+
+#ifdef HAVE_INET6
+ case DNS_TYPE_AAAA:
+ if (!sip_transp_supported(req->sip, req->tp, AF_INET6))
+ break;
+
+ list_unlink(&rr->le_priv);
+ list_append(&req->cachel, &rr->le_priv, rr);
+ break;
+#endif
+
+ case DNS_TYPE_CNAME:
+ list_unlink(&rr->le_priv);
+ list_append(&req->cachel, &rr->le_priv, rr);
+ break;
+ }
+
+ return false;
+}
+
+
+static bool rr_naptr_handler(struct dnsrr *rr, void *arg)
+{
+ struct sip_request *req = arg;
+ enum sip_transp tp;
+
+ if (rr->type != DNS_TYPE_NAPTR)
+ return false;
+
+ if (!str_casecmp(rr->rdata.naptr.services, "SIP+D2U"))
+ tp = SIP_TRANSP_UDP;
+ else if (!str_casecmp(rr->rdata.naptr.services, "SIP+D2T"))
+ tp = SIP_TRANSP_TCP;
+ else if (!str_casecmp(rr->rdata.naptr.services, "SIPS+D2T"))
+ tp = SIP_TRANSP_TLS;
+ else
+ return false;
+
+ if (!sip_transp_supported(req->sip, tp, AF_UNSPEC))
+ return false;
+
+ req->tp = tp;
+ req->tp_selected = true;
+
+ return true;
+}
+
+
+static void naptr_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg)
+{
+ struct sip_request *req = arg;
+ struct dnsrr *rr;
+ (void)hdr;
+ (void)authl;
+
+ dns_rrlist_sort(ansl, DNS_TYPE_NAPTR, req->sortkey);
+
+ rr = dns_rrlist_apply(ansl, NULL, DNS_TYPE_NAPTR, DNS_CLASS_IN, false,
+ rr_naptr_handler, req);
+ if (!rr) {
+ req->tp = SIP_TRANSPC;
+ if (!transp_next_srv(req->sip, &req->tp)) {
+ err = EPROTONOSUPPORT;
+ goto fail;
+ }
+
+ err = srv_lookup(req, req->host);
+ if (err)
+ goto fail;
+
+ return;
+ }
+
+ dns_rrlist_apply(addl, rr->rdata.naptr.replace, DNS_TYPE_SRV,
+ DNS_CLASS_IN, true, rr_append_handler, &req->srvl);
+
+ if (!req->srvl.head) {
+ err = dnsc_query(&req->dnsq, req->sip->dnsc,
+ rr->rdata.naptr.replace, DNS_TYPE_SRV,
+ DNS_CLASS_IN, true, srv_handler, req);
+ if (err)
+ goto fail;
+
+ return;
+ }
+
+ dns_rrlist_sort(&req->srvl, DNS_TYPE_SRV, req->sortkey);
+
+ dns_rrlist_apply(addl, NULL, DNS_QTYPE_ANY, DNS_CLASS_IN, false,
+ rr_cache_handler, req);
+
+ err = request_next(req);
+ if (err)
+ goto fail;
+
+ return;
+
+ fail:
+ terminate(req, err, NULL);
+ mem_deref(req);
+}
+
+
+static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg)
+{
+ struct sip_request *req = arg;
+ (void)hdr;
+ (void)authl;
+
+ dns_rrlist_apply(ansl, NULL, DNS_TYPE_SRV, DNS_CLASS_IN, false,
+ rr_append_handler, &req->srvl);
+
+ if (!req->srvl.head) {
+ if (!req->tp_selected) {
+ if (transp_next_srv(req->sip, &req->tp)) {
+
+ err = srv_lookup(req, req->host);
+ if (err)
+ goto fail;
+
+ return;
+ }
+
+ req->tp = SIP_TRANSP_NONE;
+ if (!transp_next(req->sip, &req->tp)) {
+ err = EPROTONOSUPPORT;
+ goto fail;
+ }
+ }
+
+ req->port = sip_transp_port(req->tp, 0);
+
+ err = addr_lookup(req, req->host);
+ if (err)
+ goto fail;
+
+ return;
+ }
+
+ dns_rrlist_sort(&req->srvl, DNS_TYPE_SRV, req->sortkey);
+
+ dns_rrlist_apply(addl, NULL, DNS_QTYPE_ANY, DNS_CLASS_IN, false,
+ rr_cache_handler, req);
+
+ err = request_next(req);
+ if (err)
+ goto fail;
+
+ return;
+
+ fail:
+ terminate(req, err, NULL);
+ mem_deref(req);
+}
+
+
+static void addr_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg)
+{
+ struct sip_request *req = arg;
+ (void)hdr;
+ (void)authl;
+ (void)addl;
+
+ dns_rrlist_apply2(ansl, NULL, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_CLASS_IN,
+ false, rr_append_handler, &req->addrl);
+
+ /* wait for other (A/AAAA) query to complete */
+ if (req->dnsq || req->dnsq2)
+ return;
+
+ if (!req->addrl.head && !req->srvl.head) {
+ err = err ? err : EDESTADDRREQ;
+ goto fail;
+ }
+
+ dns_rrlist_sort_addr(&req->addrl, req->sortkey);
+
+ err = request_next(req);
+ if (err)
+ goto fail;
+
+ return;
+
+ fail:
+ terminate(req, err, NULL);
+ mem_deref(req);
+}
+
+
+static int srv_lookup(struct sip_request *req, const char *domain)
+{
+ char name[256];
+
+ if (re_snprintf(name, sizeof(name), "%s.%s",
+ sip_transp_srvid(req->tp), domain) < 0)
+ return ENOMEM;
+
+ return dnsc_query(&req->dnsq, req->sip->dnsc, name, DNS_TYPE_SRV,
+ DNS_CLASS_IN, true, srv_handler, req);
+}
+
+
+static int addr_lookup(struct sip_request *req, const char *name)
+{
+ int err;
+
+ if (sip_transp_supported(req->sip, req->tp, AF_INET)) {
+
+ err = dnsc_query(&req->dnsq, req->sip->dnsc, name,
+ DNS_TYPE_A, DNS_CLASS_IN, true,
+ addr_handler, req);
+ if (err)
+ return err;
+ }
+
+#ifdef HAVE_INET6
+ if (sip_transp_supported(req->sip, req->tp, AF_INET6)) {
+
+ err = dnsc_query(&req->dnsq2, req->sip->dnsc, name,
+ DNS_TYPE_AAAA, DNS_CLASS_IN, true,
+ addr_handler, req);
+ if (err)
+ return err;
+ }
+#endif
+
+ if (!req->dnsq && !req->dnsq2)
+ return EPROTONOSUPPORT;
+
+ return 0;
+}
+
+
+/**
+ * Send a SIP request
+ *
+ * @param reqp Pointer to allocated SIP request object
+ * @param sip SIP Stack
+ * @param stateful Stateful client transaction
+ * @param met SIP Method string
+ * @param metl Length of SIP Method string
+ * @param uri Request URI
+ * @param uril Length of Request URI string
+ * @param route Next hop route URI
+ * @param mb Buffer containing SIP request
+ * @param sortkey Key for DNS record sorting
+ * @param sendh Send handler
+ * @param resph Response handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_request(struct sip_request **reqp, struct sip *sip, bool stateful,
+ const char *met, int metl, const char *uri, int uril,
+ const struct uri *route, struct mbuf *mb, size_t sortkey,
+ sip_send_h *sendh, sip_resp_h *resph, void *arg)
+{
+ struct sip_request *req;
+ struct sa dst;
+ struct pl pl;
+ int err;
+
+ if (!sip || !met || !uri || !route || !mb)
+ return EINVAL;
+
+ if (pl_strcasecmp(&route->scheme, "sip"))
+ return ENOSYS;
+
+ req = mem_zalloc(sizeof(*req), destructor);
+ if (!req)
+ return ENOMEM;
+
+ list_append(&sip->reql, &req->le, req);
+
+ err = str_ldup(&req->met, met, metl);
+ if (err)
+ goto out;
+
+ err = str_ldup(&req->uri, uri, uril);
+ if (err)
+ goto out;
+
+ if (msg_param_decode(&route->params, "maddr", &pl))
+ pl = route->host;
+
+ err = pl_strdup(&req->host, &pl);
+ if (err)
+ goto out;
+
+ req->stateful = stateful;
+ req->sortkey = sortkey;
+ req->mb = mem_ref(mb);
+ req->sip = sip;
+ req->sendh = sendh;
+ req->resph = resph;
+ req->arg = arg;
+
+ if (!msg_param_decode(&route->params, "transport", &pl)) {
+
+ if (!pl_strcasecmp(&pl, "udp"))
+ req->tp = SIP_TRANSP_UDP;
+ else if (!pl_strcasecmp(&pl, "tcp"))
+ req->tp = SIP_TRANSP_TCP;
+ else if (!pl_strcasecmp(&pl, "tls"))
+ req->tp = SIP_TRANSP_TLS;
+ else {
+ err = EPROTONOSUPPORT;
+ goto out;
+ }
+
+ if (!sip_transp_supported(sip, req->tp, AF_UNSPEC)) {
+ err = EPROTONOSUPPORT;
+ goto out;
+ }
+
+ req->tp_selected = true;
+ }
+ else {
+ req->tp = SIP_TRANSP_NONE;
+ if (!transp_next(sip, &req->tp)) {
+ err = EPROTONOSUPPORT;
+ goto out;
+ }
+
+ req->tp_selected = false;
+ }
+
+ if (!sa_set_str(&dst, req->host,
+ sip_transp_port(req->tp, route->port))) {
+
+ err = request(req, req->tp, &dst);
+ if (!req->stateful) {
+ mem_deref(req);
+ return err;
+ }
+ }
+ else if (route->port) {
+
+ req->port = sip_transp_port(req->tp, route->port);
+ err = addr_lookup(req, req->host);
+ }
+ else if (req->tp_selected) {
+
+ err = srv_lookup(req, req->host);
+ }
+ else {
+ err = dnsc_query(&req->dnsq, sip->dnsc, req->host,
+ DNS_TYPE_NAPTR, DNS_CLASS_IN, true,
+ naptr_handler, req);
+ }
+
+ out:
+ if (err)
+ mem_deref(req);
+ else if (reqp) {
+ req->reqp = reqp;
+ *reqp = req;
+ }
+
+ return err;
+}
+
+
+/**
+ * Send a SIP request with formatted arguments
+ *
+ * @param reqp Pointer to allocated SIP request object
+ * @param sip SIP Stack
+ * @param stateful Stateful client transaction
+ * @param met Null-terminated SIP Method string
+ * @param uri Null-terminated Request URI string
+ * @param route Next hop route URI (optional)
+ * @param auth SIP authentication state
+ * @param sendh Send handler
+ * @param resph Response handler
+ * @param arg Handler argument
+ * @param fmt Formatted SIP headers and body
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_requestf(struct sip_request **reqp, struct sip *sip, bool stateful,
+ const char *met, const char *uri, const struct uri *route,
+ struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph,
+ void *arg, const char *fmt, ...)
+{
+ struct uri lroute;
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ if (!sip || !met || !uri || !fmt)
+ return EINVAL;
+
+ if (!route) {
+ struct pl uripl;
+
+ pl_set_str(&uripl, uri);
+
+ err = uri_decode(&lroute, &uripl);
+ if (err)
+ return err;
+
+ route = &lroute;
+ }
+
+ mb = mbuf_alloc(2048);
+ if (!mb)
+ return ENOMEM;
+
+ err = mbuf_write_str(mb, "Max-Forwards: 70\r\n");
+
+ if (auth)
+ err |= sip_auth_encode(mb, auth, met, uri);
+
+ if (err)
+ goto out;
+
+ va_start(ap, fmt);
+ err = mbuf_vprintf(mb, fmt, ap);
+ va_end(ap);
+
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ err = sip_request(reqp, sip, stateful, met, -1, uri, -1, route, mb,
+ (size_t)arg, sendh, resph, arg);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Send a SIP dialog request with formatted arguments
+ *
+ * @param reqp Pointer to allocated SIP request object
+ * @param sip SIP Stack
+ * @param stateful Stateful client transaction
+ * @param met Null-terminated SIP Method string
+ * @param dlg SIP Dialog state
+ * @param cseq CSeq number
+ * @param auth SIP authentication state
+ * @param sendh Send handler
+ * @param resph Response handler
+ * @param arg Handler argument
+ * @param fmt Formatted SIP headers and body
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_drequestf(struct sip_request **reqp, struct sip *sip, bool stateful,
+ const char *met, struct sip_dialog *dlg, uint32_t cseq,
+ struct sip_auth *auth, sip_send_h *sendh, sip_resp_h *resph,
+ void *arg, const char *fmt, ...)
+{
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ if (!sip || !met || !dlg || !fmt)
+ return EINVAL;
+
+ mb = mbuf_alloc(2048);
+ if (!mb)
+ return ENOMEM;
+
+ err = mbuf_write_str(mb, "Max-Forwards: 70\r\n");
+
+ if (auth)
+ err |= sip_auth_encode(mb, auth, met, sip_dialog_uri(dlg));
+
+ err |= sip_dialog_encode(mb, dlg, cseq, met);
+
+ if (sip->software)
+ err |= mbuf_printf(mb, "User-Agent: %s\r\n", sip->software);
+
+ if (err)
+ goto out;
+
+ va_start(ap, fmt);
+ err = mbuf_vprintf(mb, fmt, ap);
+ va_end(ap);
+
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ err = sip_request(reqp, sip, stateful, met, -1, sip_dialog_uri(dlg),
+ -1, sip_dialog_route(dlg), mb, sip_dialog_hash(dlg),
+ sendh, resph, arg);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Cancel a pending SIP Request
+ *
+ * @param req SIP Request
+ */
+void sip_request_cancel(struct sip_request *req)
+{
+ if (!req || req->canceled)
+ return;
+
+ req->canceled = true;
+
+ if (!req->provrecv)
+ return;
+
+ (void)sip_ctrans_cancel(req->ct);
+}
+
+
+void sip_request_close(struct sip *sip)
+{
+ if (!sip)
+ return;
+
+ list_apply(&sip->reql, true, close_handler, NULL);
+}
+
+
+/**
+ * Check if a SIP request loops
+ *
+ * @param ls Loop state
+ * @param scode Status code from SIP response
+ *
+ * @return True if loops, otherwise false
+ */
+bool sip_request_loops(struct sip_loopstate *ls, uint16_t scode)
+{
+ bool loop = false;
+
+ if (!ls)
+ return false;
+
+ if (scode < 200) {
+ return false;
+ }
+ else if (scode < 300) {
+ ls->failc = 0;
+ }
+ else if (scode < 400) {
+ loop = (++ls->failc >= 16);
+ }
+ else {
+ switch (scode) {
+
+ default:
+ if (ls->last_scode == scode)
+ loop = true;
+ /*@fallthrough@*/
+ case 401:
+ case 407:
+ case 491:
+ if (++ls->failc >= 16)
+ loop = true;
+ break;
+ }
+ }
+
+ ls->last_scode = scode;
+
+ return loop;
+}
+
+
+/**
+ * Reset the loop state
+ *
+ * @param ls Loop state
+ */
+void sip_loopstate_reset(struct sip_loopstate *ls)
+{
+ if (!ls)
+ return;
+
+ ls->last_scode = 0;
+ ls->failc = 0;
+}
diff --git a/src/sip/sip.c b/src/sip/sip.c
new file mode 100644
index 0000000..651628c
--- /dev/null
+++ b/src/sip/sip.c
@@ -0,0 +1,238 @@
+/**
+ * @file sip.c SIP Core
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+static void destructor(void *arg)
+{
+ struct sip *sip = arg;
+
+ if (sip->closing) {
+ sip->closing = false;
+ mem_ref(sip);
+ if (sip->exith)
+ sip->exith(sip->arg);
+ return;
+ }
+
+ sip_request_close(sip);
+ sip_request_close(sip);
+
+ hash_flush(sip->ht_ctrans);
+ mem_deref(sip->ht_ctrans);
+
+ hash_flush(sip->ht_strans);
+ hash_clear(sip->ht_strans_mrg);
+ mem_deref(sip->ht_strans);
+ mem_deref(sip->ht_strans_mrg);
+
+ hash_flush(sip->ht_conn);
+ mem_deref(sip->ht_conn);
+
+ hash_flush(sip->ht_udpconn);
+ mem_deref(sip->ht_udpconn);
+
+ list_flush(&sip->transpl);
+ list_flush(&sip->lsnrl);
+
+ mem_deref(sip->software);
+ mem_deref(sip->dnsc);
+ mem_deref(sip->stun);
+}
+
+
+static void lsnr_destructor(void *arg)
+{
+ struct sip_lsnr *lsnr = arg;
+
+ if (lsnr->lsnrp)
+ *lsnr->lsnrp = NULL;
+
+ list_unlink(&lsnr->le);
+}
+
+
+/**
+ * Allocate a SIP stack instance
+ *
+ * @param sipp Pointer to allocated SIP stack
+ * @param dnsc DNS Client (optional)
+ * @param ctsz Size of client transactions hashtable (power of 2)
+ * @param stsz Size of server transactions hashtable (power of 2)
+ * @param tcsz Size of SIP transport hashtable (power of 2)
+ * @param software Software identifier
+ * @param exith SIP-stack exit handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_alloc(struct sip **sipp, struct dnsc *dnsc, uint32_t ctsz,
+ uint32_t stsz, uint32_t tcsz, const char *software,
+ sip_exit_h *exith, void *arg)
+{
+ struct sip *sip;
+ int err;
+
+ if (!sipp)
+ return EINVAL;
+
+ sip = mem_zalloc(sizeof(*sip), destructor);
+ if (!sip)
+ return ENOMEM;
+
+ err = sip_transp_init(sip, tcsz);
+ if (err)
+ goto out;
+
+ err = sip_ctrans_init(sip, ctsz);
+ if (err)
+ goto out;
+
+ err = sip_strans_init(sip, stsz);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&sip->ht_udpconn, tcsz);
+ if (err)
+ goto out;
+
+ err = stun_alloc(&sip->stun, NULL, NULL, NULL);
+ if (err)
+ goto out;
+
+ if (software) {
+ err = str_dup(&sip->software, software);
+ if (err)
+ goto out;
+ }
+
+ sip->dnsc = mem_ref(dnsc);
+ sip->exith = exith;
+ sip->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(sip);
+ else
+ *sipp = sip;
+
+ return err;
+}
+
+
+/**
+ * Close the SIP stack instance
+ *
+ * @param sip SIP stack instance
+ * @param force Don't wait for transactions to complete
+ */
+void sip_close(struct sip *sip, bool force)
+{
+ if (!sip)
+ return;
+
+ if (force) {
+ sip_request_close(sip);
+ sip_request_close(sip);
+ }
+ else if (!sip->closing) {
+ sip->closing = true;
+ mem_deref(sip);
+ }
+}
+
+
+/**
+ * Send a SIP message
+ *
+ * @param sip SIP stack instance
+ * @param sock Optional socket to send from
+ * @param tp SIP transport
+ * @param dst Destination network address
+ * @param mb Buffer containing SIP message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_send(struct sip *sip, void *sock, enum sip_transp tp,
+ const struct sa *dst, struct mbuf *mb)
+{
+ return sip_transp_send(NULL, sip, sock, tp, dst, mb, NULL, NULL);
+}
+
+
+/**
+ * Listen for incoming SIP Requests and SIP Responses
+ *
+ * @param lsnrp Pointer to allocated listener
+ * @param sip SIP stack instance
+ * @param req True for Request, false for Response
+ * @param msgh SIP message handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_listen(struct sip_lsnr **lsnrp, struct sip *sip, bool req,
+ sip_msg_h *msgh, void *arg)
+{
+ struct sip_lsnr *lsnr;
+
+ if (!sip || !msgh)
+ return EINVAL;
+
+ lsnr = mem_zalloc(sizeof(*lsnr), lsnr_destructor);
+ if (!lsnr)
+ return ENOMEM;
+
+ list_append(&sip->lsnrl, &lsnr->le, lsnr);
+
+ lsnr->msgh = msgh;
+ lsnr->arg = arg;
+ lsnr->req = req;
+
+ if (lsnrp) {
+ lsnr->lsnrp = lsnrp;
+ *lsnrp = lsnr;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Print debug information about the SIP stack
+ *
+ * @param pf Print function for debug output
+ * @param sip SIP stack instance
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_debug(struct re_printf *pf, const struct sip *sip)
+{
+ int err;
+
+ if (!sip)
+ return 0;
+
+ err = sip_transp_debug(pf, sip);
+ err |= sip_ctrans_debug(pf, sip);
+ err |= sip_strans_debug(pf, sip);
+
+ return err;
+}
diff --git a/src/sip/sip.h b/src/sip/sip.h
new file mode 100644
index 0000000..912c69b
--- /dev/null
+++ b/src/sip/sip.h
@@ -0,0 +1,101 @@
+/**
+ * @file sip.h SIP Private Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct sip {
+ struct list transpl;
+ struct list lsnrl;
+ struct list reql;
+ struct hash *ht_ctrans;
+ struct hash *ht_strans;
+ struct hash *ht_strans_mrg;
+ struct hash *ht_conn;
+ struct hash *ht_udpconn;
+ struct dnsc *dnsc;
+ struct stun *stun;
+ char *software;
+ sip_exit_h *exith;
+ void *arg;
+ bool closing;
+};
+
+
+struct sip_lsnr {
+ struct le le;
+ struct sip_lsnr **lsnrp;
+ sip_msg_h *msgh;
+ void *arg;
+ bool req;
+};
+
+
+struct sip_keepalive {
+ struct le le;
+ struct sip_keepalive **kap;
+ sip_keepalive_h *kah;
+ void *arg;
+};
+
+
+/* request */
+void sip_request_close(struct sip *sip);
+
+
+/* ctrans */
+struct sip_ctrans;
+
+int sip_ctrans_request(struct sip_ctrans **ctp, struct sip *sip,
+ enum sip_transp tp, const struct sa *dst, char *met,
+ char *branch, struct mbuf *mb, sip_resp_h *resph,
+ void *arg);
+int sip_ctrans_cancel(struct sip_ctrans *ct);
+int sip_ctrans_init(struct sip *sip, uint32_t sz);
+int sip_ctrans_debug(struct re_printf *pf, const struct sip *sip);
+
+
+/* strans */
+int sip_strans_init(struct sip *sip, uint32_t sz);
+int sip_strans_debug(struct re_printf *pf, const struct sip *sip);
+
+
+/* transp */
+struct sip_connqent;
+
+typedef void(sip_transp_h)(int err, void *arg);
+
+int sip_transp_init(struct sip *sip, uint32_t sz);
+int sip_transp_send(struct sip_connqent **qentp, struct sip *sip, void *sock,
+ enum sip_transp tp, const struct sa *dst, struct mbuf *mb,
+ sip_transp_h *transph, void *arg);
+bool sip_transp_supported(struct sip *sip, enum sip_transp tp, int af);
+const char *sip_transp_srvid(enum sip_transp tp);
+bool sip_transp_reliable(enum sip_transp tp);
+int sip_transp_debug(struct re_printf *pf, const struct sip *sip);
+
+
+/* auth */
+int sip_auth_encode(struct mbuf *mb, struct sip_auth *auth, const char *met,
+ const char *uri);
+
+
+/* dialog */
+int sip_dialog_encode(struct mbuf *mb, struct sip_dialog *dlg, uint32_t cseq,
+ const char *met);
+const char *sip_dialog_uri(const struct sip_dialog *dlg);
+const struct uri *sip_dialog_route(const struct sip_dialog *dlg);
+uint32_t sip_dialog_hash(const struct sip_dialog *dlg);
+
+
+/* keepalive */
+struct sip_conn;
+
+void sip_keepalive_signal(struct list *kal, int err);
+uint64_t sip_keepalive_wait(uint32_t interval);
+int sip_keepalive_tcp(struct sip_keepalive *ka, struct sip_conn *conn,
+ uint32_t interval);
+int sip_keepalive_udp(struct sip_keepalive *ka, struct sip *sip,
+ struct udp_sock *us, const struct sa *paddr,
+ uint32_t interval);
diff --git a/src/sip/strans.c b/src/sip/strans.c
new file mode 100644
index 0000000..13a875c
--- /dev/null
+++ b/src/sip/strans.c
@@ -0,0 +1,455 @@
+/**
+ * @file strans.c SIP Server Transaction
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum state {
+ TRYING,
+ PROCEEDING,
+ ACCEPTED,
+ COMPLETED,
+ CONFIRMED,
+};
+
+
+struct sip_strans {
+ struct le he;
+ struct le he_mrg;
+ struct tmr tmr;
+ struct tmr tmrg;
+ struct sa dst;
+ struct sip *sip;
+ struct sip_msg *msg;
+ struct mbuf *mb;
+ sip_cancel_h *cancelh;
+ void *arg;
+ enum state state;
+ uint32_t txc;
+ bool invite;
+};
+
+
+static void destructor(void *arg)
+{
+ struct sip_strans *st = arg;
+
+ hash_unlink(&st->he);
+ hash_unlink(&st->he_mrg);
+ tmr_cancel(&st->tmr);
+ tmr_cancel(&st->tmrg);
+ mem_deref(st->msg);
+ mem_deref(st->mb);
+}
+
+
+static bool strans_cmp(const struct sip_msg *msg1, const struct sip_msg *msg2)
+{
+ if (pl_cmp(&msg1->via.branch, &msg2->via.branch))
+ return false;
+
+ if (pl_cmp(&msg1->via.sentby, &msg2->via.sentby))
+ return false;
+
+ return true;
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+ struct sip_strans *st = le->data;
+ const struct sip_msg *msg = arg;
+
+ if (!strans_cmp(st->msg, msg))
+ return false;
+
+ if (pl_cmp(&st->msg->cseq.met, &msg->cseq.met))
+ return false;
+
+ return true;
+}
+
+
+static bool cmp_ack_handler(struct le *le, void *arg)
+{
+ struct sip_strans *st = le->data;
+ const struct sip_msg *msg = arg;
+
+ if (!strans_cmp(st->msg, msg))
+ return false;
+
+ if (pl_strcmp(&st->msg->cseq.met, "INVITE"))
+ return false;
+
+ return true;
+}
+
+
+static bool cmp_cancel_handler(struct le *le, void *arg)
+{
+ struct sip_strans *st = le->data;
+ const struct sip_msg *msg = arg;
+
+ if (!strans_cmp(st->msg, msg))
+ return false;
+
+ if (!pl_strcmp(&st->msg->cseq.met, "CANCEL"))
+ return false;
+
+ return true;
+}
+
+
+static bool cmp_merge_handler(struct le *le, void *arg)
+{
+ struct sip_strans *st = le->data;
+ const struct sip_msg *msg = arg;
+
+ if (pl_cmp(&st->msg->cseq.met, &msg->cseq.met))
+ return false;
+
+ if (st->msg->cseq.num != msg->cseq.num)
+ return false;
+
+ if (pl_cmp(&st->msg->callid, &msg->callid))
+ return false;
+
+ if (pl_cmp(&st->msg->from.tag, &msg->from.tag))
+ return false;
+
+ if (pl_cmp(&st->msg->ruri, &msg->ruri))
+ return false;
+
+ return true;
+}
+
+
+static void dummy_handler(void *arg)
+{
+ (void)arg;
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct sip_strans *st = arg;
+
+ mem_deref(st);
+}
+
+
+static void retransmit_handler(void *arg)
+{
+ struct sip_strans *st = arg;
+
+ (void)sip_send(st->sip, st->msg->sock, st->msg->tp, &st->dst,
+ st->mb);
+
+ st->txc++;
+ tmr_start(&st->tmrg, MIN(SIP_T1<<st->txc, SIP_T2), retransmit_handler,
+ st);
+}
+
+
+static bool ack_handler(struct sip *sip, const struct sip_msg *msg)
+{
+ struct sip_strans *st;
+
+ st = list_ledata(hash_lookup(sip->ht_strans,
+ hash_joaat_pl(&msg->via.branch),
+ cmp_ack_handler, (void *)msg));
+ if (!st)
+ return false;
+
+ switch (st->state) {
+
+ case ACCEPTED:
+ /* make sure ACKs for 2xx are passed to TU */
+ return false;
+
+ case COMPLETED:
+ if (sip_transp_reliable(st->msg->tp)) {
+ mem_deref(st);
+ break;
+ }
+
+ tmr_start(&st->tmr, SIP_T4, tmr_handler, st);
+ tmr_cancel(&st->tmrg);
+ st->state = CONFIRMED;
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+
+static bool cancel_handler(struct sip *sip, const struct sip_msg *msg)
+{
+ struct sip_strans *st;
+
+ st = list_ledata(hash_lookup(sip->ht_strans,
+ hash_joaat_pl(&msg->via.branch),
+ cmp_cancel_handler, (void *)msg));
+ if (!st)
+ return false;
+
+ ((struct sip_msg *)msg)->tag = st->msg->tag;
+
+ (void)sip_reply(sip, msg, 200, "OK");
+
+ switch (st->state) {
+
+ case TRYING:
+ case PROCEEDING:
+ st->cancelh(st->arg);
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+
+static bool request_handler(const struct sip_msg *msg, void *arg)
+{
+ struct sip_strans *st;
+ struct sip *sip = arg;
+
+ if (!pl_strcmp(&msg->met, "ACK"))
+ return ack_handler(sip, msg);
+
+ st = list_ledata(hash_lookup(sip->ht_strans,
+ hash_joaat_pl(&msg->via.branch),
+ cmp_handler, (void *)msg));
+ if (st) {
+ switch (st->state) {
+
+ case PROCEEDING:
+ case COMPLETED:
+ (void)sip_send(st->sip, st->msg->sock, st->msg->tp,
+ &st->dst, st->mb);
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+ }
+ else if (!pl_isset(&msg->to.tag)) {
+
+ st = list_ledata(hash_lookup(sip->ht_strans_mrg,
+ hash_joaat_pl(&msg->callid),
+ cmp_merge_handler, (void *)msg));
+ if (st) {
+ (void)sip_reply(sip, msg, 482, "Loop Detected");
+ return true;
+ }
+ }
+
+ if (!pl_strcmp(&msg->met, "CANCEL"))
+ return cancel_handler(sip, msg);
+
+ return false;
+}
+
+
+/**
+ * Allocate a SIP Server Transaction
+ *
+ * @param stp Pointer to allocated SIP Server Transaction
+ * @param sip SIP Stack instance
+ * @param msg Incoming SIP message
+ * @param cancelh Cancel handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_strans_alloc(struct sip_strans **stp, struct sip *sip,
+ const struct sip_msg *msg, sip_cancel_h *cancelh,
+ void *arg)
+{
+ struct sip_strans *st;
+
+ if (!stp || !sip || !msg)
+ return EINVAL;
+
+ st = mem_zalloc(sizeof(*st), destructor);
+ if (!st)
+ return ENOMEM;
+
+ hash_append(sip->ht_strans, hash_joaat_pl(&msg->via.branch),
+ &st->he, st);
+
+ hash_append(sip->ht_strans_mrg, hash_joaat_pl(&msg->callid),
+ &st->he_mrg, st);
+
+ st->invite = !pl_strcmp(&msg->met, "INVITE");
+ st->msg = mem_ref((void *)msg);
+ st->state = TRYING;
+ st->cancelh = cancelh ? cancelh : dummy_handler;
+ st->arg = arg;
+ st->sip = sip;
+
+ *stp = st;
+
+ return 0;
+}
+
+
+/**
+ * Reply using a SIP Server Transaction
+ *
+ * @param stp Pointer to allocated SIP Server Transaction
+ * @param sip SIP Stack instance
+ * @param msg Incoming SIP message
+ * @param dst Destination network address
+ * @param scode Response status code
+ * @param mb Buffer containing SIP response
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_strans_reply(struct sip_strans **stp, struct sip *sip,
+ const struct sip_msg *msg, const struct sa *dst,
+ uint16_t scode, struct mbuf *mb)
+{
+ struct sip_strans *st = NULL;
+ int err;
+
+ if (!sip || !mb || !dst || (scode < 200 && !stp))
+ return EINVAL;
+
+ if (stp)
+ st = *stp;
+
+ if (!st) {
+ err = sip_strans_alloc(&st, sip, msg, NULL, NULL);
+ if (err)
+ return err;
+ }
+
+ mem_deref(st->mb);
+ st->mb = mem_ref(mb);
+ st->dst = *dst;
+
+ err = sip_send(sip, st->msg->sock, st->msg->tp, dst, mb);
+
+ if (stp)
+ *stp = (err || scode >= 200) ? NULL : st;
+
+ if (err) {
+ mem_deref(st);
+ return err;
+ }
+
+ if (st->invite) {
+ if (scode < 200) {
+ st->state = PROCEEDING;
+ }
+ else if (scode < 300) {
+ tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler, st);
+ st->state = ACCEPTED;
+ }
+ else {
+ tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler, st);
+ st->state = COMPLETED;
+
+ if (!sip_transp_reliable(st->msg->tp))
+ tmr_start(&st->tmrg, SIP_T1,
+ retransmit_handler, st);
+ }
+ }
+ else {
+ if (scode < 200) {
+ st->state = PROCEEDING;
+ }
+ else {
+ if (!sip_transp_reliable(st->msg->tp)) {
+ tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler,
+ st);
+ st->state = COMPLETED;
+ }
+ else {
+ mem_deref(st);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+int sip_strans_init(struct sip *sip, uint32_t sz)
+{
+ int err;
+
+ err = sip_listen(NULL, sip, true, request_handler, sip);
+ if (err)
+ return err;
+
+ err = hash_alloc(&sip->ht_strans_mrg, sz);
+ if (err)
+ return err;
+
+ return hash_alloc(&sip->ht_strans, sz);
+}
+
+
+static const char *statename(enum state state)
+{
+ switch (state) {
+
+ case TRYING: return "TRYING";
+ case PROCEEDING: return "PROCEEDING";
+ case ACCEPTED: return "ACCEPTED";
+ case COMPLETED: return "COMPLETED";
+ case CONFIRMED: return "CONFIRMED";
+ default: return "???";
+ }
+}
+
+
+static bool debug_handler(struct le *le, void *arg)
+{
+ struct sip_strans *st = le->data;
+ struct re_printf *pf = arg;
+
+ (void)re_hprintf(pf, " %-10r %-10s %2llus (%r)\n",
+ &st->msg->met,
+ statename(st->state),
+ tmr_get_expire(&st->tmr)/1000,
+ &st->msg->via.branch);
+
+ return false;
+}
+
+
+int sip_strans_debug(struct re_printf *pf, const struct sip *sip)
+{
+ int err;
+
+ err = re_hprintf(pf, "server transactions:\n");
+ hash_apply(sip->ht_strans, debug_handler, pf);
+
+ return err;
+}
diff --git a/src/sip/transp.c b/src/sip/transp.c
new file mode 100644
index 0000000..aed33bb
--- /dev/null
+++ b/src/sip/transp.c
@@ -0,0 +1,985 @@
+/**
+ * @file sip/transp.c SIP Transport
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_srtp.h>
+#include <re_tcp.h>
+#include <re_tls.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include "sip.h"
+
+
+enum {
+ TCP_ACCEPT_TIMEOUT = 32,
+ TCP_IDLE_TIMEOUT = 900,
+ TCP_KEEPALIVE_TIMEOUT = 10,
+ TCP_KEEPALIVE_INTVAL = 120,
+ TCP_BUFSIZE_MAX = 65536,
+};
+
+
+struct sip_transport {
+ struct le le;
+ struct sa laddr;
+ struct sip *sip;
+ struct tls *tls;
+ void *sock;
+ enum sip_transp tp;
+};
+
+
+struct sip_conn {
+ struct le he;
+ struct list ql;
+ struct list kal;
+ struct tmr tmr;
+ struct tmr tmr_ka;
+ struct sa laddr;
+ struct sa paddr;
+ struct tls_conn *sc;
+ struct tcp_conn *tc;
+ struct mbuf *mb;
+ struct sip *sip;
+ uint32_t ka_interval;
+ bool established;
+};
+
+
+struct sip_connqent {
+ struct le le;
+ struct mbuf *mb;
+ struct sip_connqent **qentp;
+ sip_transp_h *transph;
+ void *arg;
+};
+
+
+static uint8_t crlfcrlf[4] = {0x0d, 0x0a, 0x0d, 0x0a};
+
+
+static void internal_transport_handler(int err, void *arg)
+{
+ (void)err;
+ (void)arg;
+}
+
+
+static void transp_destructor(void *arg)
+{
+ struct sip_transport *transp = arg;
+
+ if (transp->tp == SIP_TRANSP_UDP)
+ udp_handler_set(transp->sock, NULL, NULL);
+
+ list_unlink(&transp->le);
+ mem_deref(transp->sock);
+ mem_deref(transp->tls);
+}
+
+
+static void conn_destructor(void *arg)
+{
+ struct sip_conn *conn = arg;
+
+ tmr_cancel(&conn->tmr_ka);
+ tmr_cancel(&conn->tmr);
+ list_flush(&conn->kal);
+ list_flush(&conn->ql);
+ hash_unlink(&conn->he);
+ mem_deref(conn->sc);
+ mem_deref(conn->tc);
+ mem_deref(conn->mb);
+}
+
+
+static void qent_destructor(void *arg)
+{
+ struct sip_connqent *qent = arg;
+
+ if (qent->qentp)
+ *qent->qentp = NULL;
+
+ list_unlink(&qent->le);
+ mem_deref(qent->mb);
+}
+
+
+static const struct sip_transport *transp_find(struct sip *sip,
+ enum sip_transp tp,
+ int af, const struct sa *dst)
+{
+ struct le *le;
+ (void)dst;
+
+ for (le = sip->transpl.head; le; le = le->next) {
+
+ const struct sip_transport *transp = le->data;
+
+ if (transp->tp != tp)
+ continue;
+
+ if (af != AF_UNSPEC && sa_af(&transp->laddr) != af)
+ continue;
+
+ return transp;
+ }
+
+ return NULL;
+}
+
+
+static struct sip_conn *conn_find(struct sip *sip, const struct sa *paddr,
+ bool secure)
+{
+ struct le *le;
+
+ le = list_head(hash_list(sip->ht_conn, sa_hash(paddr, SA_ALL)));
+
+ for (; le; le = le->next) {
+
+ struct sip_conn *conn = le->data;
+
+ if (!secure != (conn->sc == NULL))
+ continue;
+
+ if (!sa_cmp(&conn->paddr, paddr, SA_ALL))
+ continue;
+
+ return conn;
+ }
+
+ return NULL;
+}
+
+
+static void conn_close(struct sip_conn *conn, int err)
+{
+ struct le *le;
+
+ conn->sc = mem_deref(conn->sc);
+ conn->tc = mem_deref(conn->tc);
+ tmr_cancel(&conn->tmr_ka);
+ tmr_cancel(&conn->tmr);
+ hash_unlink(&conn->he);
+
+ le = list_head(&conn->ql);
+
+ while (le) {
+
+ struct sip_connqent *qent = le->data;
+ le = le->next;
+
+ if (qent->qentp) {
+ *qent->qentp = NULL;
+ qent->qentp = NULL;
+ }
+
+ qent->transph(err, qent->arg);
+ list_unlink(&qent->le);
+ mem_deref(qent);
+ }
+
+ sip_keepalive_signal(&conn->kal, err);
+}
+
+
+static void conn_tmr_handler(void *arg)
+{
+ struct sip_conn *conn = arg;
+
+ conn_close(conn, ETIMEDOUT);
+ mem_deref(conn);
+}
+
+
+static void conn_keepalive_handler(void *arg)
+{
+ struct sip_conn *conn = arg;
+ struct mbuf mb;
+ int err;
+
+ mb.buf = crlfcrlf;
+ mb.size = sizeof(crlfcrlf);
+ mb.pos = 0;
+ mb.end = 4;
+
+ err = tcp_send(conn->tc, &mb);
+ if (err) {
+ conn_close(conn, err);
+ mem_deref(conn);
+ return;
+ }
+
+ tmr_start(&conn->tmr, TCP_KEEPALIVE_TIMEOUT * 1000,
+ conn_tmr_handler, conn);
+ tmr_start(&conn->tmr_ka, sip_keepalive_wait(conn->ka_interval),
+ conn_keepalive_handler, conn);
+}
+
+
+static void sip_recv(struct sip *sip, const struct sip_msg *msg)
+{
+ struct le *le = sip->lsnrl.head;
+
+ while (le) {
+ struct sip_lsnr *lsnr = le->data;
+
+ le = le->next;
+
+ if (msg->req != lsnr->req)
+ continue;
+
+ if (lsnr->msgh(msg, lsnr->arg))
+ return;
+ }
+
+ if (msg->req) {
+ (void)re_fprintf(stderr, "unhandeled request from %J: %r %r\n",
+ &msg->src, &msg->met, &msg->ruri);
+
+ if (!pl_strcmp(&msg->met, "CANCEL"))
+ (void)sip_reply(sip, msg,
+ 481, "Transaction Does Not Exist");
+ else
+ (void)sip_reply(sip, msg,
+ 501, "Not Implemented");
+ }
+ else {
+ (void)re_fprintf(stderr, "unhandeled response from %J:"
+ " %u %r (%r)\n", &msg->src,
+ msg->scode, &msg->reason, &msg->cseq.met);
+ }
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct sip_transport *transp = arg;
+ struct stun_unknown_attr ua;
+ struct stun_msg *stun_msg;
+ struct sip_msg *msg;
+ int err;
+
+ if (mb->end <= 4)
+ return;
+
+ if (!stun_msg_decode(&stun_msg, mb, &ua)) {
+
+ if (stun_msg_method(stun_msg) == STUN_METHOD_BINDING) {
+
+ switch (stun_msg_class(stun_msg)) {
+
+ case STUN_CLASS_REQUEST:
+ (void)stun_reply(IPPROTO_UDP, transp->sock,
+ src, 0, stun_msg,
+ NULL, 0, false, 2,
+ STUN_ATTR_XOR_MAPPED_ADDR,
+ src,
+ STUN_ATTR_SOFTWARE,
+ transp->sip->software);
+ break;
+
+ default:
+ (void)stun_ctrans_recv(transp->sip->stun,
+ stun_msg, &ua);
+ break;
+ }
+ }
+
+ mem_deref(stun_msg);
+
+ return;
+ }
+
+ err = sip_msg_decode(&msg, mb);
+ if (err) {
+ (void)re_fprintf(stderr, "sip: msg decode err: %m\n", err);
+ return;
+ }
+
+ msg->sock = mem_ref(transp->sock);
+ msg->src = *src;
+ msg->dst = transp->laddr;
+ msg->tp = SIP_TRANSP_UDP;
+
+ sip_recv(transp->sip, msg);
+
+ mem_deref(msg);
+}
+
+
+static void tcp_recv_handler(struct mbuf *mb, void *arg)
+{
+ struct sip_conn *conn = arg;
+ size_t pos;
+ int err = 0;
+
+ if (conn->mb) {
+ pos = conn->mb->pos;
+
+ conn->mb->pos = conn->mb->end;
+
+ err = mbuf_write_mem(conn->mb, mbuf_buf(mb),mbuf_get_left(mb));
+ if (err)
+ goto out;
+
+ conn->mb->pos = pos;
+
+ if (mbuf_get_left(conn->mb) > TCP_BUFSIZE_MAX) {
+ err = EOVERFLOW;
+ goto out;
+ }
+ }
+ else {
+ conn->mb = mem_ref(mb);
+ }
+
+ for (;;) {
+ struct sip_msg *msg;
+ uint32_t clen;
+ size_t end;
+
+ if (mbuf_get_left(conn->mb) < 2)
+ break;
+
+ if (!memcmp(mbuf_buf(conn->mb), "\r\n", 2)) {
+
+ tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000,
+ conn_tmr_handler, conn);
+
+ conn->mb->pos += 2;
+
+ if (mbuf_get_left(conn->mb) >= 2 &&
+ !memcmp(mbuf_buf(conn->mb), "\r\n", 2)) {
+
+ struct mbuf mbr;
+
+ conn->mb->pos += 2;
+
+ mbr.buf = crlfcrlf;
+ mbr.size = sizeof(crlfcrlf);
+ mbr.pos = 0;
+ mbr.end = 2;
+
+ err = tcp_send(conn->tc, &mbr);
+ if (err)
+ break;
+ }
+
+ if (mbuf_get_left(conn->mb))
+ continue;
+
+ conn->mb = mem_deref(conn->mb);
+ break;
+ }
+
+ pos = conn->mb->pos;
+
+ err = sip_msg_decode(&msg, conn->mb);
+ if (err) {
+ if (err == ENODATA)
+ err = 0;
+ break;
+ }
+
+ if (!msg->clen.p) {
+ mem_deref(msg);
+ err = EBADMSG;
+ break;
+ }
+
+ clen = pl_u32(&msg->clen);
+
+ if (mbuf_get_left(conn->mb) < clen) {
+ conn->mb->pos = pos;
+ mem_deref(msg);
+ break;
+ }
+
+ tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000,
+ conn_tmr_handler, conn);
+
+ end = conn->mb->end;
+
+ msg->mb->end = msg->mb->pos + clen;
+ msg->sock = mem_ref(conn);
+ msg->src = conn->paddr;
+ msg->dst = conn->laddr;
+ msg->tp = conn->sc ? SIP_TRANSP_TLS : SIP_TRANSP_TCP;
+
+ sip_recv(conn->sip, msg);
+ mem_deref(msg);
+
+ if (end <= conn->mb->end) {
+ conn->mb = mem_deref(conn->mb);
+ break;
+ }
+
+ mb = mbuf_alloc(end - conn->mb->end);
+ if (!mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ (void)mbuf_write_mem(mb, &conn->mb->buf[conn->mb->end],
+ end - conn->mb->end);
+
+ mb->pos = 0;
+
+ mem_deref(conn->mb);
+ conn->mb = mb;
+ }
+
+ out:
+ if (err) {
+ conn_close(conn, err);
+ mem_deref(conn);
+ }
+}
+
+
+static void tcp_estab_handler(void *arg)
+{
+ struct sip_conn *conn = arg;
+ struct le *le;
+ int err;
+
+#ifdef WIN32
+ tcp_conn_local_get(conn->tc, &conn->laddr);
+#endif
+
+ conn->established = true;
+
+ le = list_head(&conn->ql);
+
+ while (le) {
+
+ struct sip_connqent *qent = le->data;
+ le = le->next;
+
+ if (qent->qentp) {
+ *qent->qentp = NULL;
+ qent->qentp = NULL;
+ }
+
+ err = tcp_send(conn->tc, qent->mb);
+ if (err)
+ qent->transph(err, qent->arg);
+
+ list_unlink(&qent->le);
+ mem_deref(qent);
+ }
+}
+
+
+static void tcp_close_handler(int err, void *arg)
+{
+ struct sip_conn *conn = arg;
+
+ conn_close(conn, err ? err : ECONNRESET);
+ mem_deref(conn);
+}
+
+
+static void tcp_connect_handler(const struct sa *paddr, void *arg)
+{
+ struct sip_transport *transp = arg;
+ struct sip_conn *conn;
+ int err;
+
+ conn = mem_zalloc(sizeof(*conn), conn_destructor);
+ if (!conn) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ hash_append(transp->sip->ht_conn, sa_hash(paddr, SA_ALL),
+ &conn->he, conn);
+
+ conn->paddr = *paddr;
+ conn->sip = transp->sip;
+
+ err = tcp_accept(&conn->tc, transp->sock, tcp_estab_handler,
+ tcp_recv_handler, tcp_close_handler, conn);
+ if (err)
+ goto out;
+
+ err = tcp_conn_local_get(conn->tc, &conn->laddr);
+ if (err)
+ goto out;
+
+#ifdef USE_TLS
+ if (transp->tls) {
+ err = tls_start_tcp(&conn->sc, transp->tls, conn->tc, 0);
+ if (err)
+ goto out;
+ }
+#endif
+
+ tmr_start(&conn->tmr, TCP_ACCEPT_TIMEOUT * 1000,
+ conn_tmr_handler, conn);
+
+ out:
+ if (err) {
+ tcp_reject(transp->sock);
+ mem_deref(conn);
+ }
+}
+
+
+static int conn_send(struct sip_connqent **qentp, struct sip *sip, bool secure,
+ const struct sa *dst, struct mbuf *mb,
+ sip_transp_h *transph, void *arg)
+{
+ struct sip_conn *conn, *new_conn = NULL;
+ struct sip_connqent *qent;
+ int err = 0;
+
+ conn = conn_find(sip, dst, secure);
+ if (conn) {
+ if (!conn->established)
+ goto enqueue;
+
+ return tcp_send(conn->tc, mb);
+ }
+
+ new_conn = conn = mem_zalloc(sizeof(*conn), conn_destructor);
+ if (!conn)
+ return ENOMEM;
+
+ hash_append(sip->ht_conn, sa_hash(dst, SA_ALL), &conn->he, conn);
+ conn->paddr = *dst;
+ conn->sip = sip;
+
+ err = tcp_connect(&conn->tc, dst, tcp_estab_handler, tcp_recv_handler,
+ tcp_close_handler, conn);
+ if (err)
+ goto out;
+
+ err = tcp_conn_local_get(conn->tc, &conn->laddr);
+ if (err)
+ goto out;
+
+#ifdef USE_TLS
+ if (secure) {
+ const struct sip_transport *transp;
+
+ transp = transp_find(sip, SIP_TRANSP_TLS, sa_af(dst), dst);
+ if (!transp || !transp->tls) {
+ err = EPROTONOSUPPORT;
+ goto out;
+ }
+
+ err = tls_start_tcp(&conn->sc, transp->tls, conn->tc, 0);
+ if (err)
+ goto out;
+ }
+#endif
+
+ tmr_start(&conn->tmr, TCP_IDLE_TIMEOUT * 1000, conn_tmr_handler, conn);
+
+ enqueue:
+ qent = mem_zalloc(sizeof(*qent), qent_destructor);
+ if (!qent) {
+ err = ENOMEM;
+ goto out;
+
+ }
+
+ list_append(&conn->ql, &qent->le, qent);
+ qent->mb = mem_ref(mb);
+ qent->transph = transph ? transph : internal_transport_handler;
+ qent->arg = arg;
+
+ if (qentp) {
+ qent->qentp = qentp;
+ *qentp = qent;
+ }
+
+ out:
+ if (err)
+ mem_deref(new_conn);
+
+ return err;
+}
+
+
+int sip_transp_init(struct sip *sip, uint32_t sz)
+{
+ return hash_alloc(&sip->ht_conn, sz);
+}
+
+
+/**
+ * Add a SIP transport
+ *
+ * @param sip SIP stack instance
+ * @param tp SIP Transport
+ * @param laddr Local network address
+ * @param ... Optional transport parameters such as TLS context
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sip_transp_add(struct sip *sip, enum sip_transp tp,
+ const struct sa *laddr, ...)
+{
+ struct sip_transport *transp;
+ struct tls *tls;
+ va_list ap;
+ int err;
+
+ if (!sip || !laddr || !sa_isset(laddr, SA_ADDR))
+ return EINVAL;
+
+ transp = mem_zalloc(sizeof(*transp), transp_destructor);
+ if (!transp)
+ return ENOMEM;
+
+ list_append(&sip->transpl, &transp->le, transp);
+ transp->sip = sip;
+ transp->tp = tp;
+
+ va_start(ap, laddr);
+
+ switch (tp) {
+
+ case SIP_TRANSP_UDP:
+ err = udp_listen((struct udp_sock **)&transp->sock, laddr,
+ udp_recv_handler, transp);
+ if (err)
+ break;
+
+ err = udp_local_get(transp->sock, &transp->laddr);
+ break;
+
+ case SIP_TRANSP_TLS:
+ tls = va_arg(ap, struct tls *);
+ if (!tls) {
+ err = EINVAL;
+ break;
+ }
+
+ transp->tls = mem_ref(tls);
+
+ /*@fallthrough@*/
+
+ case SIP_TRANSP_TCP:
+ err = tcp_listen((struct tcp_sock **)&transp->sock, laddr,
+ tcp_connect_handler, transp);
+ if (err)
+ break;
+
+ err = tcp_sock_local_get(transp->sock, &transp->laddr);
+ break;
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ va_end(ap);
+
+ if (err)
+ mem_deref(transp);
+
+ return err;
+}
+
+
+/**
+ * Flush all transports of a SIP stack instance
+ *
+ * @param sip SIP stack instance
+ */
+void sip_transp_flush(struct sip *sip)
+{
+ if (!sip)
+ return;
+
+ hash_flush(sip->ht_conn);
+ list_flush(&sip->transpl);
+}
+
+
+int sip_transp_send(struct sip_connqent **qentp, struct sip *sip, void *sock,
+ enum sip_transp tp, const struct sa *dst, struct mbuf *mb,
+ sip_transp_h *transph, void *arg)
+{
+ const struct sip_transport *transp;
+ struct sip_conn *conn;
+ bool secure = false;
+ int err;
+
+ if (!sip || !dst || !mb)
+ return EINVAL;
+
+ switch (tp) {
+
+ case SIP_TRANSP_UDP:
+ if (!sock) {
+ transp = transp_find(sip, tp, sa_af(dst), dst);
+ if (!transp)
+ return EPROTONOSUPPORT;
+
+ sock = transp->sock;
+ }
+
+ err = udp_send(sock, dst, mb);
+ break;
+
+ case SIP_TRANSP_TLS:
+ secure = true;
+ /*@fallthrough@*/
+
+ case SIP_TRANSP_TCP:
+ conn = sock;
+
+ if (conn && conn->tc)
+ err = tcp_send(conn->tc, mb);
+ else
+ err = conn_send(qentp, sip, secure, dst, mb,
+ transph, arg);
+ break;
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ return err;
+}
+
+
+int sip_transp_laddr(struct sip *sip, struct sa *laddr, enum sip_transp tp,
+ const struct sa *dst)
+{
+ const struct sip_transport *transp;
+
+ if (!sip || !laddr)
+ return EINVAL;
+
+ transp = transp_find(sip, tp, sa_af(dst), dst);
+ if (!transp)
+ return EPROTONOSUPPORT;
+
+ *laddr = transp->laddr;
+
+ return 0;
+}
+
+
+bool sip_transp_supported(struct sip *sip, enum sip_transp tp, int af)
+{
+ if (!sip)
+ return false;
+
+ return transp_find(sip, tp, af, NULL) != NULL;
+}
+
+
+/**
+ * Check if network address is part of SIP transports
+ *
+ * @param sip SIP stack instance
+ * @param tp SIP transport
+ * @param laddr Local network address to check
+ *
+ * @return True if part of SIP transports, otherwise false
+ */
+bool sip_transp_isladdr(const struct sip *sip, enum sip_transp tp,
+ const struct sa *laddr)
+{
+ struct le *le;
+
+ if (!sip || !laddr)
+ return false;
+
+ for (le=sip->transpl.head; le; le=le->next) {
+
+ const struct sip_transport *transp = le->data;
+
+ if (tp != SIP_TRANSP_NONE && transp->tp != tp)
+ continue;
+
+ if (!sa_cmp(&transp->laddr, laddr, SA_ALL))
+ continue;
+
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Get the name of a given SIP Transport
+ *
+ * @param tp SIP Transport
+ *
+ * @return Name of the corresponding SIP Transport
+ */
+const char *sip_transp_name(enum sip_transp tp)
+{
+ switch (tp) {
+
+ case SIP_TRANSP_UDP: return "UDP";
+ case SIP_TRANSP_TCP: return "TCP";
+ case SIP_TRANSP_TLS: return "TLS";
+ default: return "???";
+ }
+}
+
+
+const char *sip_transp_srvid(enum sip_transp tp)
+{
+ switch (tp) {
+
+ case SIP_TRANSP_UDP: return "_sip._udp";
+ case SIP_TRANSP_TCP: return "_sip._tcp";
+ case SIP_TRANSP_TLS: return "_sips._tcp";
+ default: return "???";
+ }
+}
+
+
+/**
+ * Get the transport parameters for a given SIP Transport
+ *
+ * @param tp SIP Transport
+ *
+ * @return Transport parameters of the corresponding SIP Transport
+ */
+const char *sip_transp_param(enum sip_transp tp)
+{
+ switch (tp) {
+
+ case SIP_TRANSP_UDP: return "";
+ case SIP_TRANSP_TCP: return ";transport=tcp";
+ case SIP_TRANSP_TLS: return ";transport=tls";
+ default: return "";
+ }
+}
+
+
+bool sip_transp_reliable(enum sip_transp tp)
+{
+ switch (tp) {
+
+ case SIP_TRANSP_UDP: return false;
+ case SIP_TRANSP_TCP: return true;
+ case SIP_TRANSP_TLS: return true;
+ default: return false;
+ }
+}
+
+
+/**
+ * Get the default port number for a given SIP Transport
+ *
+ * @param tp SIP Transport
+ * @param port Port number
+ *
+ * @return Corresponding port number
+ */
+uint16_t sip_transp_port(enum sip_transp tp, uint16_t port)
+{
+ if (port)
+ return port;
+
+ switch (tp) {
+
+ case SIP_TRANSP_UDP: return SIP_PORT;
+ case SIP_TRANSP_TCP: return SIP_PORT;
+ case SIP_TRANSP_TLS: return SIP_PORT_TLS;
+ default: return 0;
+ }
+}
+
+
+static bool debug_handler(struct le *le, void *arg)
+{
+ const struct sip_transport *transp = le->data;
+ struct re_printf *pf = arg;
+
+ (void)re_hprintf(pf, " %J (%s)\n",
+ &transp->laddr,
+ sip_transp_name(transp->tp));
+
+ return false;
+}
+
+
+int sip_transp_debug(struct re_printf *pf, const struct sip *sip)
+{
+ int err;
+
+ err = re_hprintf(pf, "transports:\n");
+ list_apply(&sip->transpl, true, debug_handler, pf);
+
+ return err;
+}
+
+
+/**
+ * Get the TCP Connection from a SIP Message
+ *
+ * @param msg SIP Message
+ *
+ * @return TCP Connection if reliable transport, otherwise NULL
+ */
+struct tcp_conn *sip_msg_tcpconn(const struct sip_msg *msg)
+{
+ if (!msg || !msg->sock)
+ return NULL;
+
+ switch (msg->tp) {
+
+ case SIP_TRANSP_TCP:
+ case SIP_TRANSP_TLS:
+ return ((struct sip_conn *)msg->sock)->tc;
+
+ default:
+ return NULL;
+ }
+}
+
+
+int sip_keepalive_tcp(struct sip_keepalive *ka, struct sip_conn *conn,
+ uint32_t interval)
+{
+ if (!ka || !conn)
+ return EINVAL;
+
+ if (!conn->tc || !conn->established)
+ return ENOTCONN;
+
+ list_append(&conn->kal, &ka->le, ka);
+
+ if (!tmr_isrunning(&conn->tmr_ka)) {
+
+ interval = MAX(interval ? interval : TCP_KEEPALIVE_INTVAL,
+ TCP_KEEPALIVE_TIMEOUT * 2);
+
+ conn->ka_interval = interval;
+
+ tmr_start(&conn->tmr_ka, sip_keepalive_wait(conn->ka_interval),
+ conn_keepalive_handler, conn);
+ }
+
+ return 0;
+}
diff --git a/src/sip/via.c b/src/sip/via.c
new file mode 100644
index 0000000..76e518a
--- /dev/null
+++ b/src/sip/via.c
@@ -0,0 +1,77 @@
+/**
+ * @file via.c SIP Via decode
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+
+
+static int decode_hostport(const struct pl *hostport, struct pl *host,
+ struct pl *port)
+{
+ /* Try IPv6 first */
+ if (!re_regex(hostport->p, hostport->l, "\\[[0-9a-f:]+\\][:]*[0-9]*",
+ host, NULL, port))
+ return 0;
+
+ /* Then non-IPv6 host */
+ return re_regex(hostport->p, hostport->l, "[^:]+[:]*[0-9]*",
+ host, NULL, port);
+}
+
+
+/**
+ * Decode a pointer-length string into a SIP Via header
+ *
+ * @param via SIP Via header
+ * @param pl Pointer-length string
+ *
+ * @return 0 for success, otherwise errorcode
+ */
+int sip_via_decode(struct sip_via *via, const struct pl *pl)
+{
+ struct pl transp, host, port;
+ int err;
+
+ if (!via || !pl)
+ return EINVAL;
+
+ err = re_regex(pl->p, pl->l,
+ "SIP[ \t\r\n]*/[ \t\r\n]*2.0[ \t\r\n]*/[ \t\r\n]*"
+ "[A-Z]+[ \t\r\n]*[^; \t\r\n]+[ \t\r\n]*[^]*",
+ NULL, NULL, NULL, NULL, &transp,
+ NULL, &via->sentby, NULL, &via->params);
+ if (err)
+ return err;
+
+ if (!pl_strcmp(&transp, "TCP"))
+ via->tp = SIP_TRANSP_TCP;
+ else if (!pl_strcmp(&transp, "TLS"))
+ via->tp = SIP_TRANSP_TLS;
+ else if (!pl_strcmp(&transp, "UDP"))
+ via->tp = SIP_TRANSP_UDP;
+ else
+ via->tp = SIP_TRANSP_NONE;
+
+ err = decode_hostport(&via->sentby, &host, &port);
+ if (err)
+ return err;
+
+ sa_init(&via->addr, AF_INET);
+
+ (void)sa_set(&via->addr, &host, 0);
+
+ if (pl_isset(&port))
+ sa_set_port(&via->addr, pl_u32(&port));
+
+ via->val = *pl;
+
+ return msg_param_decode(&via->params, "branch", &via->branch);
+}
diff --git a/src/sipevent/listen.c b/src/sipevent/listen.c
new file mode 100644
index 0000000..ab35d95
--- /dev/null
+++ b/src/sipevent/listen.c
@@ -0,0 +1,357 @@
+/**
+ * @file sipevent/listen.c SIP Event Listen
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipevent.h>
+#include "sipevent.h"
+
+
+struct subcmp {
+ const struct sipevent_event *evt;
+ const struct sip_msg *msg;
+};
+
+
+static void destructor(void *arg)
+{
+ struct sipevent_sock *sock = arg;
+
+ mem_deref(sock->lsnr);
+ hash_flush(sock->ht_not);
+ hash_flush(sock->ht_sub);
+ mem_deref(sock->ht_not);
+ mem_deref(sock->ht_sub);
+}
+
+
+static bool event_cmp(const struct sipevent_event *evt,
+ const char *event, const char *id,
+ int32_t refer_cseq)
+{
+ if (pl_strcmp(&evt->event, event))
+ return false;
+
+ if (!pl_isset(&evt->id) && !id)
+ return true;
+
+ if (!pl_isset(&evt->id))
+ return false;
+
+ if (!id) {
+ if (refer_cseq >= 0 && (int32_t)pl_u32(&evt->id) == refer_cseq)
+ return true;
+
+ return false;
+ }
+
+ if (pl_strcmp(&evt->id, id))
+ return false;
+
+ return true;
+}
+
+
+static bool not_cmp_handler(struct le *le, void *arg)
+{
+ const struct subcmp *cmp = arg;
+ struct sipnot *not = le->data;
+
+ return sip_dialog_cmp(not->dlg, cmp->msg) &&
+ event_cmp(cmp->evt, not->event, not->id, -1);
+}
+
+
+static bool sub_cmp_handler(struct le *le, void *arg)
+{
+ const struct subcmp *cmp = arg;
+ struct sipsub *sub = le->data;
+
+ return sip_dialog_cmp(sub->dlg, cmp->msg) &&
+ (!cmp->evt || event_cmp(cmp->evt, sub->event, sub->id,
+ sub->refer_cseq));
+}
+
+
+static bool sub_cmp_half_handler(struct le *le, void *arg)
+{
+ const struct subcmp *cmp = arg;
+ struct sipsub *sub = le->data;
+
+ return sip_dialog_cmp_half(sub->dlg, cmp->msg) &&
+ !sip_dialog_established(sub->dlg) &&
+ (!cmp->evt || event_cmp(cmp->evt, sub->event, sub->id,
+ sub->refer_cseq));
+}
+
+
+static struct sipnot *sipnot_find(struct sipevent_sock *sock,
+ const struct sip_msg *msg,
+ const struct sipevent_event *evt)
+{
+ struct subcmp cmp;
+
+ cmp.msg = msg;
+ cmp.evt = evt;
+
+ return list_ledata(hash_lookup(sock->ht_not,
+ hash_joaat_pl(&msg->callid),
+ not_cmp_handler, &cmp));
+}
+
+
+struct sipsub *sipsub_find(struct sipevent_sock *sock,
+ const struct sip_msg *msg,
+ const struct sipevent_event *evt, bool full)
+{
+ struct subcmp cmp;
+
+ cmp.msg = msg;
+ cmp.evt = evt;
+
+ return list_ledata(hash_lookup(sock->ht_sub,
+ hash_joaat_pl(&msg->callid), full ?
+ sub_cmp_handler : sub_cmp_half_handler,
+ &cmp));
+}
+
+
+static void notify_handler(struct sipevent_sock *sock,
+ const struct sip_msg *msg)
+{
+ struct sipevent_substate state;
+ struct sipevent_event event;
+ struct sip *sip = sock->sip;
+ const struct sip_hdr *hdr;
+ struct sipsub *sub;
+ uint32_t nrefs;
+ char m[256];
+ int err;
+
+ hdr = sip_msg_hdr(msg, SIP_HDR_EVENT);
+ if (!hdr || sipevent_event_decode(&event, &hdr->val)) {
+ (void)sip_reply(sip, msg, 489, "Bad Event");
+ return;
+ }
+
+ hdr = sip_msg_hdr(msg, SIP_HDR_SUBSCRIPTION_STATE);
+ if (!hdr || sipevent_substate_decode(&state, &hdr->val)) {
+ (void)sip_reply(sip, msg, 400,"Bad Subscription-State Header");
+ return;
+ }
+
+ sub = sipsub_find(sock, msg, &event, true);
+ if (!sub) {
+ sub = sipsub_find(sock, msg, &event, false);
+ if (!sub) {
+ (void)sip_reply(sip, msg,
+ 481, "Subscription Does Not Exist");
+ return;
+ }
+
+ if (sub->forkh) {
+
+ struct sipsub *fsub;
+
+ err = sub->forkh(&fsub, sub, msg, sub->arg);
+ if (err) {
+ (void)sip_reply(sip, msg, 500,
+ str_error(err, m, sizeof(m)));
+ return;
+ }
+
+ sub = fsub;
+ }
+ else {
+ err = sip_dialog_create(sub->dlg, msg);
+ if (err) {
+ (void)sip_reply(sip, msg, 500,
+ str_error(err, m, sizeof(m)));
+ return;
+ }
+ }
+ }
+ else {
+ if (!sip_dialog_rseq_valid(sub->dlg, msg)) {
+ (void)sip_reply(sip, msg, 500, "Bad Sequence");
+ return;
+ }
+
+ (void)sip_dialog_update(sub->dlg, msg);
+ }
+
+ if (sub->refer_cseq >= 0 && !sub->id && pl_isset(&event.id)) {
+
+ err = pl_strdup(&sub->id, &event.id);
+ if (err) {
+ (void)sip_treply(NULL, sip, msg, 500,
+ str_error(err, m, sizeof(m)));
+ return;
+ }
+ }
+
+ switch (state.state) {
+
+ case SIPEVENT_ACTIVE:
+ case SIPEVENT_PENDING:
+ if (!sub->termconf)
+ sub->subscribed = true;
+
+ if (!sub->terminated && !sub->termwait &&
+ pl_isset(&state.expires))
+ sipsub_reschedule(sub, pl_u32(&state.expires) * 900);
+ break;
+
+ case SIPEVENT_TERMINATED:
+ sub->subscribed = false;
+ sub->termconf = true;
+ break;
+ }
+
+ mem_ref(sub);
+ sub->notifyh(sip, msg, sub->arg);
+ nrefs = mem_nrefs(sub);
+ mem_deref(sub);
+
+ /* check if subscription was deref'd from notify handler */
+ if (nrefs == 1)
+ return;
+
+ if (state.state == SIPEVENT_TERMINATED) {
+
+ if (!sub->terminated) {
+ sub->termwait = false;
+ sipsub_terminate(sub, 0, msg, &state);
+ }
+ else if (sub->termwait) {
+ sub->termwait = false;
+ tmr_cancel(&sub->tmr);
+ mem_deref(sub);
+ }
+ }
+}
+
+
+static void subscribe_handler(struct sipevent_sock *sock,
+ const struct sip_msg *msg)
+{
+ struct sipevent_event event;
+ struct sip *sip = sock->sip;
+ const struct sip_hdr *hdr;
+ struct sipnot *not;
+ uint32_t expires;
+
+ hdr = sip_msg_hdr(msg, SIP_HDR_EVENT);
+ if (!hdr || sipevent_event_decode(&event, &hdr->val)) {
+ (void)sip_reply(sip, msg, 400, "Bad Event Header");
+ return;
+ }
+
+ not = sipnot_find(sock, msg, &event);
+ if (!not || not->terminated) {
+ (void)sip_reply(sip, msg, 481, "Subscription Does Not Exist");
+ return;
+ }
+
+ if (pl_isset(&msg->expires))
+ expires = pl_u32(&msg->expires);
+ else
+ expires = not->expires_dfl;
+
+ if (expires > 0 && expires < not->expires_min) {
+ (void)sip_replyf(sip, msg, 423, "Interval Too Brief",
+ "Min-Expires: %u\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n",
+ not->expires_min);
+ return;
+ }
+
+ if (!sip_dialog_rseq_valid(not->dlg, msg)) {
+ (void)sip_reply(sip, msg, 500, "Bad Sequence");
+ return;
+ }
+
+ (void)sip_dialog_update(not->dlg, msg);
+
+ sipnot_refresh(not, expires);
+
+ (void)sipnot_reply(not, msg, 200, "OK");
+
+ (void)sipnot_notify(not);
+}
+
+
+static bool request_handler(const struct sip_msg *msg, void *arg)
+{
+ struct sipevent_sock *sock = arg;
+
+ if (!pl_strcmp(&msg->met, "SUBSCRIBE")) {
+
+ if (pl_isset(&msg->to.tag)) {
+ subscribe_handler(sock, msg);
+ return true;
+ }
+
+ return sock->subh ? sock->subh(msg, sock->arg) : false;
+ }
+ else if (!pl_strcmp(&msg->met, "NOTIFY")) {
+
+ notify_handler(sock, msg);
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+
+int sipevent_listen(struct sipevent_sock **sockp, struct sip *sip,
+ uint32_t htsize_not, uint32_t htsize_sub,
+ sip_msg_h *subh, void *arg)
+{
+ struct sipevent_sock *sock;
+ int err;
+
+ if (!sockp || !sip || !htsize_not || !htsize_sub)
+ return EINVAL;
+
+ sock = mem_zalloc(sizeof(*sock), destructor);
+ if (!sock)
+ return ENOMEM;
+
+ err = sip_listen(&sock->lsnr, sip, true, request_handler, sock);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&sock->ht_not, htsize_not);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&sock->ht_sub, htsize_sub);
+ if (err)
+ goto out;
+
+ sock->sip = sip;
+ sock->subh = subh;
+ sock->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(sock);
+ else
+ *sockp = sock;
+
+ return err;
+}
diff --git a/src/sipevent/mod.mk b/src/sipevent/mod.mk
new file mode 100644
index 0000000..3426b8c
--- /dev/null
+++ b/src/sipevent/mod.mk
@@ -0,0 +1,10 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += sipevent/listen.c
+SRCS += sipevent/msg.c
+SRCS += sipevent/notify.c
+SRCS += sipevent/subscribe.c
diff --git a/src/sipevent/msg.c b/src/sipevent/msg.c
new file mode 100644
index 0000000..472c828
--- /dev/null
+++ b/src/sipevent/msg.c
@@ -0,0 +1,120 @@
+/**
+ * @file sipevent/msg.c SIP event messages
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_uri.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipevent.h>
+
+
+int sipevent_event_decode(struct sipevent_event *se, const struct pl *pl)
+{
+ struct pl param;
+ int err;
+
+ if (!se || !pl)
+ return EINVAL;
+
+ err = re_regex(pl->p, pl->l, "[^; \t\r\n]+[ \t\r\n]*[^]*",
+ &se->event, NULL, &se->params);
+ if (err)
+ return EBADMSG;
+
+ if (!msg_param_decode(&se->params, "id", ¶m))
+ se->id = param;
+ else
+ se->id = pl_null;
+
+ return 0;
+}
+
+
+int sipevent_substate_decode(struct sipevent_substate *ss, const struct pl *pl)
+{
+ struct pl state, param;
+ int err;
+
+ if (!ss || !pl)
+ return EINVAL;
+
+ err = re_regex(pl->p, pl->l, "[a-z]+[ \t\r\n]*[^]*",
+ &state, NULL, &ss->params);
+ if (err)
+ return EBADMSG;
+
+ if (!pl_strcasecmp(&state, "active"))
+ ss->state = SIPEVENT_ACTIVE;
+ else if (!pl_strcasecmp(&state, "pending"))
+ ss->state = SIPEVENT_PENDING;
+ else if (!pl_strcasecmp(&state, "terminated"))
+ ss->state = SIPEVENT_TERMINATED;
+ else
+ ss->state = -1;
+
+ if (!msg_param_decode(&ss->params, "reason", ¶m)) {
+
+ if (!pl_strcasecmp(¶m, "deactivated"))
+ ss->reason = SIPEVENT_DEACTIVATED;
+ else if (!pl_strcasecmp(¶m, "probation"))
+ ss->reason = SIPEVENT_PROBATION;
+ else if (!pl_strcasecmp(¶m, "rejected"))
+ ss->reason = SIPEVENT_REJECTED;
+ else if (!pl_strcasecmp(¶m, "timeout"))
+ ss->reason = SIPEVENT_TIMEOUT;
+ else if (!pl_strcasecmp(¶m, "giveup"))
+ ss->reason = SIPEVENT_GIVEUP;
+ else if (!pl_strcasecmp(¶m, "noresource"))
+ ss->reason = SIPEVENT_NORESOURCE;
+ else
+ ss->reason = -1;
+ }
+ else {
+ ss->reason = -1;
+ }
+
+ if (!msg_param_decode(&ss->params, "expires", ¶m))
+ ss->expires = param;
+ else
+ ss->expires = pl_null;
+
+ if (!msg_param_decode(&ss->params, "retry-after", ¶m))
+ ss->retry_after = param;
+ else
+ ss->retry_after = pl_null;
+
+ return 0;
+}
+
+
+const char *sipevent_substate_name(enum sipevent_subst state)
+{
+ switch (state) {
+
+ case SIPEVENT_ACTIVE: return "active";
+ case SIPEVENT_PENDING: return "pending";
+ case SIPEVENT_TERMINATED: return "terminated";
+ default: return "unknown";
+ }
+}
+
+
+const char *sipevent_reason_name(enum sipevent_reason reason)
+{
+ switch (reason) {
+
+ case SIPEVENT_DEACTIVATED: return "deactivated";
+ case SIPEVENT_PROBATION: return "probation";
+ case SIPEVENT_REJECTED: return "rejected";
+ case SIPEVENT_TIMEOUT: return "timeout";
+ case SIPEVENT_GIVEUP: return "giveup";
+ case SIPEVENT_NORESOURCE: return "noresource";
+ default: return "unknown";
+ }
+}
diff --git a/src/sipevent/notify.c b/src/sipevent/notify.c
new file mode 100644
index 0000000..7a7a87d
--- /dev/null
+++ b/src/sipevent/notify.c
@@ -0,0 +1,488 @@
+/**
+ * @file notify.c SIP Event Notify
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipevent.h>
+#include "sipevent.h"
+
+
+static int notify_request(struct sipnot *not, bool reset_ls);
+
+
+static void internal_close_handler(int err, const struct sip_msg *msg,
+ void *arg)
+{
+ (void)err;
+ (void)msg;
+ (void)arg;
+}
+
+
+static bool terminate(struct sipnot *not, enum sipevent_reason reason)
+{
+ not->terminated = true;
+ not->reason = reason;
+ not->closeh = internal_close_handler;
+
+ if (not->req) {
+ mem_ref(not);
+ return true;
+ }
+
+ if (not->subscribed && !notify_request(not, true)) {
+ mem_ref(not);
+ return true;
+ }
+
+ return false;
+}
+
+
+static void destructor(void *arg)
+{
+ struct sipnot *not = arg;
+
+ tmr_cancel(¬->tmr);
+
+ if (!not->terminated) {
+
+ if (terminate(not, SIPEVENT_DEACTIVATED))
+ return;
+ }
+
+ hash_unlink(¬->he);
+ mem_deref(not->req);
+ mem_deref(not->dlg);
+ mem_deref(not->auth);
+ mem_deref(not->mb);
+ mem_deref(not->event);
+ mem_deref(not->id);
+ mem_deref(not->cuser);
+ mem_deref(not->hdrs);
+ mem_deref(not->ctype);
+ mem_deref(not->sock);
+ mem_deref(not->sip);
+}
+
+
+static void sipnot_terminate(struct sipnot *not, int err,
+ const struct sip_msg *msg,
+ enum sipevent_reason reason)
+{
+ sipnot_close_h *closeh;
+ void *arg;
+
+ closeh = not->closeh;
+ arg = not->arg;
+
+ tmr_cancel(¬->tmr);
+ (void)terminate(not, reason);
+
+ closeh(err, msg, arg);
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct sipnot *not = arg;
+
+ if (not->terminated)
+ return;
+
+ sipnot_terminate(not, ETIMEDOUT, NULL, SIPEVENT_TIMEOUT);
+}
+
+
+void sipnot_refresh(struct sipnot *not, uint32_t expires)
+{
+ not->expires = min(expires, not->expires_max);
+
+ tmr_start(¬->tmr, not->expires * 1000, tmr_handler, not);
+}
+
+
+static void response_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct sipnot *not = arg;
+
+ if (err) {
+ if (err == ETIMEDOUT)
+ not->subscribed = false;
+ goto out;
+ }
+
+ if (sip_request_loops(¬->ls, msg->scode)) {
+ not->subscribed = false;
+ goto out;
+ }
+
+ if (msg->scode < 200) {
+ return;
+ }
+ else if (msg->scode < 300) {
+
+ (void)sip_dialog_update(not->dlg, msg);
+ }
+ else {
+ switch (msg->scode) {
+
+ case 401:
+ case 407:
+ err = sip_auth_authenticate(not->auth, msg);
+ if (err) {
+ err = (err == EAUTH) ? 0 : err;
+ break;
+ }
+
+ err = notify_request(not, false);
+ if (err)
+ break;
+
+ return;
+ }
+
+ not->subscribed = false;
+ }
+
+ out:
+ if (not->termsent) {
+ mem_deref(not);
+ }
+ else if (not->terminated) {
+ if (!not->subscribed || notify_request(not, true))
+ mem_deref(not);
+ }
+ else if (!not->subscribed) {
+ sipnot_terminate(not, err, msg, -1);
+ }
+ else if (not->notify_pending) {
+ (void)notify_request(not, true);
+ }
+}
+
+
+static int send_handler(enum sip_transp tp, const struct sa *src,
+ const struct sa *dst, struct mbuf *mb, void *arg)
+{
+ struct sip_contact contact;
+ struct sipnot *not = arg;
+ (void)dst;
+
+ sip_contact_set(&contact, not->cuser, src, tp);
+
+ return mbuf_printf(mb, "%H", sip_contact_print, &contact);
+}
+
+
+static int print_event(struct re_printf *pf, const struct sipnot *not)
+{
+ if (not->id)
+ return re_hprintf(pf, "%s;id=%s", not->event, not->id);
+ else
+ return re_hprintf(pf, "%s", not->event);
+}
+
+
+static int print_substate(struct re_printf *pf, const struct sipnot *not)
+{
+ int err;
+
+ if (not->terminated) {
+
+ err = re_hprintf(pf, "terminated;reason=%s",
+ sipevent_reason_name(not->reason));
+
+ if (not->retry_after)
+ err |= re_hprintf(pf, ";retry-after=%u",
+ not->retry_after);
+ }
+ else {
+ uint32_t expires;
+
+ expires = (uint32_t)(tmr_get_expire(¬->tmr) / 1000);
+
+ err = re_hprintf(pf, "%s;expires=%u",
+ sipevent_substate_name(not->substate),
+ expires);
+ }
+
+ return err;
+}
+
+
+static int print_content(struct re_printf *pf, const struct sipnot *not)
+{
+ if (!not->mb)
+ return re_hprintf(pf,
+ "Content-Length: 0\r\n"
+ "\r\n");
+ else
+ return re_hprintf(pf,
+ "Content-Type: %s\r\n"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ not->ctype,
+ mbuf_get_left(not->mb),
+ mbuf_buf(not->mb),
+ mbuf_get_left(not->mb));
+}
+
+
+static int notify_request(struct sipnot *not, bool reset_ls)
+{
+ if (reset_ls)
+ sip_loopstate_reset(¬->ls);
+
+ if (not->terminated)
+ not->termsent = true;
+
+ not->notify_pending = false;
+
+ return sip_drequestf(¬->req, not->sip, true, "NOTIFY",
+ not->dlg, 0, not->auth,
+ send_handler, response_handler, not,
+ "Event: %H\r\n"
+ "Subscription-State: %H\r\n"
+ "%s"
+ "%H",
+ print_event, not,
+ print_substate, not,
+ not->hdrs,
+ print_content, not);
+}
+
+
+int sipnot_notify(struct sipnot *not)
+{
+ if (not->expires == 0) {
+ return 0;
+ }
+
+ if (not->req) {
+ not->notify_pending = true;
+ return 0;
+ }
+
+ return notify_request(not, true);
+}
+
+
+int sipnot_reply(struct sipnot *not, const struct sip_msg *msg,
+ uint16_t scode, const char *reason)
+{
+ struct sip_contact contact;
+ uint32_t expires;
+
+ expires = (uint32_t)(tmr_get_expire(¬->tmr) / 1000);
+
+ sip_contact_set(&contact, not->cuser, &msg->dst, msg->tp);
+
+ return sip_treplyf(NULL, NULL, not->sip, msg, true, scode, reason,
+ "%H"
+ "Expires: %u\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n",
+ sip_contact_print, &contact,
+ expires);
+}
+
+
+int sipevent_accept(struct sipnot **notp, struct sipevent_sock *sock,
+ const struct sip_msg *msg, struct sip_dialog *dlg,
+ const struct sipevent_event *event,
+ uint16_t scode, const char *reason, uint32_t expires_min,
+ uint32_t expires_dfl, uint32_t expires_max,
+ const char *cuser, const char *ctype,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipnot_close_h *closeh, void *arg, const char *fmt, ...)
+{
+ struct sipnot *not;
+ uint32_t expires;
+ int err;
+
+ if (!notp || !sock || !msg || !scode || !reason || !expires_dfl ||
+ !expires_max || !cuser || !ctype || expires_dfl < expires_min)
+ return EINVAL;
+
+ not = mem_zalloc(sizeof(*not), destructor);
+ if (!not)
+ return ENOMEM;
+
+ if (!pl_strcmp(&msg->met, "REFER")) {
+
+ err = str_dup(¬->event, "refer");
+ if (err)
+ goto out;
+
+ err = re_sdprintf(¬->id, "%u", msg->cseq.num);
+ if (err)
+ goto out;
+ }
+ else {
+ if (!event) {
+ err = EINVAL;
+ goto out;
+ }
+
+ err = pl_strdup(¬->event, &event->event);
+ if (err)
+ goto out;
+
+ if (pl_isset(&event->id)) {
+
+ err = pl_strdup(¬->id, &event->id);
+ if (err)
+ goto out;
+ }
+ }
+
+ if (dlg) {
+ not->dlg = mem_ref(dlg);
+ }
+ else {
+ err = sip_dialog_accept(¬->dlg, msg);
+ if (err)
+ goto out;
+ }
+
+ hash_append(sock->ht_not,
+ hash_joaat_str(sip_dialog_callid(not->dlg)),
+ ¬->he, not);
+
+ err = sip_auth_alloc(¬->auth, authh, aarg, aref);
+ if (err)
+ goto out;
+
+ err = str_dup(¬->cuser, cuser);
+ if (err)
+ goto out;
+
+ err = str_dup(¬->ctype, ctype);
+ if (err)
+ goto out;
+
+ if (fmt) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ err = re_vsdprintf(¬->hdrs, fmt, ap);
+ va_end(ap);
+ if (err)
+ goto out;
+ }
+
+ not->expires_min = expires_min;
+ not->expires_dfl = expires_dfl;
+ not->expires_max = expires_max;
+ not->substate = SIPEVENT_PENDING;
+ not->sock = mem_ref(sock);
+ not->sip = mem_ref(sock->sip);
+ not->closeh = closeh ? closeh : internal_close_handler;
+ not->arg = arg;
+
+ if (pl_isset(&msg->expires))
+ expires = pl_u32(&msg->expires);
+ else
+ expires = not->expires_dfl;
+
+ sipnot_refresh(not, expires);
+
+ err = sipnot_reply(not, msg, scode, reason);
+ if (err)
+ goto out;
+
+ not->subscribed = true;
+
+ out:
+ if (err)
+ mem_deref(not);
+ else
+ *notp = not;
+
+ return err;
+}
+
+
+int sipevent_notify(struct sipnot *not, struct mbuf *mb,
+ enum sipevent_subst state, enum sipevent_reason reason,
+ uint32_t retry_after)
+{
+ if (!not || not->terminated)
+ return EINVAL;
+
+ if (mb || state != SIPEVENT_TERMINATED) {
+ mem_deref(not->mb);
+ not->mb = mem_ref(mb);
+ }
+
+ switch (state) {
+
+ case SIPEVENT_ACTIVE:
+ case SIPEVENT_PENDING:
+ not->substate = state;
+ return sipnot_notify(not);
+
+ case SIPEVENT_TERMINATED:
+ tmr_cancel(¬->tmr);
+ not->retry_after = retry_after;
+ (void)terminate(not, reason);
+ return 0;
+
+ default:
+ return EINVAL;
+ }
+}
+
+
+int sipevent_notifyf(struct sipnot *not, struct mbuf **mbp,
+ enum sipevent_subst state, enum sipevent_reason reason,
+ uint32_t retry_after, const char *fmt, ...)
+{
+ struct mbuf *mb;
+ va_list ap;
+ int err;
+
+ if (!not || not->terminated || !fmt)
+ return EINVAL;
+
+ if (mbp && *mbp)
+ return sipevent_notify(not, *mbp, state, reason, retry_after);
+
+ mb = mbuf_alloc(1024);
+ if (!mb)
+ return ENOMEM;
+
+ va_start(ap, fmt);
+ err = mbuf_vprintf(mb, fmt, ap);
+ va_end(ap);
+ if (err)
+ goto out;
+
+ mb->pos = 0;
+
+ err = sipevent_notify(not, mb, state, reason, retry_after);
+ if (err)
+ goto out;
+
+ out:
+ if (err || !mbp)
+ mem_deref(mb);
+ else
+ *mbp = mb;
+
+ return err;
+}
diff --git a/src/sipevent/sipevent.h b/src/sipevent/sipevent.h
new file mode 100644
index 0000000..e186efb
--- /dev/null
+++ b/src/sipevent/sipevent.h
@@ -0,0 +1,92 @@
+/**
+ * @file sipevent.h SIP Event Private Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+/* Listener Socket */
+
+struct sipevent_sock {
+ struct sip_lsnr *lsnr;
+ struct hash *ht_not;
+ struct hash *ht_sub;
+ struct sip *sip;
+ sip_msg_h *subh;
+ void *arg;
+};
+
+
+/* Notifier */
+
+struct sipnot {
+ struct le he;
+ struct sip_loopstate ls;
+ struct tmr tmr;
+ struct sipevent_sock *sock;
+ struct sip_request *req;
+ struct sip_dialog *dlg;
+ struct sip_auth *auth;
+ struct sip *sip;
+ struct mbuf *mb;
+ char *event;
+ char *id;
+ char *cuser;
+ char *hdrs;
+ char *ctype;
+ sipnot_close_h *closeh;
+ void *arg;
+ uint32_t expires;
+ uint32_t expires_min;
+ uint32_t expires_dfl;
+ uint32_t expires_max;
+ uint32_t retry_after;
+ enum sipevent_subst substate;
+ enum sipevent_reason reason;
+ bool notify_pending;
+ bool subscribed;
+ bool terminated;
+ bool termsent;
+};
+
+void sipnot_refresh(struct sipnot *not, uint32_t expires);
+int sipnot_notify(struct sipnot *not);
+int sipnot_reply(struct sipnot *not, const struct sip_msg *msg,
+ uint16_t scode, const char *reason);
+
+
+/* Subscriber */
+
+struct sipsub {
+ struct le he;
+ struct sip_loopstate ls;
+ struct tmr tmr;
+ struct sipevent_sock *sock;
+ struct sip_request *req;
+ struct sip_dialog *dlg;
+ struct sip_auth *auth;
+ struct sip *sip;
+ char *event;
+ char *id;
+ char *cuser;
+ char *hdrs;
+ char *refer_hdrs;
+ sipsub_fork_h *forkh;
+ sipsub_notify_h *notifyh;
+ sipsub_close_h *closeh;
+ void *arg;
+ int32_t refer_cseq;
+ uint32_t expires;
+ uint32_t failc;
+ bool subscribed;
+ bool terminated;
+ bool termconf;
+ bool termwait;
+ bool refer;
+};
+
+struct sipsub *sipsub_find(struct sipevent_sock *sock,
+ const struct sip_msg *msg,
+ const struct sipevent_event *evt, bool full);
+void sipsub_reschedule(struct sipsub *sub, uint64_t wait);
+void sipsub_terminate(struct sipsub *sub, int err, const struct sip_msg *msg,
+ const struct sipevent_substate *substate);
diff --git a/src/sipevent/subscribe.c b/src/sipevent/subscribe.c
new file mode 100644
index 0000000..fe6ba56
--- /dev/null
+++ b/src/sipevent/subscribe.c
@@ -0,0 +1,669 @@
+/**
+ * @file subscribe.c SIP Event Subscribe
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipevent.h>
+#include "sipevent.h"
+
+
+enum {
+ DEFAULT_EXPIRES = 3600,
+ RESUB_FAIL_WAIT = 60000,
+ RESUB_FAILC_MAX = 7,
+ NOTIFY_TIMEOUT = 10000,
+};
+
+
+static int request(struct sipsub *sub, bool reset_ls);
+
+
+static void internal_notify_handler(struct sip *sip, const struct sip_msg *msg,
+ void *arg)
+{
+ (void)arg;
+
+ (void)sip_treply(NULL, sip, msg, 200, "OK");
+}
+
+
+static void internal_close_handler(int err, const struct sip_msg *msg,
+ const struct sipevent_substate *substate,
+ void *arg)
+{
+ (void)err;
+ (void)msg;
+ (void)substate;
+ (void)arg;
+}
+
+
+static bool terminate(struct sipsub *sub)
+{
+ sub->terminated = true;
+ sub->forkh = NULL;
+ sub->notifyh = internal_notify_handler;
+ sub->closeh = internal_close_handler;
+
+ if (sub->termwait) {
+ mem_ref(sub);
+ return true;
+ }
+
+ tmr_cancel(&sub->tmr);
+
+ if (sub->req) {
+ mem_ref(sub);
+ return true;
+ }
+
+ if (sub->expires && sub->subscribed && !request(sub, true)) {
+ mem_ref(sub);
+ return true;
+ }
+
+ return false;
+}
+
+
+static void destructor(void *arg)
+{
+ struct sipsub *sub = arg;
+
+ if (!sub->terminated) {
+
+ if (terminate(sub))
+ return;
+ }
+
+ tmr_cancel(&sub->tmr);
+ hash_unlink(&sub->he);
+ mem_deref(sub->req);
+ mem_deref(sub->dlg);
+ mem_deref(sub->auth);
+ mem_deref(sub->event);
+ mem_deref(sub->id);
+ mem_deref(sub->cuser);
+ mem_deref(sub->hdrs);
+ mem_deref(sub->refer_hdrs);
+ mem_deref(sub->sock);
+ mem_deref(sub->sip);
+}
+
+
+static void notify_timeout_handler(void *arg)
+{
+ struct sipsub *sub = arg;
+
+ sub->termwait = false;
+
+ if (sub->terminated)
+ mem_deref(sub);
+ else
+ sipsub_terminate(sub, ETIMEDOUT, NULL, NULL);
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct sipsub *sub = arg;
+ int err;
+
+ if (sub->req || sub->terminated)
+ return;
+
+ err = request(sub, true);
+ if (err) {
+ if (++sub->failc < RESUB_FAILC_MAX) {
+ sipsub_reschedule(sub, RESUB_FAIL_WAIT);
+ }
+ else {
+ sipsub_terminate(sub, err, NULL, NULL);
+ }
+ }
+}
+
+
+void sipsub_reschedule(struct sipsub *sub, uint64_t wait)
+{
+ tmr_start(&sub->tmr, wait, tmr_handler, sub);
+}
+
+
+void sipsub_terminate(struct sipsub *sub, int err, const struct sip_msg *msg,
+ const struct sipevent_substate *substate)
+{
+ sipsub_close_h *closeh;
+ void *arg;
+
+ closeh = sub->closeh;
+ arg = sub->arg;
+
+ (void)terminate(sub);
+
+ closeh(err, msg, substate, arg);
+}
+
+
+static void response_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ const struct sip_hdr *minexp;
+ struct sipsub *sub = arg;
+
+ if (err || sip_request_loops(&sub->ls, msg->scode))
+ goto out;
+
+ if (msg->scode < 200) {
+ return;
+ }
+ else if (msg->scode < 300) {
+
+ uint32_t wait;
+
+ if (sub->forkh) {
+
+ struct sipsub *fsub;
+
+ fsub = sipsub_find(sub->sock, msg, NULL, true);
+ if (!fsub) {
+
+ err = sub->forkh(&fsub, sub, msg, sub->arg);
+ if (err)
+ return;
+ }
+ else {
+ (void)sip_dialog_update(fsub->dlg, msg);
+ }
+
+ sub = fsub;
+ }
+ else if (!sip_dialog_established(sub->dlg)) {
+
+ err = sip_dialog_create(sub->dlg, msg);
+ if (err) {
+ sub->subscribed = false;
+ goto out;
+ }
+ }
+ else {
+ /* Ignore 2xx responses for other dialogs
+ * if forking is disabled */
+ if (!sip_dialog_cmp(sub->dlg, msg))
+ return;
+
+ (void)sip_dialog_update(sub->dlg, msg);
+ }
+
+ if (!sub->termconf)
+ sub->subscribed = true;
+
+ sub->failc = 0;
+
+ if (!sub->expires && !sub->termconf) {
+
+ tmr_start(&sub->tmr, NOTIFY_TIMEOUT,
+ notify_timeout_handler, sub);
+ sub->termwait = true;
+ return;
+ }
+
+ if (sub->terminated)
+ goto out;
+
+ if (sub->refer) {
+ sub->refer = false;
+ return;
+ }
+
+ if (pl_isset(&msg->expires))
+ wait = pl_u32(&msg->expires);
+ else
+ wait = sub->expires;
+
+ sipsub_reschedule(sub, wait * 900);
+ return;
+ }
+ else {
+ if (sub->terminated && !sub->subscribed)
+ goto out;
+
+ switch (msg->scode) {
+
+ case 401:
+ case 407:
+ err = sip_auth_authenticate(sub->auth, msg);
+ if (err) {
+ err = (err == EAUTH) ? 0 : err;
+ break;
+ }
+
+ err = request(sub, false);
+ if (err)
+ break;
+
+ return;
+
+ case 403:
+ sip_auth_reset(sub->auth);
+ break;
+
+ case 423:
+ minexp = sip_msg_hdr(msg, SIP_HDR_MIN_EXPIRES);
+ if (!minexp || !pl_u32(&minexp->val) || !sub->expires)
+ break;
+
+ sub->expires = pl_u32(&minexp->val);
+
+ err = request(sub, false);
+ if (err)
+ break;
+
+ return;
+
+ case 481:
+ sub->subscribed = false;
+ break;
+ }
+ }
+
+ out:
+ sub->refer = false;
+
+ if (sub->terminated) {
+
+ if (!sub->expires || !sub->subscribed || request(sub, true))
+ mem_deref(sub);
+ }
+ else {
+ if (sub->subscribed && ++sub->failc < RESUB_FAILC_MAX)
+ sipsub_reschedule(sub, RESUB_FAIL_WAIT);
+ else
+ sipsub_terminate(sub, err, msg, NULL);
+ }
+}
+
+
+static int send_handler(enum sip_transp tp, const struct sa *src,
+ const struct sa *dst, struct mbuf *mb, void *arg)
+{
+ struct sip_contact contact;
+ struct sipsub *sub = arg;
+ (void)dst;
+
+ sip_contact_set(&contact, sub->cuser, src, tp);
+
+ return mbuf_printf(mb, "%H", sip_contact_print, &contact);
+}
+
+
+static int print_event(struct re_printf *pf, const struct sipsub *sub)
+{
+ if (sub->id)
+ return re_hprintf(pf, "%s;id=%s", sub->event, sub->id);
+ else
+ return re_hprintf(pf, "%s", sub->event);
+}
+
+
+static int request(struct sipsub *sub, bool reset_ls)
+{
+ if (reset_ls)
+ sip_loopstate_reset(&sub->ls);
+
+ if (sub->refer) {
+
+ sub->refer_cseq = sip_dialog_lseq(sub->dlg);
+
+ return sip_drequestf(&sub->req, sub->sip, true, "REFER",
+ sub->dlg, 0, sub->auth,
+ send_handler, response_handler, sub,
+ "%s"
+ "Content-Length: 0\r\n"
+ "\r\n",
+ sub->refer_hdrs);
+ }
+ else {
+ if (sub->terminated)
+ sub->expires = 0;
+
+ return sip_drequestf(&sub->req, sub->sip, true, "SUBSCRIBE",
+ sub->dlg, 0, sub->auth,
+ send_handler, response_handler, sub,
+ "Event: %H\r\n"
+ "Expires: %u\r\n"
+ "%s"
+ "Content-Length: 0\r\n"
+ "\r\n",
+ print_event, sub,
+ sub->expires,
+ sub->hdrs);
+ }
+}
+
+
+static int sipsub_alloc(struct sipsub **subp, struct sipevent_sock *sock,
+ bool refer, struct sip_dialog *dlg, const char *uri,
+ const char *from_name, const char *from_uri,
+ const char *event, const char *id, uint32_t expires,
+ const char *cuser,
+ const char *routev[], uint32_t routec,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsub_fork_h *forkh, sipsub_notify_h *notifyh,
+ sipsub_close_h *closeh, void *arg,
+ const char *fmt, va_list ap)
+{
+ struct sipsub *sub;
+ int err;
+
+ if (!subp || !sock || !event || !cuser)
+ return EINVAL;
+
+ if (!dlg && (!uri || !from_uri))
+ return EINVAL;
+
+ sub = mem_zalloc(sizeof(*sub), destructor);
+ if (!sub)
+ return ENOMEM;
+
+ if (dlg) {
+ sub->dlg = mem_ref(dlg);
+ }
+ else {
+ err = sip_dialog_alloc(&sub->dlg, uri, uri, from_name,
+ from_uri, routev, routec);
+ if (err)
+ goto out;
+ }
+
+ hash_append(sock->ht_sub,
+ hash_joaat_str(sip_dialog_callid(sub->dlg)),
+ &sub->he, sub);
+
+ err = sip_auth_alloc(&sub->auth, authh, aarg, aref);
+ if (err)
+ goto out;
+
+ err = str_dup(&sub->event, event);
+ if (err)
+ goto out;
+
+ if (id) {
+ err = str_dup(&sub->id, id);
+ if (err)
+ goto out;
+ }
+
+ err = str_dup(&sub->cuser, cuser);
+ if (err)
+ goto out;
+
+ if (fmt) {
+ err = re_vsdprintf(refer ? &sub->refer_hdrs : &sub->hdrs,
+ fmt, ap);
+ if (err)
+ goto out;
+ }
+
+ sub->refer_cseq = -1;
+ sub->refer = refer;
+ sub->sock = mem_ref(sock);
+ sub->sip = mem_ref(sock->sip);
+ sub->expires = expires;
+ sub->forkh = forkh;
+ sub->notifyh = notifyh ? notifyh : internal_notify_handler;
+ sub->closeh = closeh ? closeh : internal_close_handler;
+ sub->arg = arg;
+
+ err = request(sub, true);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(sub);
+ else
+ *subp = sub;
+
+ return err;
+}
+
+
+/**
+ * Allocate a SIP subscriber client
+ *
+ * @param subp Pointer to allocated SIP subscriber client
+ * @param sock SIP Event socket
+ * @param uri SIP Request URI
+ * @param from_name SIP From-header Name (optional)
+ * @param from_uri SIP From-header URI
+ * @param event SIP Event to subscribe to
+ * @param id SIP Event ID (optional)
+ * @param expires Subscription expires value
+ * @param cuser Contact username or URI
+ * @param routev Optional route vector
+ * @param routec Number of routes
+ * @param authh Authentication handler
+ * @param aarg Authentication handler argument
+ * @param aref True to ref argument
+ * @param forkh Fork handler
+ * @param notifyh Notify handler
+ * @param closeh Close handler
+ * @param arg Response handler argument
+ * @param fmt Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipevent_subscribe(struct sipsub **subp, struct sipevent_sock *sock,
+ const char *uri, const char *from_name,
+ const char *from_uri, const char *event, const char *id,
+ uint32_t expires, const char *cuser,
+ const char *routev[], uint32_t routec,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsub_fork_h *forkh, sipsub_notify_h *notifyh,
+ sipsub_close_h *closeh, void *arg,
+ const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, fmt);
+ err = sipsub_alloc(subp, sock, false, NULL, uri, from_name, from_uri,
+ event, id, expires, cuser,
+ routev, routec, authh, aarg, aref, forkh, notifyh,
+ closeh, arg, fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Allocate a SIP subscriber client using an existing dialog
+ *
+ * @param subp Pointer to allocated SIP subscriber client
+ * @param sock SIP Event socket
+ * @param dlg Established SIP Dialog
+ * @param event SIP Event to subscribe to
+ * @param id SIP Event ID (optional)
+ * @param expires Subscription expires value
+ * @param cuser Contact username or URI
+ * @param authh Authentication handler
+ * @param aarg Authentication handler argument
+ * @param aref True to ref argument
+ * @param notifyh Notify handler
+ * @param closeh Close handler
+ * @param arg Response handler argument
+ * @param fmt Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipevent_dsubscribe(struct sipsub **subp, struct sipevent_sock *sock,
+ struct sip_dialog *dlg, const char *event,
+ const char *id, uint32_t expires, const char *cuser,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsub_notify_h *notifyh, sipsub_close_h *closeh,
+ void *arg, const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, fmt);
+ err = sipsub_alloc(subp, sock, false, dlg, NULL, NULL, NULL,
+ event, id, expires, cuser,
+ NULL, 0, authh, aarg, aref, NULL, notifyh,
+ closeh, arg, fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Allocate a SIP refer client
+ *
+ * @param subp Pointer to allocated SIP subscriber client
+ * @param sock SIP Event socket
+ * @param uri SIP Request URI
+ * @param from_name SIP From-header Name (optional)
+ * @param from_uri SIP From-header URI
+ * @param cuser Contact username or URI
+ * @param routev Optional route vector
+ * @param routec Number of routes
+ * @param authh Authentication handler
+ * @param aarg Authentication handler argument
+ * @param aref True to ref argument
+ * @param forkh Fork handler
+ * @param notifyh Notify handler
+ * @param closeh Close handler
+ * @param arg Response handler argument
+ * @param fmt Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipevent_refer(struct sipsub **subp, struct sipevent_sock *sock,
+ const char *uri, const char *from_name,
+ const char *from_uri, const char *cuser,
+ const char *routev[], uint32_t routec,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsub_fork_h *forkh, sipsub_notify_h *notifyh,
+ sipsub_close_h *closeh, void *arg,
+ const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, fmt);
+ err = sipsub_alloc(subp, sock, true, NULL, uri, from_name, from_uri,
+ "refer", NULL, DEFAULT_EXPIRES, cuser,
+ routev, routec, authh, aarg, aref, forkh, notifyh,
+ closeh, arg, fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Allocate a SIP refer client using an existing dialog
+ *
+ * @param subp Pointer to allocated SIP subscriber client
+ * @param sock SIP Event socket
+ * @param dlg Established SIP Dialog
+ * @param cuser Contact username or URI
+ * @param authh Authentication handler
+ * @param aarg Authentication handler argument
+ * @param aref True to ref argument
+ * @param notifyh Notify handler
+ * @param closeh Close handler
+ * @param arg Response handler argument
+ * @param fmt Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipevent_drefer(struct sipsub **subp, struct sipevent_sock *sock,
+ struct sip_dialog *dlg, const char *cuser,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsub_notify_h *notifyh, sipsub_close_h *closeh,
+ void *arg, const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, fmt);
+ err = sipsub_alloc(subp, sock, true, dlg, NULL, NULL, NULL,
+ "refer", NULL, DEFAULT_EXPIRES, cuser,
+ NULL, 0, authh, aarg, aref, NULL, notifyh,
+ closeh, arg, fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+int sipevent_fork(struct sipsub **subp, struct sipsub *osub,
+ const struct sip_msg *msg,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsub_notify_h *notifyh, sipsub_close_h *closeh,
+ void *arg)
+{
+ struct sipsub *sub;
+ int err;
+
+ if (!subp || !osub || !msg)
+ return EINVAL;
+
+ sub = mem_zalloc(sizeof(*sub), destructor);
+ if (!sub)
+ return ENOMEM;
+
+ err = sip_dialog_fork(&sub->dlg, osub->dlg, msg);
+ if (err)
+ goto out;
+
+ hash_append(osub->sock->ht_sub,
+ hash_joaat_str(sip_dialog_callid(sub->dlg)),
+ &sub->he, sub);
+
+ err = sip_auth_alloc(&sub->auth, authh, aarg, aref);
+ if (err)
+ goto out;
+
+ sub->event = mem_ref(osub->event);
+ sub->id = mem_ref(osub->id);
+ sub->cuser = mem_ref(osub->cuser);
+ sub->hdrs = mem_ref(osub->hdrs);
+ sub->refer = osub->refer;
+ sub->sock = mem_ref(osub->sock);
+ sub->sip = mem_ref(osub->sip);
+ sub->expires = osub->expires;
+ sub->forkh = NULL;
+ sub->notifyh = notifyh ? notifyh : internal_notify_handler;
+ sub->closeh = closeh ? closeh : internal_close_handler;
+ sub->arg = arg;
+
+ if (!sub->expires) {
+ tmr_start(&sub->tmr, NOTIFY_TIMEOUT,
+ notify_timeout_handler, sub);
+ sub->termwait = true;
+ }
+
+ out:
+ if (err)
+ mem_deref(sub);
+ else
+ *subp = sub;
+
+ return err;
+}
diff --git a/src/sipreg/mod.mk b/src/sipreg/mod.mk
new file mode 100644
index 0000000..2edcff4
--- /dev/null
+++ b/src/sipreg/mod.mk
@@ -0,0 +1,7 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += sipreg/reg.c
diff --git a/src/sipreg/reg.c b/src/sipreg/reg.c
new file mode 100644
index 0000000..3967200
--- /dev/null
+++ b/src/sipreg/reg.c
@@ -0,0 +1,409 @@
+/**
+ * @file reg.c SIP Registration
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_sys.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipreg.h>
+
+
+enum {
+ DEFAULT_EXPIRES = 3600,
+};
+
+
+/** Defines a SIP Registration client */
+struct sipreg {
+ struct sip_loopstate ls;
+ struct sa laddr;
+ struct tmr tmr;
+ struct sip *sip;
+ struct sip_keepalive *ka;
+ struct sip_request *req;
+ struct sip_dialog *dlg;
+ struct sip_auth *auth;
+ struct mbuf *hdrs;
+ char *cuser;
+ sip_resp_h *resph;
+ void *arg;
+ uint32_t expires;
+ uint32_t failc;
+ uint32_t wait;
+ enum sip_transp tp;
+ bool registered;
+ bool terminated;
+ char *params;
+ int regid;
+};
+
+
+static int request(struct sipreg *reg, bool reset_ls);
+
+
+static void dummy_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ (void)err;
+ (void)msg;
+ (void)arg;
+}
+
+
+static void destructor(void *arg)
+{
+ struct sipreg *reg = arg;
+
+ tmr_cancel(®->tmr);
+
+ if (!reg->terminated) {
+
+ reg->resph = dummy_handler;
+ reg->terminated = true;
+
+ if (reg->req) {
+ mem_ref(reg);
+ return;
+ }
+
+ if (reg->registered && !request(reg, true)) {
+ mem_ref(reg);
+ return;
+ }
+ }
+
+ mem_deref(reg->ka);
+ mem_deref(reg->dlg);
+ mem_deref(reg->auth);
+ mem_deref(reg->cuser);
+ mem_deref(reg->sip);
+ mem_deref(reg->hdrs);
+ mem_deref(reg->params);
+}
+
+
+static uint32_t failwait(uint32_t failc)
+{
+ return min(1800, (30 * (1<<min(failc, 6)))) * (500 + rand_u16() % 501);
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct sipreg *reg = arg;
+ int err;
+
+ err = request(reg, true);
+ if (err) {
+ tmr_start(®->tmr, failwait(++reg->failc), tmr_handler, reg);
+ reg->resph(err, NULL, reg->arg);
+ }
+}
+
+
+static void keepalive_handler(int err, void *arg)
+{
+ struct sipreg *reg = arg;
+
+ /* failure will be handled in response handler */
+ if (reg->req || reg->terminated)
+ return;
+
+ tmr_start(®->tmr, failwait(++reg->failc), tmr_handler, reg);
+ reg->resph(err, NULL, reg->arg);
+}
+
+
+static void start_outbound(struct sipreg *reg, const struct sip_msg *msg)
+{
+ const struct sip_hdr *flowtimer;
+
+ if (!sip_msg_hdr_has_value(msg, SIP_HDR_REQUIRE, "outbound"))
+ return;
+
+ flowtimer = sip_msg_hdr(msg, SIP_HDR_FLOW_TIMER);
+
+ (void)sip_keepalive_start(®->ka, reg->sip, msg,
+ flowtimer ? pl_u32(&flowtimer->val) : 0,
+ keepalive_handler, reg);
+}
+
+
+static bool contact_handler(const struct sip_hdr *hdr,
+ const struct sip_msg *msg, void *arg)
+{
+ struct sipreg *reg = arg;
+ struct sip_addr c;
+ struct pl pval;
+ char uri[256];
+
+ if (sip_addr_decode(&c, &hdr->val))
+ return false;
+
+ if (re_snprintf(uri, sizeof(uri), "sip:%s@%J%s", reg->cuser,
+ ®->laddr, sip_transp_param(reg->tp)) < 0)
+ return false;
+
+ if (pl_strcmp(&c.auri, uri))
+ return false;
+
+ if (!msg_param_decode(&c.params, "expires", &pval)) {
+ reg->wait = pl_u32(&pval);
+ }
+ else if (pl_isset(&msg->expires))
+ reg->wait = pl_u32(&msg->expires);
+ else
+ reg->wait = DEFAULT_EXPIRES;
+
+ return true;
+}
+
+
+static void response_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ const struct sip_hdr *minexp;
+ struct sipreg *reg = arg;
+
+ reg->wait = failwait(reg->failc + 1);
+
+ if (err || sip_request_loops(®->ls, msg->scode)) {
+ reg->failc++;
+ goto out;
+ }
+
+ if (msg->scode < 200) {
+ return;
+ }
+ else if (msg->scode < 300) {
+ reg->registered = true;
+ reg->wait = reg->expires;
+ sip_msg_hdr_apply(msg, true, SIP_HDR_CONTACT, contact_handler,
+ reg);
+ reg->wait *= 900;
+ reg->failc = 0;
+
+ if (reg->regid > 0 && !reg->terminated && !reg->ka)
+ start_outbound(reg, msg);
+ }
+ else {
+ if (reg->terminated && !reg->registered)
+ goto out;
+
+ switch (msg->scode) {
+
+ case 401:
+ case 407:
+ err = sip_auth_authenticate(reg->auth, msg);
+ if (err) {
+ err = (err == EAUTH) ? 0 : err;
+ break;
+ }
+
+ err = request(reg, false);
+ if (err)
+ break;
+
+ return;
+
+ case 403:
+ sip_auth_reset(reg->auth);
+ break;
+
+ case 423:
+ minexp = sip_msg_hdr(msg, SIP_HDR_MIN_EXPIRES);
+ if (!minexp || !pl_u32(&minexp->val) || !reg->expires)
+ break;
+
+ reg->expires = pl_u32(&minexp->val);
+
+ err = request(reg, false);
+ if (err)
+ break;
+
+ return;
+ }
+
+ ++reg->failc;
+ }
+
+ out:
+ if (!reg->expires) {
+ mem_deref(reg);
+ }
+ else if (reg->terminated) {
+ if (!reg->registered || request(reg, true))
+ mem_deref(reg);
+ }
+ else {
+ tmr_start(®->tmr, reg->wait, tmr_handler, reg);
+ reg->resph(err, msg, reg->arg);
+ }
+}
+
+
+static int send_handler(enum sip_transp tp, const struct sa *src,
+ const struct sa *dst, struct mbuf *mb, void *arg)
+{
+ struct sipreg *reg = arg;
+ int err;
+
+ (void)dst;
+
+ if (reg->expires > 0) {
+ reg->laddr = *src;
+ reg->tp = tp;
+ }
+
+ err = mbuf_printf(mb, "Contact: <sip:%s@%J%s>;expires=%u%s%s",
+ reg->cuser, ®->laddr, sip_transp_param(reg->tp),
+ reg->expires,
+ reg->params ? ";" : "",
+ reg->params ? reg->params : "");
+
+ if (reg->regid > 0)
+ err |= mbuf_printf(mb, ";reg-id=%d", reg->regid);
+
+ err |= mbuf_printf(mb, "\r\n");
+
+ return err;
+}
+
+
+static int request(struct sipreg *reg, bool reset_ls)
+{
+ if (reg->terminated)
+ reg->expires = 0;
+
+ if (reset_ls)
+ sip_loopstate_reset(®->ls);
+
+ return sip_drequestf(®->req, reg->sip, true, "REGISTER", reg->dlg,
+ 0, reg->auth, send_handler, response_handler, reg,
+ "%s"
+ "%b"
+ "Content-Length: 0\r\n"
+ "\r\n",
+ reg->regid > 0
+ ? "Supported: gruu, outbound, path\r\n" : "",
+ reg->hdrs ? mbuf_buf(reg->hdrs) : NULL,
+ reg->hdrs ? mbuf_get_left(reg->hdrs) : (size_t)0);
+}
+
+
+/**
+ * Allocate a SIP Registration client
+ *
+ * @param regp Pointer to allocated SIP Registration client
+ * @param sip SIP Stack instance
+ * @param reg_uri SIP Request URI
+ * @param to_uri SIP To-header URI
+ * @param from_name SIP From-header display name (optional)
+ * @param from_uri SIP From-header URI
+ * @param expires Registration interval in [seconds]
+ * @param cuser Contact username
+ * @param routev Optional route vector
+ * @param routec Number of routes
+ * @param regid Register identification
+ * @param authh Authentication handler
+ * @param aarg Authentication handler argument
+ * @param aref True to ref argument
+ * @param resph Response handler
+ * @param arg Response handler argument
+ * @param params Optional Contact-header parameters
+ * @param fmt Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipreg_register(struct sipreg **regp, struct sip *sip, const char *reg_uri,
+ const char *to_uri, const char *from_name,
+ const char *from_uri, uint32_t expires,
+ const char *cuser, const char *routev[], uint32_t routec,
+ int regid, sip_auth_h *authh, void *aarg, bool aref,
+ sip_resp_h *resph, void *arg,
+ const char *params, const char *fmt, ...)
+{
+ struct sipreg *reg;
+ int err;
+
+ if (!regp || !sip || !reg_uri || !to_uri || !from_uri ||
+ !expires || !cuser)
+ return EINVAL;
+
+ reg = mem_zalloc(sizeof(*reg), destructor);
+ if (!reg)
+ return ENOMEM;
+
+ err = sip_dialog_alloc(®->dlg, reg_uri, to_uri, from_name, from_uri,
+ routev, routec);
+ if (err)
+ goto out;
+
+ err = sip_auth_alloc(®->auth, authh, aarg, aref);
+ if (err)
+ goto out;
+
+ err = str_dup(®->cuser, cuser);
+ if (params)
+ err |= str_dup(®->params, params);
+ if (err)
+ goto out;
+
+ /* Custom SIP headers */
+ if (fmt) {
+ va_list ap;
+
+ reg->hdrs = mbuf_alloc(256);
+ if (!reg->hdrs) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ va_start(ap, fmt);
+ err = mbuf_vprintf(reg->hdrs, fmt, ap);
+ reg->hdrs->pos = 0;
+ va_end(ap);
+
+ if (err)
+ goto out;
+ }
+
+ reg->sip = mem_ref(sip);
+ reg->expires = expires;
+ reg->resph = resph ? resph : dummy_handler;
+ reg->arg = arg;
+ reg->regid = regid;
+
+ err = request(reg, true);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(reg);
+ else
+ *regp = reg;
+
+ return err;
+}
+
+
+/**
+ * Get the local socket address for a SIP Registration client
+ *
+ * @param reg SIP Registration client
+ *
+ * @return Local socket address
+ */
+const struct sa *sipreg_laddr(const struct sipreg *reg)
+{
+ return reg ? ®->laddr : NULL;
+}
diff --git a/src/sipsess/accept.c b/src/sipsess/accept.c
new file mode 100644
index 0000000..b8d02f8
--- /dev/null
+++ b/src/sipsess/accept.c
@@ -0,0 +1,246 @@
+/**
+ * @file accept.c SIP Session Accept
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static void cancel_handler(void *arg)
+{
+ struct sipsess *sess = arg;
+
+ (void)sip_treply(&sess->st, sess->sip, sess->msg,
+ 487, "Request Terminated");
+
+ sess->peerterm = true;
+
+ if (sess->terminated)
+ return;
+
+ sipsess_terminate(sess, ECONNRESET, NULL);
+}
+
+
+/**
+ * Accept an incoming SIP Session connection
+ *
+ * @param sessp Pointer to allocated SIP Session
+ * @param sock SIP Session socket
+ * @param msg Incoming SIP message
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ * @param cuser Contact username or URI
+ * @param ctype Session content-type
+ * @param desc Content description (e.g. SDP)
+ * @param authh SIP Authentication handler
+ * @param aarg Authentication handler argument
+ * @param aref True to mem_ref() aarg
+ * @param offerh Session offer handler
+ * @param answerh Session answer handler
+ * @param estabh Session established handler
+ * @param infoh Session info handler
+ * @param referh Session refer handler
+ * @param closeh Session close handler
+ * @param arg Handler argument
+ * @param fmt Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_accept(struct sipsess **sessp, struct sipsess_sock *sock,
+ const struct sip_msg *msg, uint16_t scode,
+ const char *reason, const char *cuser, const char *ctype,
+ struct mbuf *desc,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsess_offer_h *offerh, sipsess_answer_h *answerh,
+ sipsess_estab_h *estabh, sipsess_info_h *infoh,
+ sipsess_refer_h *referh, sipsess_close_h *closeh,
+ void *arg, const char *fmt, ...)
+{
+ struct sipsess *sess;
+ va_list ap;
+ int err;
+
+ if (!sessp || !sock || !msg || scode < 101 || scode > 299 ||
+ !cuser || !ctype)
+ return EINVAL;
+
+ err = sipsess_alloc(&sess, sock, cuser, ctype, NULL, authh, aarg, aref,
+ offerh, answerh, NULL, estabh, infoh, referh,
+ closeh, arg);
+ if (err)
+ return err;
+
+ err = sip_dialog_accept(&sess->dlg, msg);
+ if (err)
+ goto out;
+
+ hash_append(sock->ht_sess,
+ hash_joaat_str(sip_dialog_callid(sess->dlg)),
+ &sess->he, sess);
+
+ sess->msg = mem_ref((void *)msg);
+
+ err = sip_strans_alloc(&sess->st, sess->sip, msg, cancel_handler,
+ sess);
+ if (err)
+ goto out;
+
+ va_start(ap, fmt);
+
+ if (scode >= 200)
+ err = sipsess_reply_2xx(sess, msg, scode, reason, desc,
+ fmt, &ap);
+ else {
+ struct sip_contact contact;
+
+ sip_contact_set(&contact, sess->cuser, &msg->dst, msg->tp);
+
+ err = sip_treplyf(&sess->st, NULL, sess->sip,
+ msg, true, scode, reason,
+ "%H"
+ "%v"
+ "%s%s%s"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ sip_contact_print, &contact,
+ fmt, &ap,
+ desc ? "Content-Type: " : "",
+ desc ? sess->ctype : "",
+ desc ? "\r\n" : "",
+ desc ? mbuf_get_left(desc) : (size_t)0,
+ desc ? mbuf_buf(desc) : NULL,
+ desc ? mbuf_get_left(desc) : (size_t)0);
+ }
+
+ va_end(ap);
+
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+/**
+ * Send progress response
+ *
+ * @param sess SIP Session
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ * @param desc Content description (e.g. SDP)
+ * @param fmt Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_progress(struct sipsess *sess, uint16_t scode, const char *reason,
+ struct mbuf *desc, const char *fmt, ...)
+{
+ struct sip_contact contact;
+ va_list ap;
+ int err;
+
+ if (!sess || !sess->st || !sess->msg || scode < 101 || scode > 199)
+ return EINVAL;
+
+ va_start(ap, fmt);
+
+ sip_contact_set(&contact, sess->cuser, &sess->msg->dst, sess->msg->tp);
+
+ err = sip_treplyf(&sess->st, NULL, sess->sip, sess->msg, true,
+ scode, reason,
+ "%H"
+ "%v"
+ "%s%s%s"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ sip_contact_print, &contact,
+ fmt, &ap,
+ desc ? "Content-Type: " : "",
+ desc ? sess->ctype : "",
+ desc ? "\r\n" : "",
+ desc ? mbuf_get_left(desc) : (size_t)0,
+ desc ? mbuf_buf(desc) : NULL,
+ desc ? mbuf_get_left(desc) : (size_t)0);
+
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Answer an incoming SIP Session connection
+ *
+ * @param sess SIP Session
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ * @param desc Content description (e.g. SDP)
+ * @param fmt Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_answer(struct sipsess *sess, uint16_t scode, const char *reason,
+ struct mbuf *desc, const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ if (!sess || !sess->st || !sess->msg || scode < 200 || scode > 299)
+ return EINVAL;
+
+ va_start(ap, fmt);
+ err = sipsess_reply_2xx(sess, sess->msg, scode, reason, desc,
+ fmt, &ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Reject an incoming SIP Session connection
+ *
+ * @param sess SIP Session
+ * @param scode Response status code
+ * @param reason Response reason phrase
+ * @param fmt Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_reject(struct sipsess *sess, uint16_t scode, const char *reason,
+ const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ if (!sess || !sess->st || !sess->msg || scode < 300)
+ return EINVAL;
+
+ va_start(ap, fmt);
+ err = sip_treplyf(&sess->st, NULL, sess->sip, sess->msg, false,
+ scode, reason, fmt ? "%v" : NULL, fmt, &ap);
+ va_end(ap);
+
+ return err;
+}
diff --git a/src/sipsess/ack.c b/src/sipsess/ack.c
new file mode 100644
index 0000000..4a9be68
--- /dev/null
+++ b/src/sipsess/ack.c
@@ -0,0 +1,147 @@
+/**
+ * @file ack.c SIP Session ACK
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+struct sipsess_ack {
+ struct le he;
+ struct tmr tmr;
+ struct sa dst;
+ struct sip_request *req;
+ struct sip_dialog *dlg;
+ struct mbuf *mb;
+ enum sip_transp tp;
+ uint32_t cseq;
+};
+
+
+static void destructor(void *arg)
+{
+ struct sipsess_ack *ack = arg;
+
+ hash_unlink(&ack->he);
+ tmr_cancel(&ack->tmr);
+ mem_deref(ack->req);
+ mem_deref(ack->dlg);
+ mem_deref(ack->mb);
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct sipsess_ack *ack = arg;
+
+ mem_deref(ack);
+}
+
+
+static int send_handler(enum sip_transp tp, const struct sa *src,
+ const struct sa *dst, struct mbuf *mb, void *arg)
+{
+ struct sipsess_ack *ack = arg;
+ (void)src;
+
+ mem_deref(ack->mb);
+ ack->mb = mem_ref(mb);
+ ack->dst = *dst;
+ ack->tp = tp;
+
+ tmr_start(&ack->tmr, 64 * SIP_T1, tmr_handler, ack);
+
+ return 0;
+}
+
+
+static void resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct sipsess_ack *ack = arg;
+ (void)err;
+ (void)msg;
+
+ mem_deref(ack);
+}
+
+
+int sipsess_ack(struct sipsess_sock *sock, struct sip_dialog *dlg,
+ uint32_t cseq, struct sip_auth *auth,
+ const char *ctype, struct mbuf *desc)
+{
+ struct sipsess_ack *ack;
+ int err;
+
+ ack = mem_zalloc(sizeof(*ack), destructor);
+ if (!ack)
+ return ENOMEM;
+
+ hash_append(sock->ht_ack,
+ hash_joaat_str(sip_dialog_callid(dlg)),
+ &ack->he, ack);
+
+ ack->dlg = mem_ref(dlg);
+ ack->cseq = cseq;
+
+ err = sip_drequestf(&ack->req, sock->sip, false, "ACK", dlg, cseq,
+ auth, send_handler, resp_handler, ack,
+ "%s%s%s"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ desc ? "Content-Type: " : "",
+ desc ? ctype : "",
+ desc ? "\r\n" : "",
+ desc ? mbuf_get_left(desc) : (size_t)0,
+ desc ? mbuf_buf(desc) : NULL,
+ desc ? mbuf_get_left(desc) : (size_t)0);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(ack);
+
+ return err;
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+ struct sipsess_ack *ack = le->data;
+ const struct sip_msg *msg = arg;
+
+ if (!sip_dialog_cmp(ack->dlg, msg))
+ return false;
+
+ if (ack->cseq != msg->cseq.num)
+ return false;
+
+ return true;
+}
+
+
+int sipsess_ack_again(struct sipsess_sock *sock, const struct sip_msg *msg)
+{
+ struct sipsess_ack *ack;
+
+ ack = list_ledata(hash_lookup(sock->ht_ack,
+ hash_joaat_pl(&msg->callid),
+ cmp_handler, (void *)msg));
+ if (!ack)
+ return ENOENT;
+
+ return sip_send(sock->sip, NULL, ack->tp, &ack->dst, ack->mb);
+}
diff --git a/src/sipsess/close.c b/src/sipsess/close.c
new file mode 100644
index 0000000..b680a9a
--- /dev/null
+++ b/src/sipsess/close.c
@@ -0,0 +1,74 @@
+/**
+ * @file close.c SIP Session Close
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static void bye_resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct sipsess *sess = arg;
+
+ if (err || sip_request_loops(&sess->ls, msg->scode))
+ goto out;
+
+ if (msg->scode < 200) {
+ return;
+ }
+ else if (msg->scode < 300) {
+ ;
+ }
+ else {
+ if (sess->peerterm)
+ goto out;
+
+ switch (msg->scode) {
+
+ case 401:
+ case 407:
+ err = sip_auth_authenticate(sess->auth, msg);
+ if (err)
+ break;
+
+ err = sipsess_bye(sess, false);
+ if (err)
+ break;
+
+ return;
+ }
+ }
+
+ out:
+ mem_deref(sess);
+}
+
+
+int sipsess_bye(struct sipsess *sess, bool reset_ls)
+{
+ if (sess->req)
+ return EPROTO;
+
+ if (reset_ls)
+ sip_loopstate_reset(&sess->ls);
+
+ return sip_drequestf(&sess->req, sess->sip, true, "BYE",
+ sess->dlg, 0, sess->auth,
+ NULL, bye_resp_handler, sess,
+ "%s"
+ "Content-Length: 0\r\n"
+ "\r\n",
+ sess->close_hdrs);
+}
diff --git a/src/sipsess/connect.c b/src/sipsess/connect.c
new file mode 100644
index 0000000..5f6317e
--- /dev/null
+++ b/src/sipsess/connect.c
@@ -0,0 +1,245 @@
+/**
+ * @file connect.c SIP Session Connect
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static int invite(struct sipsess *sess);
+
+
+static int send_handler(enum sip_transp tp, const struct sa *src,
+ const struct sa *dst, struct mbuf *mb, void *arg)
+{
+ struct sip_contact contact;
+ struct sipsess *sess = arg;
+ (void)dst;
+
+ sip_contact_set(&contact, sess->cuser, src, tp);
+
+ return mbuf_printf(mb, "%H", sip_contact_print, &contact);
+}
+
+
+static void invite_resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct sipsess *sess = arg;
+ struct mbuf *desc = NULL;
+
+ if (err || sip_request_loops(&sess->ls, msg->scode))
+ goto out;
+
+ if (msg->scode < 200) {
+ sess->progrh(msg, sess->arg);
+ return;
+ }
+ else if (msg->scode < 300) {
+
+ sess->hdrs = mem_deref(sess->hdrs);
+
+ err = sip_dialog_create(sess->dlg, msg);
+ if (err)
+ goto out;
+
+ if (sess->sent_offer)
+ err = sess->answerh(msg, sess->arg);
+ else {
+ sess->modify_pending = false;
+ err = sess->offerh(&desc, msg, sess->arg);
+ }
+
+ err |= sipsess_ack(sess->sock, sess->dlg, msg->cseq.num,
+ sess->auth, sess->ctype, desc);
+
+ sess->established = true;
+ mem_deref(desc);
+
+ if (err || sess->terminated)
+ goto out;
+
+ if (sess->modify_pending)
+ (void)sipsess_reinvite(sess, true);
+ else
+ sess->desc = mem_deref(sess->desc);
+
+ sess->estabh(msg, sess->arg);
+ return;
+ }
+ else if (msg->scode < 400) {
+
+ /* Redirect to first Contact */
+
+ if (sess->terminated)
+ goto out;
+
+ err = sip_dialog_update(sess->dlg, msg);
+ if (err)
+ goto out;
+
+ err = invite(sess);
+ if (err)
+ goto out;
+
+ return;
+ }
+ else {
+ if (sess->terminated)
+ goto out;
+
+ switch (msg->scode) {
+
+ case 401:
+ case 407:
+ err = sip_auth_authenticate(sess->auth, msg);
+ if (err) {
+ err = (err == EAUTH) ? 0 : err;
+ break;
+ }
+
+ err = invite(sess);
+ if (err)
+ break;
+
+ return;
+ }
+ }
+
+ out:
+ if (!sess->terminated)
+ sipsess_terminate(sess, err, msg);
+ else
+ mem_deref(sess);
+}
+
+
+static int invite(struct sipsess *sess)
+{
+ sess->sent_offer = sess->desc ? true : false;
+ sess->modify_pending = false;
+
+ return sip_drequestf(&sess->req, sess->sip, true, "INVITE",
+ sess->dlg, 0, sess->auth,
+ send_handler, invite_resp_handler, sess,
+ "%b"
+ "%s%s%s"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ sess->hdrs ? mbuf_buf(sess->hdrs) : NULL,
+ sess->hdrs ? mbuf_get_left(sess->hdrs) :(size_t)0,
+ sess->desc ? "Content-Type: " : "",
+ sess->desc ? sess->ctype : "",
+ sess->desc ? "\r\n" : "",
+ sess->desc ? mbuf_get_left(sess->desc) :(size_t)0,
+ sess->desc ? mbuf_buf(sess->desc) : NULL,
+ sess->desc ? mbuf_get_left(sess->desc):(size_t)0);
+}
+
+
+/**
+ * Connect to a remote SIP useragent
+ *
+ * @param sessp Pointer to allocated SIP Session
+ * @param sock SIP Session socket
+ * @param to_uri To SIP uri
+ * @param from_name From display name
+ * @param from_uri From SIP uri
+ * @param cuser Contact username or URI
+ * @param routev Outbound route vector
+ * @param routec Outbound route vector count
+ * @param ctype Session content-type
+ * @param desc Content description (e.g. SDP)
+ * @param authh SIP Authentication handler
+ * @param aarg Authentication handler argument
+ * @param aref True to mem_ref() aarg
+ * @param offerh Session offer handler
+ * @param answerh Session answer handler
+ * @param progrh Session progress handler
+ * @param estabh Session established handler
+ * @param infoh Session info handler
+ * @param referh Session refer handler
+ * @param closeh Session close handler
+ * @param arg Handler argument
+ * @param fmt Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_connect(struct sipsess **sessp, struct sipsess_sock *sock,
+ const char *to_uri, const char *from_name,
+ const char *from_uri, const char *cuser,
+ const char *routev[], uint32_t routec,
+ const char *ctype, struct mbuf *desc,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsess_offer_h *offerh, sipsess_answer_h *answerh,
+ sipsess_progr_h *progrh, sipsess_estab_h *estabh,
+ sipsess_info_h *infoh, sipsess_refer_h *referh,
+ sipsess_close_h *closeh, void *arg, const char *fmt, ...)
+{
+ struct sipsess *sess;
+ int err;
+
+ if (!sessp || !sock || !to_uri || !from_uri || !cuser || !ctype)
+ return EINVAL;
+
+ err = sipsess_alloc(&sess, sock, cuser, ctype, desc, authh, aarg, aref,
+ offerh, answerh, progrh, estabh, infoh, referh,
+ closeh, arg);
+ if (err)
+ return err;
+
+ /* Custom SIP headers */
+ if (fmt) {
+ va_list ap;
+
+ sess->hdrs = mbuf_alloc(256);
+ if (!sess->hdrs) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ va_start(ap, fmt);
+ err = mbuf_vprintf(sess->hdrs, fmt, ap);
+ sess->hdrs->pos = 0;
+ va_end(ap);
+
+ if (err)
+ goto out;
+ }
+
+ sess->owner = true;
+
+ err = sip_dialog_alloc(&sess->dlg, to_uri, to_uri, from_name,
+ from_uri, routev, routec);
+ if (err)
+ goto out;
+
+ hash_append(sock->ht_sess,
+ hash_joaat_str(sip_dialog_callid(sess->dlg)),
+ &sess->he, sess);
+
+ err = invite(sess);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
diff --git a/src/sipsess/info.c b/src/sipsess/info.c
new file mode 100644
index 0000000..e30bc0d
--- /dev/null
+++ b/src/sipsess/info.c
@@ -0,0 +1,123 @@
+/**
+ * @file info.c SIP Session Info
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static int info_request(struct sipsess_request *req);
+
+
+static void info_resp_handler(int err, const struct sip_msg *msg, void *arg)
+{
+ struct sipsess_request *req = arg;
+
+ if (err || sip_request_loops(&req->ls, msg->scode))
+ goto out;
+
+ if (msg->scode < 200) {
+ return;
+ }
+ else if (msg->scode < 300) {
+ ;
+ }
+ else {
+ if (req->sess->terminated)
+ goto out;
+
+ switch (msg->scode) {
+
+ case 401:
+ case 407:
+ err = sip_auth_authenticate(req->sess->auth, msg);
+ if (err) {
+ err = (err == EAUTH) ? 0 : err;
+ break;
+ }
+
+ err = info_request(req);
+ if (err)
+ break;
+
+ return;
+
+ case 408:
+ case 481:
+ sipsess_terminate(req->sess, 0, msg);
+ break;
+ }
+ }
+
+ out:
+ if (!req->sess->terminated) {
+ if (err == ETIMEDOUT)
+ sipsess_terminate(req->sess, err, NULL);
+ else
+ req->resph(err, msg, req->arg);
+ }
+
+ mem_deref(req);
+}
+
+
+static int info_request(struct sipsess_request *req)
+{
+ return sip_drequestf(&req->req, req->sess->sip, true, "INFO",
+ req->sess->dlg, 0, req->sess->auth,
+ NULL, info_resp_handler, req,
+ "Content-Type: %s\r\n"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ req->ctype,
+ mbuf_get_left(req->body),
+ mbuf_buf(req->body), mbuf_get_left(req->body));
+}
+
+
+/**
+ * Send a SIP INFO request in the SIP Session
+ *
+ * @param sess SIP Session
+ * @param ctype Content-type
+ * @param body Content description (e.g. SDP)
+ * @param resph Response handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_info(struct sipsess *sess, const char *ctype, struct mbuf *body,
+ sip_resp_h *resph, void *arg)
+{
+ struct sipsess_request *req;
+ int err;
+
+ if (!sess || sess->terminated || !ctype || !body)
+ return EINVAL;
+
+ if (!sip_dialog_established(sess->dlg))
+ return ENOTCONN;
+
+ err = sipsess_request_alloc(&req, sess, ctype, body, resph, arg);
+ if (err)
+ return err;
+
+ err = info_request(req);
+ if (err)
+ mem_deref(req);
+
+ return err;
+}
diff --git a/src/sipsess/listen.c b/src/sipsess/listen.c
new file mode 100644
index 0000000..86a0ece
--- /dev/null
+++ b/src/sipsess/listen.c
@@ -0,0 +1,367 @@
+/**
+ * @file sipsess/listen.c SIP Session Listen
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static void destructor(void *arg)
+{
+ struct sipsess_sock *sock = arg;
+
+ mem_deref(sock->lsnr_resp);
+ mem_deref(sock->lsnr_req);
+ hash_flush(sock->ht_sess);
+ mem_deref(sock->ht_sess);
+ hash_flush(sock->ht_ack);
+ mem_deref(sock->ht_ack);
+}
+
+
+static void internal_connect_handler(const struct sip_msg *msg, void *arg)
+{
+ struct sipsess_sock *sock = arg;
+
+ (void)sip_treply(NULL, sock->sip, msg, 486, "Busy Here");
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+ struct sipsess *sess = le->data;
+ const struct sip_msg *msg = arg;
+
+ return sip_dialog_cmp(sess->dlg, msg);
+}
+
+
+static struct sipsess *sipsess_find(struct sipsess_sock *sock,
+ const struct sip_msg *msg)
+{
+ return list_ledata(hash_lookup(sock->ht_sess,
+ hash_joaat_pl(&msg->callid),
+ cmp_handler, (void *)msg));
+}
+
+
+static void info_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
+{
+ struct sip *sip = sock->sip;
+ struct sipsess *sess;
+
+ sess = sipsess_find(sock, msg);
+ if (!sess || sess->terminated) {
+ (void)sip_reply(sip, msg, 481, "Call Does Not Exist");
+ return;
+ }
+
+ if (!sip_dialog_rseq_valid(sess->dlg, msg)) {
+ (void)sip_reply(sip, msg, 500, "Server Internal Error");
+ return;
+ }
+
+ if (!sess->infoh) {
+ (void)sip_reply(sip, msg, 501, "Not Implemented");
+ return;
+ }
+
+ sess->infoh(sip, msg, sess->arg);
+}
+
+
+static void refer_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
+{
+ struct sip *sip = sock->sip;
+ struct sipsess *sess;
+
+ sess = sipsess_find(sock, msg);
+ if (!sess || sess->terminated) {
+ (void)sip_reply(sip, msg, 481, "Call Does Not Exist");
+ return;
+ }
+
+ if (!sip_dialog_rseq_valid(sess->dlg, msg)) {
+ (void)sip_reply(sip, msg, 500, "Server Internal Error");
+ return;
+ }
+
+ if (!sess->referh) {
+ (void)sip_reply(sip, msg, 501, "Not Implemented");
+ return;
+ }
+
+ sess->referh(sip, msg, sess->arg);
+}
+
+
+static void bye_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
+{
+ struct sip *sip = sock->sip;
+ struct sipsess *sess;
+
+ sess = sipsess_find(sock, msg);
+ if (!sess) {
+ (void)sip_reply(sip, msg, 481, "Call Does Not Exist");
+ return;
+ }
+
+ if (!sip_dialog_rseq_valid(sess->dlg, msg)) {
+ (void)sip_reply(sip, msg, 500, "Server Internal Error");
+ return;
+ }
+
+ (void)sip_treplyf(NULL, NULL, sip, msg, false, 200, "OK",
+ "%s"
+ "Content-Length: 0\r\n"
+ "\r\n",
+ sess->close_hdrs);
+
+ sess->peerterm = true;
+
+ if (sess->terminated)
+ return;
+
+ if (sess->st) {
+ (void)sip_treply(&sess->st, sess->sip, sess->msg,
+ 487, "Request Terminated");
+ }
+
+ sipsess_terminate(sess, ECONNRESET, NULL);
+}
+
+
+static void ack_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
+{
+ struct sipsess *sess;
+ bool awaiting_answer;
+ int err = 0;
+
+ sess = sipsess_find(sock, msg);
+ if (!sess)
+ return;
+
+ if (sipsess_reply_ack(sess, msg, &awaiting_answer))
+ return;
+
+ if (sess->terminated) {
+ if (!sess->replyl.head) {
+ sess->established = true;
+ mem_deref(sess);
+ }
+ return;
+ }
+
+ if (awaiting_answer) {
+ sess->awaiting_answer = false;
+ err = sess->answerh(msg, sess->arg);
+ }
+
+ if (sess->modify_pending && !sess->replyl.head)
+ (void)sipsess_reinvite(sess, true);
+
+ if (sess->established)
+ return;
+
+ sess->msg = mem_deref((void *)sess->msg);
+ sess->established = true;
+
+ if (err)
+ sipsess_terminate(sess, err, NULL);
+ else
+ sess->estabh(msg, sess->arg);
+}
+
+
+static void reinvite_handler(struct sipsess_sock *sock,
+ const struct sip_msg *msg)
+{
+ struct sip *sip = sock->sip;
+ struct sipsess *sess;
+ struct mbuf *desc;
+ char m[256];
+ int err;
+
+ sess = sipsess_find(sock, msg);
+ if (!sess || sess->terminated) {
+ (void)sip_treply(NULL, sip, msg, 481, "Call Does Not Exist");
+ return;
+ }
+
+ if (!sip_dialog_rseq_valid(sess->dlg, msg)) {
+ (void)sip_treply(NULL, sip, msg, 500, "Server Internal Error");
+ return;
+ }
+
+ if (sess->st || sess->awaiting_answer) {
+ (void)sip_treplyf(NULL, NULL, sip, msg, false,
+ 500, "Server Internal Error",
+ "Retry-After: 5\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n");
+ return;
+ }
+
+ if (sess->req) {
+ (void)sip_treply(NULL, sip, msg, 491, "Request Pending");
+ return;
+ }
+
+ err = sess->offerh(&desc, msg, sess->arg);
+ if (err) {
+ (void)sip_reply(sip, msg, 488, str_error(err, m, sizeof(m)));
+ return;
+ }
+
+ (void)sip_dialog_update(sess->dlg, msg);
+ (void)sipsess_reply_2xx(sess, msg, 200, "OK", desc,
+ NULL, NULL);
+
+ /* pending modifications considered outdated;
+ sdp may have changed in above exchange */
+ sess->desc = mem_deref(sess->desc);
+ sess->modify_pending = false;
+ tmr_cancel(&sess->tmr);
+ mem_deref(desc);
+}
+
+
+static void invite_handler(struct sipsess_sock *sock,
+ const struct sip_msg *msg)
+{
+ sock->connh(msg, sock->arg);
+}
+
+
+static bool request_handler(const struct sip_msg *msg, void *arg)
+{
+ struct sipsess_sock *sock = arg;
+
+ if (!pl_strcmp(&msg->met, "INVITE")) {
+
+ if (pl_isset(&msg->to.tag))
+ reinvite_handler(sock, msg);
+ else
+ invite_handler(sock, msg);
+
+ return true;
+ }
+ else if (!pl_strcmp(&msg->met, "ACK")) {
+ ack_handler(sock, msg);
+ return true;
+ }
+ else if (!pl_strcmp(&msg->met, "BYE")) {
+ bye_handler(sock, msg);
+ return true;
+ }
+ else if (!pl_strcmp(&msg->met, "INFO")) {
+ info_handler(sock, msg);
+ return true;
+ }
+ else if (!pl_strcmp(&msg->met, "REFER")) {
+
+ if (!pl_isset(&msg->to.tag))
+ return false;
+
+ refer_handler(sock, msg);
+ return true;
+ }
+
+ return false;
+}
+
+
+static bool response_handler(const struct sip_msg *msg, void *arg)
+{
+ struct sipsess_sock *sock = arg;
+
+ if (pl_strcmp(&msg->cseq.met, "INVITE"))
+ return false;
+
+ if (msg->scode < 200 || msg->scode > 299)
+ return false;
+
+ (void)sipsess_ack_again(sock, msg);
+
+ return true;
+}
+
+
+/**
+ * Listen to a SIP Session socket for incoming connections
+ *
+ * @param sockp Pointer to allocated SIP Session socket
+ * @param sip SIP Stack instance
+ * @param htsize Hashtable size
+ * @param connh Connection handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_listen(struct sipsess_sock **sockp, struct sip *sip,
+ int htsize, sipsess_conn_h *connh, void *arg)
+{
+ struct sipsess_sock *sock;
+ int err;
+
+ if (!sockp || !sip || !htsize)
+ return EINVAL;
+
+ sock = mem_zalloc(sizeof(*sock), destructor);
+ if (!sock)
+ return ENOMEM;
+
+ err = sip_listen(&sock->lsnr_resp, sip, false, response_handler, sock);
+ if (err)
+ goto out;
+
+ err = sip_listen(&sock->lsnr_req, sip, true, request_handler, sock);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&sock->ht_sess, htsize);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&sock->ht_ack, htsize);
+ if (err)
+ goto out;
+
+ sock->sip = sip;
+ sock->connh = connh ? connh : internal_connect_handler;
+ sock->arg = connh ? arg : sock;
+
+ out:
+ if (err)
+ mem_deref(sock);
+ else
+ *sockp = sock;
+
+ return err;
+}
+
+
+/**
+ * Close all SIP Sessions
+ *
+ * @param sock SIP Session socket
+ */
+void sipsess_close_all(struct sipsess_sock *sock)
+{
+ if (!sock)
+ return;
+
+ hash_flush(sock->ht_sess);
+}
diff --git a/src/sipsess/mod.mk b/src/sipsess/mod.mk
new file mode 100644
index 0000000..c2644e7
--- /dev/null
+++ b/src/sipsess/mod.mk
@@ -0,0 +1,16 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += sipsess/sess.c
+SRCS += sipsess/accept.c
+SRCS += sipsess/ack.c
+SRCS += sipsess/close.c
+SRCS += sipsess/connect.c
+SRCS += sipsess/info.c
+SRCS += sipsess/listen.c
+SRCS += sipsess/modify.c
+SRCS += sipsess/reply.c
+SRCS += sipsess/request.c
diff --git a/src/sipsess/modify.c b/src/sipsess/modify.c
new file mode 100644
index 0000000..452115f
--- /dev/null
+++ b/src/sipsess/modify.c
@@ -0,0 +1,171 @@
+/**
+ * @file modify.c SIP Session Modify
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static void tmr_handler(void *arg)
+{
+ struct sipsess *sess = arg;
+
+ (void)sipsess_reinvite(sess, true);
+}
+
+
+static void reinvite_resp_handler(int err, const struct sip_msg *msg,
+ void *arg)
+{
+ struct sipsess *sess = arg;
+ const struct sip_hdr *hdr;
+ struct mbuf *desc = NULL;
+
+ if (err || sip_request_loops(&sess->ls, msg->scode))
+ goto out;
+
+ if (msg->scode < 200) {
+ return;
+ }
+ else if (msg->scode < 300) {
+
+ (void)sip_dialog_update(sess->dlg, msg);
+
+ if (sess->sent_offer)
+ (void)sess->answerh(msg, sess->arg);
+ else {
+ sess->modify_pending = false;
+ (void)sess->offerh(&desc, msg, sess->arg);
+ }
+
+ (void)sipsess_ack(sess->sock, sess->dlg, msg->cseq.num,
+ sess->auth, sess->ctype, desc);
+ mem_deref(desc);
+ }
+ else {
+ if (sess->terminated)
+ goto out;
+
+ switch (msg->scode) {
+
+ case 401:
+ case 407:
+ err = sip_auth_authenticate(sess->auth, msg);
+ if (err) {
+ err = (err == EAUTH) ? 0 : err;
+ break;
+ }
+
+ err = sipsess_reinvite(sess, false);
+ if (err)
+ break;
+
+ return;
+
+ case 408:
+ case 481:
+ sipsess_terminate(sess, 0, msg);
+ return;
+
+ case 491:
+ tmr_start(&sess->tmr, sess->owner ? 3000 : 1000,
+ tmr_handler, sess);
+ return;
+
+ case 500:
+ hdr = sip_msg_hdr(msg, SIP_HDR_RETRY_AFTER);
+ if (!hdr)
+ break;
+
+ tmr_start(&sess->tmr, pl_u32(&hdr->val) * 1000,
+ tmr_handler, sess);
+ return;
+ }
+ }
+ out:
+ if (sess->terminated)
+ mem_deref(sess);
+ else if (err == ETIMEDOUT)
+ sipsess_terminate(sess, err, NULL);
+ else if (sess->modify_pending)
+ (void)sipsess_reinvite(sess, true);
+ else
+ sess->desc = mem_deref(sess->desc);
+}
+
+
+static int send_handler(enum sip_transp tp, const struct sa *src,
+ const struct sa *dst, struct mbuf *mb, void *arg)
+{
+ struct sip_contact contact;
+ struct sipsess *sess = arg;
+ (void)dst;
+
+ sip_contact_set(&contact, sess->cuser, src, tp);
+
+ return mbuf_printf(mb, "%H", sip_contact_print, &contact);
+}
+
+
+int sipsess_reinvite(struct sipsess *sess, bool reset_ls)
+{
+ if (sess->req)
+ return EPROTO;
+
+ sess->sent_offer = sess->desc ? true : false;
+ sess->modify_pending = false;
+
+ if (reset_ls)
+ sip_loopstate_reset(&sess->ls);
+
+ return sip_drequestf(&sess->req, sess->sip, true, "INVITE",
+ sess->dlg, 0, sess->auth,
+ send_handler, reinvite_resp_handler, sess,
+ "%s%s%s"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ sess->desc ? "Content-Type: " : "",
+ sess->desc ? sess->ctype : "",
+ sess->desc ? "\r\n" : "",
+ sess->desc ? mbuf_get_left(sess->desc) :(size_t)0,
+ sess->desc ? mbuf_buf(sess->desc) : NULL,
+ sess->desc ? mbuf_get_left(sess->desc):(size_t)0);
+}
+
+
+/**
+ * Modify an established SIP Session sending Re-INVITE or UPDATE
+ *
+ * @param sess SIP Session
+ * @param desc Content description (e.g. SDP)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_modify(struct sipsess *sess, struct mbuf *desc)
+{
+ if (!sess || sess->st || sess->terminated)
+ return EINVAL;
+
+ mem_deref(sess->desc);
+ sess->desc = mem_ref(desc);
+
+ if (sess->req || sess->tmr.th || sess->replyl.head) {
+ sess->modify_pending = true;
+ return 0;
+ }
+
+ return sipsess_reinvite(sess, true);
+}
diff --git a/src/sipsess/reply.c b/src/sipsess/reply.c
new file mode 100644
index 0000000..4c3aa6f
--- /dev/null
+++ b/src/sipsess/reply.c
@@ -0,0 +1,161 @@
+/**
+ * @file sipsess/reply.c SIP Session Reply
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+struct sipsess_reply {
+ struct le le;
+ struct tmr tmr;
+ struct tmr tmrg;
+ const struct sip_msg *msg;
+ struct mbuf *mb;
+ struct sipsess *sess;
+ bool awaiting_answer;
+ uint32_t seq;
+ uint32_t txc;
+};
+
+
+static void destructor(void *arg)
+{
+ struct sipsess_reply *reply = arg;
+
+ list_unlink(&reply->le);
+ tmr_cancel(&reply->tmr);
+ tmr_cancel(&reply->tmrg);
+ mem_deref((void *)reply->msg);
+ mem_deref(reply->mb);
+}
+
+
+static void tmr_handler(void *arg)
+{
+ struct sipsess_reply *reply = arg;
+ struct sipsess *sess = reply->sess;
+
+ mem_deref(reply);
+
+ /* wait for all pending ACKs */
+ if (sess->replyl.head)
+ return;
+
+ /* we want to send bye */
+ sess->established = true;
+
+ if (!sess->terminated)
+ sipsess_terminate(sess, ETIMEDOUT, NULL);
+ else
+ mem_deref(sess);
+}
+
+
+static void retransmit_handler(void *arg)
+{
+ struct sipsess_reply *reply = arg;
+
+ (void)sip_send(reply->sess->sip, reply->msg->sock, reply->msg->tp,
+ &reply->msg->src, reply->mb);
+
+ reply->txc++;
+ tmr_start(&reply->tmrg, MIN(SIP_T1<<reply->txc, SIP_T2),
+ retransmit_handler, reply);
+}
+
+
+int sipsess_reply_2xx(struct sipsess *sess, const struct sip_msg *msg,
+ uint16_t scode, const char *reason, struct mbuf *desc,
+ const char *fmt, va_list *ap)
+{
+ struct sipsess_reply *reply;
+ struct sip_contact contact;
+ int err = ENOMEM;
+
+ reply = mem_zalloc(sizeof(*reply), destructor);
+ if (!reply)
+ goto out;
+
+ list_append(&sess->replyl, &reply->le, reply);
+ reply->seq = msg->cseq.num;
+ reply->msg = mem_ref((void *)msg);
+ reply->sess = sess;
+
+ sip_contact_set(&contact, sess->cuser, &msg->dst, msg->tp);
+
+ err = sip_treplyf(&sess->st, &reply->mb, sess->sip,
+ msg, true, scode, reason,
+ "%H"
+ "%v"
+ "%s%s%s"
+ "Content-Length: %zu\r\n"
+ "\r\n"
+ "%b",
+ sip_contact_print, &contact,
+ fmt, ap,
+ desc ? "Content-Type: " : "",
+ desc ? sess->ctype : "",
+ desc ? "\r\n" : "",
+ desc ? mbuf_get_left(desc) : (size_t)0,
+ desc ? mbuf_buf(desc) : NULL,
+ desc ? mbuf_get_left(desc) : (size_t)0);
+
+ if (err)
+ goto out;
+
+ tmr_start(&reply->tmr, 64 * SIP_T1, tmr_handler, reply);
+ tmr_start(&reply->tmrg, SIP_T1, retransmit_handler, reply);
+
+ if (!mbuf_get_left(msg->mb) && desc) {
+ reply->awaiting_answer = true;
+ sess->awaiting_answer = true;
+ }
+
+ out:
+ if (err) {
+ sess->st = mem_deref(sess->st);
+ mem_deref(reply);
+ }
+
+ return err;
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+ struct sipsess_reply *reply = le->data;
+ const struct sip_msg *msg = arg;
+
+ return msg->cseq.num == reply->seq;
+}
+
+
+int sipsess_reply_ack(struct sipsess *sess, const struct sip_msg *msg,
+ bool *awaiting_answer)
+{
+ struct sipsess_reply *reply;
+
+ reply = list_ledata(list_apply(&sess->replyl, false, cmp_handler,
+ (void *)msg));
+ if (!reply)
+ return ENOENT;
+
+ *awaiting_answer = reply->awaiting_answer;
+
+ mem_deref(reply);
+
+ return 0;
+}
diff --git a/src/sipsess/request.c b/src/sipsess/request.c
new file mode 100644
index 0000000..cd7384a
--- /dev/null
+++ b/src/sipsess/request.c
@@ -0,0 +1,79 @@
+/**
+ * @file sipsess/request.c SIP Session Non-INVITE Request
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static void destructor(void *arg)
+{
+ struct sipsess_request *req = arg;
+
+ list_unlink(&req->le);
+ mem_deref(req->ctype);
+ mem_deref(req->body);
+ mem_deref(req->req);
+
+ /* wait for pending requests */
+ if (req->sess->terminated && !req->sess->requestl.head)
+ mem_deref(req->sess);
+}
+
+
+static void internal_resp_handler(int err, const struct sip_msg *msg,
+ void *arg)
+{
+ (void)err;
+ (void)msg;
+ (void)arg;
+}
+
+
+int sipsess_request_alloc(struct sipsess_request **reqp, struct sipsess *sess,
+ const char *ctype, struct mbuf *body,
+ sip_resp_h *resph, void *arg)
+{
+ struct sipsess_request *req;
+ int err = 0;
+
+ if (!reqp || !sess || sess->terminated)
+ return EINVAL;
+
+ req = mem_zalloc(sizeof(*req), destructor);
+ if (!req)
+ return ENOMEM;
+
+ list_append(&sess->requestl, &req->le, req);
+
+ if (ctype) {
+ err = str_dup(&req->ctype, ctype);
+ if (err)
+ goto out;
+ }
+
+ req->sess = sess;
+ req->body = mem_ref(body);
+ req->resph = resph ? resph : internal_resp_handler;
+ req->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(req);
+ else
+ *reqp = req;
+
+ return 0;
+}
diff --git a/src/sipsess/sess.c b/src/sipsess/sess.c
new file mode 100644
index 0000000..ff505eb
--- /dev/null
+++ b/src/sipsess/sess.c
@@ -0,0 +1,264 @@
+/**
+ * @file sipsess/sess.c SIP Session Core
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+#include <re_tmr.h>
+#include <re_msg.h>
+#include <re_sip.h>
+#include <re_sipsess.h>
+#include "sipsess.h"
+
+
+static int internal_offer_handler(struct mbuf **descp,
+ const struct sip_msg *msg, void *arg)
+{
+ (void)descp;
+ (void)msg;
+ (void)arg;
+
+ return ENOSYS;
+}
+
+
+static int internal_answer_handler(const struct sip_msg *msg, void *arg)
+{
+ (void)msg;
+ (void)arg;
+
+ return ENOSYS;
+}
+
+
+static void internal_progress_handler(const struct sip_msg *msg, void *arg)
+{
+ (void)msg;
+ (void)arg;
+}
+
+
+static void internal_establish_handler(const struct sip_msg *msg, void *arg)
+{
+ (void)msg;
+ (void)arg;
+}
+
+
+static void internal_close_handler(int err, const struct sip_msg *msg,
+ void *arg)
+{
+ (void)err;
+ (void)msg;
+ (void)arg;
+}
+
+
+static bool termwait(struct sipsess *sess)
+{
+ bool wait = false;
+
+ sess->terminated = 1;
+ sess->offerh = internal_offer_handler;
+ sess->answerh = internal_answer_handler;
+ sess->progrh = internal_progress_handler;
+ sess->estabh = internal_establish_handler;
+ sess->infoh = NULL;
+ sess->referh = NULL;
+ sess->closeh = internal_close_handler;
+ sess->arg = sess;
+
+ tmr_cancel(&sess->tmr);
+
+ if (sess->st) {
+ (void)sip_treply(&sess->st, sess->sip, sess->msg,
+ 486, "Busy Here");
+ }
+
+ if (sess->req) {
+ sip_request_cancel(sess->req);
+ mem_ref(sess);
+ wait = true;
+ }
+
+ if (sess->replyl.head) {
+ mem_ref(sess);
+ wait = true;
+ }
+
+ if (sess->requestl.head) {
+ mem_ref(sess);
+ wait = true;
+ }
+
+ return wait;
+}
+
+
+static bool terminate(struct sipsess *sess)
+{
+ sess->terminated = 2;
+
+ if (!sess->established || sess->peerterm)
+ return false;
+
+ if (sipsess_bye(sess, true))
+ return false;
+
+ mem_ref(sess);
+ return true;
+}
+
+
+static void destructor(void *arg)
+{
+ struct sipsess *sess = arg;
+
+ switch (sess->terminated) {
+
+ case 0:
+ if (termwait(sess))
+ return;
+
+ /*@fallthrough@*/
+
+ case 1:
+ if (terminate(sess))
+ return;
+ break;
+ }
+
+ hash_unlink(&sess->he);
+ tmr_cancel(&sess->tmr);
+ list_flush(&sess->replyl);
+ list_flush(&sess->requestl);
+ mem_deref((void *)sess->msg);
+ mem_deref(sess->req);
+ mem_deref(sess->dlg);
+ mem_deref(sess->auth);
+ mem_deref(sess->cuser);
+ mem_deref(sess->ctype);
+ mem_deref(sess->close_hdrs);
+ mem_deref(sess->hdrs);
+ mem_deref(sess->desc);
+ mem_deref(sess->sock);
+ mem_deref(sess->sip);
+ mem_deref(sess->st);
+}
+
+
+int sipsess_alloc(struct sipsess **sessp, struct sipsess_sock *sock,
+ const char *cuser, const char *ctype, struct mbuf *desc,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsess_offer_h *offerh, sipsess_answer_h *answerh,
+ sipsess_progr_h *progrh, sipsess_estab_h *estabh,
+ sipsess_info_h *infoh, sipsess_refer_h *referh,
+ sipsess_close_h *closeh, void *arg)
+{
+ struct sipsess *sess;
+ int err;
+
+ sess = mem_zalloc(sizeof(*sess), destructor);
+ if (!sess)
+ return ENOMEM;
+
+ err = sip_auth_alloc(&sess->auth, authh, aarg, aref);
+ if (err)
+ goto out;
+
+ err = str_dup(&sess->cuser, cuser);
+ if (err)
+ goto out;
+
+ err = str_dup(&sess->ctype, ctype);
+ if (err)
+ goto out;
+
+ sess->sock = mem_ref(sock);
+ sess->desc = mem_ref(desc);
+ sess->sip = mem_ref(sock->sip);
+ sess->offerh = offerh ? offerh : internal_offer_handler;
+ sess->answerh = answerh ? answerh : internal_answer_handler;
+ sess->progrh = progrh ? progrh : internal_progress_handler;
+ sess->estabh = estabh ? estabh : internal_establish_handler;
+ sess->infoh = infoh;
+ sess->referh = referh;
+ sess->closeh = closeh ? closeh : internal_close_handler;
+ sess->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+void sipsess_terminate(struct sipsess *sess, int err,
+ const struct sip_msg *msg)
+{
+ sipsess_close_h *closeh;
+ void *arg;
+
+ if (sess->terminated)
+ return;
+
+ closeh = sess->closeh;
+ arg = sess->arg;
+
+ if (!termwait(sess))
+ (void)terminate(sess);
+
+ closeh(err, msg, arg);
+}
+
+
+/**
+ * Get the SIP dialog from a SIP Session
+ *
+ * @param sess SIP Session
+ *
+ * @return SIP Dialog object
+ */
+struct sip_dialog *sipsess_dialog(const struct sipsess *sess)
+{
+ return sess ? sess->dlg : NULL;
+}
+
+
+/**
+ * Set extra SIP headers for inclusion in Session "close" messages
+ * like BYE and 200 OK. Multiple headers can be included.
+ *
+ * @param sess SIP Session
+ * @param hdrs Formatted strings with extra SIP Headers
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sipsess_set_close_headers(struct sipsess *sess, const char *hdrs, ...)
+{
+ int err = 0;
+ va_list ap;
+
+ if (!sess)
+ return EINVAL;
+
+ sess->close_hdrs = mem_deref(sess->close_hdrs);
+
+ if (hdrs) {
+ va_start(ap, hdrs);
+ err = re_vsdprintf(&sess->close_hdrs, hdrs, ap);
+ va_end(ap);
+ }
+
+ return err;
+}
diff --git a/src/sipsess/sipsess.h b/src/sipsess/sipsess.h
new file mode 100644
index 0000000..3bdfcc7
--- /dev/null
+++ b/src/sipsess/sipsess.h
@@ -0,0 +1,89 @@
+/**
+ * @file sipsess.h SIP Session Private Interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct sipsess {
+ struct le he;
+ struct tmr tmr;
+ struct list replyl;
+ struct list requestl;
+ struct sip_loopstate ls;
+ struct sipsess_sock *sock;
+ const struct sip_msg *msg;
+ struct sip_request *req;
+ struct sip_dialog *dlg;
+ struct sip_strans *st;
+ struct sip_auth *auth;
+ struct sip *sip;
+ char *cuser;
+ char *ctype;
+ char *close_hdrs;
+ struct mbuf *hdrs;
+ struct mbuf *desc;
+ sipsess_offer_h *offerh;
+ sipsess_answer_h *answerh;
+ sipsess_progr_h *progrh;
+ sipsess_estab_h *estabh;
+ sipsess_info_h *infoh;
+ sipsess_refer_h *referh;
+ sipsess_close_h *closeh;
+ void *arg;
+ bool owner;
+ bool sent_offer;
+ bool awaiting_answer;
+ bool modify_pending;
+ bool established;
+ bool peerterm;
+ int terminated;
+};
+
+
+struct sipsess_sock {
+ struct sip_lsnr *lsnr_resp;
+ struct sip_lsnr *lsnr_req;
+ struct hash *ht_sess;
+ struct hash *ht_ack;
+ struct sip *sip;
+ sipsess_conn_h *connh;
+ void *arg;
+};
+
+
+struct sipsess_request {
+ struct le le;
+ struct sip_loopstate ls;
+ struct sipsess *sess;
+ struct sip_request *req;
+ char *ctype;
+ struct mbuf *body;
+ sip_resp_h *resph;
+ void *arg;
+};
+
+
+int sipsess_alloc(struct sipsess **sessp, struct sipsess_sock *sock,
+ const char *cuser, const char *ctype, struct mbuf *desc,
+ sip_auth_h *authh, void *aarg, bool aref,
+ sipsess_offer_h *offerh, sipsess_answer_h *answerh,
+ sipsess_progr_h *progrh, sipsess_estab_h *estabh,
+ sipsess_info_h *infoh, sipsess_refer_h *referh,
+ sipsess_close_h *closeh, void *arg);
+void sipsess_terminate(struct sipsess *sess, int err,
+ const struct sip_msg *msg);
+int sipsess_ack(struct sipsess_sock *sock, struct sip_dialog *dlg,
+ uint32_t cseq, struct sip_auth *auth,
+ const char *ctype, struct mbuf *desc);
+int sipsess_ack_again(struct sipsess_sock *sock, const struct sip_msg *msg);
+int sipsess_reply_2xx(struct sipsess *sess, const struct sip_msg *msg,
+ uint16_t scode, const char *reason, struct mbuf *desc,
+ const char *fmt, va_list *ap);
+int sipsess_reply_ack(struct sipsess *sess, const struct sip_msg *msg,
+ bool *awaiting_answer);
+int sipsess_reinvite(struct sipsess *sess, bool reset_ls);
+int sipsess_bye(struct sipsess *sess, bool reset_ls);
+int sipsess_request_alloc(struct sipsess_request **reqp, struct sipsess *sess,
+ const char *ctype, struct mbuf *body,
+ sip_resp_h *resph, void *arg);
diff --git a/src/srtp/README b/src/srtp/README
new file mode 100644
index 0000000..bd4b980
--- /dev/null
+++ b/src/srtp/README
@@ -0,0 +1,42 @@
+SRTP module
+-----------
+
+The SRTP module implements Secure RTP as defined in RFC 3711.
+It provides a clean and user friendly API and can be used
+as a standalone module.
+
+
+
+
+Requirements and features:
+
+RFC 3711 yes
+RFC 6188 yes
+Multiple Master keys: no
+Key derivation rate: 0 (zero)
+Salting keys: yes
+SRTP protection: yes
+SRTCP protection: yes
+Replay protection: yes
+Encryption: yes
+Authentication: yes
+MKI (Master Key Identifier): no
+Authentication tag length: 32-bit and 80-bit
+ROC (Roll Over Counter): yes
+Master key lifetime: no
+Multiple SSRCs: yes
+Performance: better than libsrtp
+
+Cryptographic transforms:
+- AES in Counter mode: yes
+- AES in f8-mode: no
+- NULL Cipher: no
+
+Authentication transform:
+- HMAC-SHA1: yes
+- NULL auth: no
+
+master key lengths:
+- 128 bits yes
+- 192 bits no
+- 256 bits yes
diff --git a/src/srtp/misc.c b/src/srtp/misc.c
new file mode 100644
index 0000000..8b4d5c3
--- /dev/null
+++ b/src/srtp/misc.c
@@ -0,0 +1,120 @@
+/**
+ * @file srtp/misc.c SRTP functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_aes.h>
+#include <re_sa.h>
+#include <re_srtp.h>
+#include "srtp.h"
+
+
+/*
+ * Appendix A: Pseudocode for Index Determination
+ *
+ * In the following, signed arithmetic is assumed.
+ */
+uint64_t srtp_get_index(uint32_t roc, uint16_t s_l, uint16_t seq)
+{
+ int v;
+
+ if (s_l < 32768) {
+
+ if ((int)seq - (int)s_l > 32768)
+ v = (roc-1) & 0xffffffffu;
+ else
+ v = roc;
+ }
+ else {
+ if ((int)s_l - 32768 > seq)
+ v = (roc+1) & 0xffffffffu;
+ else
+ v = roc;
+ }
+
+ return seq + v*65536;
+}
+
+
+int srtp_derive(uint8_t *out, size_t out_len, uint8_t label,
+ const uint8_t *master_key, size_t key_bytes,
+ const uint8_t *master_salt, size_t salt_bytes)
+{
+ uint8_t x[AES_BLOCK_SIZE] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+ static const uint8_t null[AES_BLOCK_SIZE * 2];
+ struct aes *aes;
+ int err;
+
+ if (!out || !master_key || !master_salt)
+ return EINVAL;
+
+ if (out_len > sizeof(null) || salt_bytes > sizeof(x))
+ return EINVAL;
+
+ memcpy(x, master_salt, salt_bytes);
+ x[7] ^= label;
+
+ /* NOTE: Counter Mode is used for both CTR and GCM */
+ err = aes_alloc(&aes, AES_MODE_CTR, master_key, key_bytes*8, x);
+ if (err)
+ return err;
+
+ err = aes_encr(aes, out, null, out_len);
+
+ mem_deref(aes);
+
+ return err;
+
+}
+
+
+void srtp_iv_calc(union vect128 *iv, const union vect128 *k_s,
+ uint32_t ssrc, uint64_t ix)
+{
+ if (!iv || !k_s)
+ return;
+
+ iv->u32[0] = k_s->u32[0];
+ iv->u32[1] = k_s->u32[1] ^ htonl(ssrc);
+ iv->u32[2] = k_s->u32[2] ^ htonl((uint32_t)(ix>>16));
+ iv->u16[6] = k_s->u16[6] ^ htons((uint16_t)ix);
+ iv->u16[7] = 0;
+}
+
+
+/*
+ * NOTE: The IV for AES-GCM is 12 bytes
+ */
+void srtp_iv_calc_gcm(union vect128 *iv, const union vect128 *k_s,
+ uint32_t ssrc, uint64_t ix)
+{
+ if (!iv || !k_s)
+ return;
+
+ iv->u16[0] = k_s->u16[0];
+ iv->u16[1] = k_s->u16[1] ^ htons(ssrc >> 16);
+ iv->u16[2] = k_s->u16[2] ^ htons(ssrc & 0xffff);
+ iv->u16[3] = k_s->u16[3] ^ htons((ix >> 32) & 0xffff);
+ iv->u16[4] = k_s->u16[4] ^ htons((ix >> 16) & 0xffff);
+ iv->u16[5] = k_s->u16[5] ^ htons(ix & 0xffff);
+}
+
+
+const char *srtp_suite_name(enum srtp_suite suite)
+{
+ switch (suite) {
+
+ case SRTP_AES_CM_128_HMAC_SHA1_32: return "AES_CM_128_HMAC_SHA1_32";
+ case SRTP_AES_CM_128_HMAC_SHA1_80: return "AES_CM_128_HMAC_SHA1_80";
+ case SRTP_AES_256_CM_HMAC_SHA1_32: return "AES_256_CM_HMAC_SHA1_32";
+ case SRTP_AES_256_CM_HMAC_SHA1_80: return "AES_256_CM_HMAC_SHA1_80";
+ case SRTP_AES_128_GCM: return "AEAD_AES_128_GCM";
+ case SRTP_AES_256_GCM: return "AEAD_AES_256_GCM";
+ default: return "?";
+ }
+}
diff --git a/src/srtp/mod.mk b/src/srtp/mod.mk
new file mode 100644
index 0000000..1494c8b
--- /dev/null
+++ b/src/srtp/mod.mk
@@ -0,0 +1,11 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += srtp/misc.c
+SRCS += srtp/replay.c
+SRCS += srtp/srtcp.c
+SRCS += srtp/srtp.c
+SRCS += srtp/stream.c
diff --git a/src/srtp/replay.c b/src/srtp/replay.c
new file mode 100644
index 0000000..f6d16d9
--- /dev/null
+++ b/src/srtp/replay.c
@@ -0,0 +1,64 @@
+/**
+ * @file srtp/replay.c SRTP replay protection
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_aes.h>
+#include <re_srtp.h>
+#include "srtp.h"
+
+
+enum {
+ SRTP_WINDOW_SIZE = 64
+};
+
+
+void srtp_replay_init(struct replay *replay)
+{
+ if (!replay)
+ return;
+
+ replay->bitmap = 0;
+ replay->lix = 0;
+}
+
+
+/*
+ * Returns false if packet disallowed, true if packet permitted
+ */
+bool srtp_replay_check(struct replay *replay, uint64_t ix)
+{
+ uint64_t diff;
+
+ if (!replay)
+ return false;
+
+ if (ix > replay->lix) {
+ diff = ix - replay->lix;
+
+ if (diff < SRTP_WINDOW_SIZE) { /* In window */
+ replay->bitmap <<= diff;
+ replay->bitmap |= 1; /* set bit for this packet */
+ }
+ else
+ replay->bitmap = 1;
+
+ replay->lix = ix;
+ return true;
+ }
+
+ diff = replay->lix - ix;
+ if (diff >= SRTP_WINDOW_SIZE)
+ return false;
+
+ if (replay->bitmap & (1ULL << diff))
+ return false; /* already seen */
+
+ /* mark as seen */
+ replay->bitmap |= (1ULL << diff);
+
+ return true;
+}
diff --git a/src/srtp/srtcp.c b/src/srtp/srtcp.c
new file mode 100644
index 0000000..d677898
--- /dev/null
+++ b/src/srtp/srtcp.c
@@ -0,0 +1,254 @@
+/**
+ * @file srtcp.c Secure Real-time Transport Control Protocol (SRTCP)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hmac.h>
+#include <re_sha.h>
+#include <re_aes.h>
+#include <re_net.h>
+#include <re_srtp.h>
+#include "srtp.h"
+
+
+static int get_rtcp_ssrc(uint32_t *ssrc, struct mbuf *mb)
+{
+ if (mbuf_get_left(mb) < 8)
+ return EBADMSG;
+
+ mbuf_advance(mb, 4);
+ *ssrc = ntohl(mbuf_read_u32(mb));
+
+ return 0;
+}
+
+
+int srtcp_encrypt(struct srtp *srtp, struct mbuf *mb)
+{
+ struct srtp_stream *strm;
+ struct comp *rtcp;
+ uint32_t ssrc;
+ size_t start;
+ uint32_t ep = 0;
+ int err;
+
+ if (!srtp || !mb)
+ return EINVAL;
+
+ rtcp = &srtp->rtcp;
+ start = mb->pos;
+
+ err = get_rtcp_ssrc(&ssrc, mb);
+ if (err)
+ return err;
+
+ err = stream_get(&strm, srtp, ssrc);
+ if (err)
+ return err;
+
+ strm->rtcp_index = (strm->rtcp_index+1) & 0x7fffffff;
+
+ if (rtcp->aes && rtcp->mode == AES_MODE_CTR) {
+ union vect128 iv;
+ uint8_t *p = mbuf_buf(mb);
+
+ srtp_iv_calc(&iv, &rtcp->k_s, ssrc, strm->rtcp_index);
+
+ aes_set_iv(rtcp->aes, iv.u8);
+ err = aes_encr(rtcp->aes, p, p, mbuf_get_left(mb));
+ if (err)
+ return err;
+
+ ep = 1;
+ }
+ else if (rtcp->aes && rtcp->mode == AES_MODE_GCM) {
+
+ union vect128 iv;
+ uint8_t *p = mbuf_buf(mb);
+ uint8_t tag[GCM_TAGLEN];
+ const uint32_t ix_be = htonl(1<<31 | strm->rtcp_index);
+
+ srtp_iv_calc_gcm(&iv, &rtcp->k_s, ssrc, strm->rtcp_index);
+
+ aes_set_iv(rtcp->aes, iv.u8);
+
+ /* The RTCP Header and Index is Associated Data */
+ err = aes_encr(rtcp->aes, NULL, &mb->buf[start],
+ mb->pos - start);
+ err |= aes_encr(rtcp->aes, NULL,
+ (void *)&ix_be, sizeof(ix_be));
+ if (err)
+ return err;
+
+ err = aes_encr(rtcp->aes, p, p, mbuf_get_left(mb));
+ if (err)
+ return err;
+
+ err = aes_get_authtag(rtcp->aes, tag, sizeof(tag));
+ if (err)
+ return err;
+
+ mb->pos = mb->end;
+ err = mbuf_write_mem(mb, tag, sizeof(tag));
+ if (err)
+ return err;
+
+ ep = 1;
+ }
+
+ /* append E-bit and SRTCP-index */
+ mb->pos = mb->end;
+ err = mbuf_write_u32(mb, htonl(ep<<31 | strm->rtcp_index));
+ if (err)
+ return err;
+
+ if (rtcp->hmac) {
+ uint8_t tag[SHA_DIGEST_LENGTH];
+
+ mb->pos = start;
+
+ err = hmac_digest(rtcp->hmac, tag, sizeof(tag),
+ mbuf_buf(mb), mbuf_get_left(mb));
+ if (err)
+ return err;
+
+ mb->pos = mb->end;
+
+ err = mbuf_write_mem(mb, tag, rtcp->tag_len);
+ if (err)
+ return err;
+ }
+
+ mb->pos = start;
+
+ return 0;
+}
+
+
+int srtcp_decrypt(struct srtp *srtp, struct mbuf *mb)
+{
+ size_t start, eix_start, pld_start;
+ struct srtp_stream *strm;
+ struct comp *rtcp;
+ uint32_t v, ix;
+ uint32_t ssrc;
+ bool ep;
+ int err;
+
+ if (!srtp || !mb)
+ return EINVAL;
+
+ rtcp = &srtp->rtcp;
+ start = mb->pos;
+
+ err = get_rtcp_ssrc(&ssrc, mb);
+ if (err)
+ return err;
+
+ err = stream_get(&strm, srtp, ssrc);
+ if (err)
+ return err;
+
+ pld_start = mb->pos;
+
+ if (mbuf_get_left(mb) < (4 + rtcp->tag_len))
+ return EBADMSG;
+
+ /* Read out E-Bit, SRTCP-index and Authentication Tag */
+ eix_start = mb->end - (4 + rtcp->tag_len);
+ mb->pos = eix_start;
+ v = ntohl(mbuf_read_u32(mb));
+
+ ep = (v >> 31) & 1;
+ ix = v & 0x7fffffff;
+
+ if (rtcp->hmac) {
+ uint8_t tag[SHA_DIGEST_LENGTH], tag_pkt[SHA_DIGEST_LENGTH];
+ const size_t tag_start = mb->pos;
+
+ err = mbuf_read_mem(mb, tag_pkt, rtcp->tag_len);
+ if (err)
+ return err;
+
+ mb->pos = start;
+ mb->end = tag_start;
+
+ err = hmac_digest(rtcp->hmac, tag, sizeof(tag),
+ mbuf_buf(mb), mbuf_get_left(mb));
+ if (err)
+ return err;
+
+ if (0 != memcmp(tag, tag_pkt, rtcp->tag_len))
+ return EAUTH;
+
+ /*
+ * SRTCP replay protection is as defined in Section 3.3.2,
+ * but using the SRTCP index as the index i and a separate
+ * Replay List that is specific to SRTCP.
+ */
+ if (!srtp_replay_check(&strm->replay_rtcp, ix))
+ return EALREADY;
+ }
+
+ mb->end = eix_start;
+
+ if (rtcp->aes && ep && rtcp->mode == AES_MODE_CTR) {
+ union vect128 iv;
+ uint8_t *p;
+
+ mb->pos = pld_start;
+ p = mbuf_buf(mb);
+
+ srtp_iv_calc(&iv, &rtcp->k_s, ssrc, ix);
+
+ aes_set_iv(rtcp->aes, iv.u8);
+ err = aes_decr(rtcp->aes, p, p, mbuf_get_left(mb));
+ if (err)
+ return err;
+ }
+ else if (rtcp->aes && ep && rtcp->mode == AES_MODE_GCM) {
+
+ union vect128 iv;
+ size_t tag_start;
+ uint8_t *p;
+
+ srtp_iv_calc_gcm(&iv, &rtcp->k_s, ssrc, ix);
+
+ aes_set_iv(rtcp->aes, iv.u8);
+
+ /* The RTP Header is Associated Data */
+ err = aes_decr(rtcp->aes, NULL, &mb->buf[start],
+ pld_start - start);
+ err |= aes_decr(rtcp->aes, NULL, &mb->buf[eix_start], 4);
+ if (err)
+ return err;
+
+ mb->pos = pld_start;
+ p = mbuf_buf(mb);
+
+ if (mbuf_get_left(mb) < GCM_TAGLEN)
+ return EBADMSG;
+
+ tag_start = mb->end - GCM_TAGLEN;
+
+ err = aes_decr(rtcp->aes, p, p, tag_start - pld_start);
+ if (err)
+ return err;
+
+ err = aes_authenticate(rtcp->aes, &mb->buf[tag_start],
+ GCM_TAGLEN);
+ if (err)
+ return err;
+
+ mb->end = tag_start;
+ }
+
+ mb->pos = start;
+
+ return 0;
+}
diff --git a/src/srtp/srtp.c b/src/srtp/srtp.c
new file mode 100644
index 0000000..7625126
--- /dev/null
+++ b/src/srtp/srtp.c
@@ -0,0 +1,430 @@
+/**
+ * @file srtp.c Secure Real-time Transport Protocol (SRTP)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hmac.h>
+#include <re_sha.h>
+#include <re_aes.h>
+#include <re_sa.h>
+#include <re_rtp.h>
+#include <re_srtp.h>
+#include "srtp.h"
+
+
+/** SRTP protocol values */
+enum {
+ MAX_KEYLEN = 32, /**< Maximum keylength in bytes */
+};
+
+
+static inline int seq_diff(uint16_t x, uint16_t y)
+{
+ return (int)y - (int)x;
+}
+
+
+static int comp_init(struct comp *c, unsigned offs,
+ const uint8_t *key, size_t key_b,
+ const uint8_t *s, size_t s_b,
+ size_t tag_len, bool encrypted, bool hash,
+ enum aes_mode mode)
+{
+ uint8_t k_e[MAX_KEYLEN], k_a[SHA_DIGEST_LENGTH];
+ int err = 0;
+
+ if (key_b > sizeof(k_e))
+ return EINVAL;
+
+ if (tag_len > SHA_DIGEST_LENGTH)
+ return EINVAL;
+
+ c->tag_len = tag_len;
+ c->mode = mode;
+
+ err |= srtp_derive(k_e, key_b, 0x00+offs, key, key_b, s, s_b);
+ err |= srtp_derive(k_a, sizeof(k_a), 0x01+offs, key, key_b, s, s_b);
+ err |= srtp_derive(c->k_s.u8, 14, 0x02+offs, key, key_b, s, s_b);
+ if (err)
+ return err;
+
+ if (encrypted) {
+ err = aes_alloc(&c->aes, mode, k_e, key_b*8, NULL);
+ if (err)
+ return err;
+ }
+
+ if (hash) {
+ err = hmac_create(&c->hmac, HMAC_HASH_SHA1, k_a, sizeof(k_a));
+ if (err)
+ return err;
+ }
+
+ return err;
+}
+
+
+static void destructor(void *arg)
+{
+ struct srtp *srtp = arg;
+
+ mem_deref(srtp->rtp.aes);
+ mem_deref(srtp->rtcp.aes);
+ mem_deref(srtp->rtp.hmac);
+ mem_deref(srtp->rtcp.hmac);
+
+ list_flush(&srtp->streaml);
+}
+
+
+int srtp_alloc(struct srtp **srtpp, enum srtp_suite suite,
+ const uint8_t *key, size_t key_bytes, int flags)
+{
+ struct srtp *srtp;
+ const uint8_t *master_salt;
+ size_t cipher_bytes, salt_bytes, auth_bytes;
+ enum aes_mode mode;
+ bool hash;
+ int err = 0;
+
+ if (!srtpp || !key)
+ return EINVAL;
+
+ switch (suite) {
+
+ case SRTP_AES_CM_128_HMAC_SHA1_80:
+ mode = AES_MODE_CTR;
+ cipher_bytes = 16;
+ salt_bytes = 14;
+ auth_bytes = 10;
+ hash = true;
+ break;
+
+ case SRTP_AES_CM_128_HMAC_SHA1_32:
+ mode = AES_MODE_CTR;
+ cipher_bytes = 16;
+ salt_bytes = 14;
+ auth_bytes = 4;
+ hash = true;
+ break;
+
+ case SRTP_AES_256_CM_HMAC_SHA1_80:
+ mode = AES_MODE_CTR;
+ cipher_bytes = 32;
+ salt_bytes = 14;
+ auth_bytes = 10;
+ hash = true;
+ break;
+
+ case SRTP_AES_256_CM_HMAC_SHA1_32:
+ mode = AES_MODE_CTR;
+ cipher_bytes = 32;
+ salt_bytes = 14;
+ auth_bytes = 4;
+ hash = true;
+ break;
+
+ case SRTP_AES_128_GCM:
+ mode = AES_MODE_GCM;
+ cipher_bytes = 16;
+ salt_bytes = 12;
+ auth_bytes = 0;
+ hash = false;
+ break;
+
+ case SRTP_AES_256_GCM:
+ mode = AES_MODE_GCM;
+ cipher_bytes = 32;
+ salt_bytes = 12;
+ auth_bytes = 0;
+ hash = false;
+ break;
+
+ default:
+ return ENOTSUP;
+ };
+
+ if ((cipher_bytes + salt_bytes) != key_bytes)
+ return EINVAL;
+
+ master_salt = &key[cipher_bytes];
+
+ srtp = mem_zalloc(sizeof(*srtp), destructor);
+ if (!srtp)
+ return ENOMEM;
+
+ err |= comp_init(&srtp->rtp, 0, key, cipher_bytes,
+ master_salt, salt_bytes, auth_bytes,
+ true, hash, mode);
+ err |= comp_init(&srtp->rtcp, 3, key, cipher_bytes,
+ master_salt, salt_bytes, auth_bytes,
+ !(flags & SRTP_UNENCRYPTED_SRTCP), hash, mode);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(srtp);
+ else
+ *srtpp = srtp;
+
+ return err;
+}
+
+
+int srtp_encrypt(struct srtp *srtp, struct mbuf *mb)
+{
+ struct srtp_stream *strm;
+ struct rtp_header hdr;
+ struct comp *comp;
+ size_t start;
+ uint64_t ix;
+ int err;
+
+ if (!srtp || !mb)
+ return EINVAL;
+
+ comp = &srtp->rtp;
+
+ start = mb->pos;
+
+ err = rtp_hdr_decode(&hdr, mb);
+ if (err)
+ return err;
+
+ err = stream_get_seq(&strm, srtp, hdr.ssrc, hdr.seq);
+ if (err)
+ return err;
+
+ /* Roll-Over Counter (ROC) */
+ if (seq_diff(strm->s_l, hdr.seq) <= -32768) {
+ strm->roc++;
+ strm->s_l = 0;
+ }
+
+ ix = 65536ULL * strm->roc + hdr.seq;
+
+ if (comp->aes && comp->mode == AES_MODE_CTR) {
+ union vect128 iv;
+ uint8_t *p = mbuf_buf(mb);
+
+ srtp_iv_calc(&iv, &comp->k_s, strm->ssrc, ix);
+
+ aes_set_iv(comp->aes, iv.u8);
+ err = aes_encr(comp->aes, p, p, mbuf_get_left(mb));
+ if (err)
+ return err;
+ }
+ else if (comp->aes && comp->mode == AES_MODE_GCM) {
+ union vect128 iv;
+ uint8_t *p = mbuf_buf(mb);
+ uint8_t tag[GCM_TAGLEN];
+
+ srtp_iv_calc_gcm(&iv, &comp->k_s, strm->ssrc, ix);
+
+ aes_set_iv(comp->aes, iv.u8);
+
+ /* The RTP Header is Associated Data */
+ err = aes_encr(comp->aes, NULL, &mb->buf[start],
+ mb->pos - start);
+ if (err)
+ return err;
+
+ err = aes_encr(comp->aes, p, p, mbuf_get_left(mb));
+ if (err)
+ return err;
+
+ err = aes_get_authtag(comp->aes, tag, sizeof(tag));
+ if (err)
+ return err;
+
+ mb->pos = mb->end;
+ err = mbuf_write_mem(mb, tag, sizeof(tag));
+ if (err)
+ return err;
+ }
+
+ if (comp->hmac) {
+ const size_t tag_start = mb->end;
+ uint8_t tag[SHA_DIGEST_LENGTH];
+
+ mb->pos = tag_start;
+
+ err = mbuf_write_u32(mb, htonl(strm->roc));
+ if (err)
+ return err;
+
+ mb->pos = start;
+
+ err = hmac_digest(comp->hmac, tag, sizeof(tag),
+ mbuf_buf(mb), mbuf_get_left(mb));
+ if (err)
+ return err;
+
+ mb->pos = mb->end = tag_start;
+
+ err = mbuf_write_mem(mb, tag, comp->tag_len);
+ if (err)
+ return err;
+ }
+
+ if (hdr.seq > strm->s_l)
+ strm->s_l = hdr.seq;
+
+ mb->pos = start;
+
+ return 0;
+}
+
+
+int srtp_decrypt(struct srtp *srtp, struct mbuf *mb)
+{
+ struct srtp_stream *strm;
+ struct rtp_header hdr;
+ struct comp *comp;
+ uint64_t ix;
+ size_t start;
+ int diff;
+ int err;
+
+ if (!srtp || !mb)
+ return EINVAL;
+
+ comp = &srtp->rtp;
+
+ start = mb->pos;
+
+ err = rtp_hdr_decode(&hdr, mb);
+ if (err)
+ return err;
+
+ err = stream_get_seq(&strm, srtp, hdr.ssrc, hdr.seq);
+ if (err)
+ return err;
+
+ diff = seq_diff(strm->s_l, hdr.seq);
+ if (diff > 32768)
+ return ETIMEDOUT;
+
+ /* Roll-Over Counter (ROC) */
+ if (diff <= -32768) {
+ strm->roc++;
+ strm->s_l = 0;
+ }
+
+ ix = srtp_get_index(strm->roc, strm->s_l, hdr.seq);
+
+ if (comp->hmac) {
+ uint8_t tag_calc[SHA_DIGEST_LENGTH];
+ uint8_t tag_pkt[SHA_DIGEST_LENGTH];
+ size_t pld_start, tag_start;
+
+ if (mbuf_get_left(mb) < comp->tag_len)
+ return EBADMSG;
+
+ pld_start = mb->pos;
+ tag_start = mb->end - comp->tag_len;
+
+ mb->pos = tag_start;
+
+ err = mbuf_read_mem(mb, tag_pkt, comp->tag_len);
+ if (err)
+ return err;
+
+ mb->pos = mb->end = tag_start;
+
+ err = mbuf_write_u32(mb, htonl(strm->roc));
+ if (err)
+ return err;
+
+ mb->pos = start;
+
+ err = hmac_digest(comp->hmac, tag_calc, sizeof(tag_calc),
+ mbuf_buf(mb), mbuf_get_left(mb));
+ if (err)
+ return err;
+
+ mb->pos = pld_start;
+ mb->end = tag_start;
+
+ if (0 != memcmp(tag_calc, tag_pkt, comp->tag_len))
+ return EAUTH;
+
+ /*
+ * 3.3.2. Replay Protection
+ *
+ * Secure replay protection is only possible when
+ * integrity protection is present.
+ */
+ if (!srtp_replay_check(&strm->replay_rtp, ix))
+ return EALREADY;
+ }
+
+ if (comp->aes && comp->mode == AES_MODE_CTR) {
+
+ union vect128 iv;
+ uint8_t *p = mbuf_buf(mb);
+
+ srtp_iv_calc(&iv, &comp->k_s, strm->ssrc, ix);
+
+ aes_set_iv(comp->aes, iv.u8);
+ err = aes_decr(comp->aes, p, p, mbuf_get_left(mb));
+ if (err)
+ return err;
+ }
+ else if (comp->aes && comp->mode == AES_MODE_GCM) {
+
+ union vect128 iv;
+ uint8_t *p = mbuf_buf(mb);
+ size_t tag_start;
+
+ srtp_iv_calc_gcm(&iv, &comp->k_s, strm->ssrc, ix);
+
+ aes_set_iv(comp->aes, iv.u8);
+
+ /* The RTP Header is Associated Data */
+ err = aes_decr(comp->aes, NULL, &mb->buf[start],
+ mb->pos - start);
+ if (err)
+ return err;
+
+ if (mbuf_get_left(mb) < GCM_TAGLEN)
+ return EBADMSG;
+
+ tag_start = mb->end - GCM_TAGLEN;
+
+ err = aes_decr(comp->aes, p, p, tag_start - mb->pos);
+ if (err)
+ return err;
+
+ err = aes_authenticate(comp->aes, &mb->buf[tag_start],
+ GCM_TAGLEN);
+ if (err)
+ return err;
+
+ mb->end = tag_start;
+
+ /*
+ * 3.3.2. Replay Protection
+ *
+ * Secure replay protection is only possible when
+ * integrity protection is present.
+ */
+ if (!srtp_replay_check(&strm->replay_rtp, ix))
+ return EALREADY;
+
+ }
+
+ if (hdr.seq > strm->s_l)
+ strm->s_l = hdr.seq;
+
+ mb->pos = start;
+
+ return 0;
+}
diff --git a/src/srtp/srtp.h b/src/srtp/srtp.h
new file mode 100644
index 0000000..94b418e
--- /dev/null
+++ b/src/srtp/srtp.h
@@ -0,0 +1,72 @@
+/**
+ * @file srtp.h Secure Real-time Transport Protocol (SRTP) -- internal
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** SRTP Protocol values */
+enum {
+ GCM_TAGLEN = 16, /**< GCM taglength in bytes */
+};
+
+
+/** Defines a 128-bit vector in network order */
+union vect128 {
+ uint64_t u64[ 2];
+ uint32_t u32[ 4];
+ uint16_t u16[ 8];
+ uint8_t u8[16];
+};
+
+/** Replay protection */
+struct replay {
+ uint64_t bitmap; /**< Session state - must be 64 bits */
+ uint64_t lix; /**< Last received index */
+};
+
+/** SRTP stream/context -- shared state between RTP/RTCP */
+struct srtp_stream {
+ struct le le; /**< Linked-list element */
+ struct replay replay_rtp; /**< recv -- replay protection for RTP */
+ struct replay replay_rtcp; /**< recv -- replay protection for RTCP */
+ uint32_t ssrc; /**< SSRC -- lookup key */
+ uint32_t roc; /**< send/recv Roll-Over Counter (ROC) */
+ uint16_t s_l; /**< send/recv -- highest SEQ number */
+ bool s_l_set; /**< True if s_l has been set */
+ uint32_t rtcp_index; /**< RTCP-index for sending (31-bits) */
+};
+
+/** SRTP Session */
+struct srtp {
+ struct comp {
+ struct aes *aes; /**< AES Context */
+ enum aes_mode mode; /**< AES encryption mode */
+ struct hmac *hmac; /**< HMAC Context */
+ union vect128 k_s; /**< Derived salting key (14 bytes) */
+ size_t tag_len; /**< CTR Auth. tag length [bytes] */
+ } rtp, rtcp;
+
+ struct list streaml; /**< SRTP-streams (struct srtp_stream) */
+};
+
+
+int stream_get(struct srtp_stream **strmp, struct srtp *srtp, uint32_t ssrc);
+int stream_get_seq(struct srtp_stream **strmp, struct srtp *srtp,
+ uint32_t ssrc, uint16_t seq);
+
+
+int srtp_derive(uint8_t *out, size_t out_len, uint8_t label,
+ const uint8_t *master_key, size_t key_bytes,
+ const uint8_t *master_salt, size_t salt_bytes);
+void srtp_iv_calc(union vect128 *iv, const union vect128 *k_s,
+ uint32_t ssrc, uint64_t ix);
+void srtp_iv_calc_gcm(union vect128 *iv, const union vect128 *k_s,
+ uint32_t ssrc, uint64_t ix);
+uint64_t srtp_get_index(uint32_t roc, uint16_t s_l, uint16_t seq);
+
+
+/* Replay protection */
+
+void srtp_replay_init(struct replay *replay);
+bool srtp_replay_check(struct replay *replay, uint64_t ix);
diff --git a/src/srtp/stream.c b/src/srtp/stream.c
new file mode 100644
index 0000000..21393d8
--- /dev/null
+++ b/src/srtp/stream.c
@@ -0,0 +1,109 @@
+/**
+ * @file srtp/stream.c Secure Real-time Transport Protocol (SRTP) -- stream
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_aes.h>
+#include <re_srtp.h>
+#include "srtp.h"
+
+
+/** SRTP protocol values */
+#ifndef SRTP_MAX_STREAMS
+#define SRTP_MAX_STREAMS (8) /**< Maximum number of SRTP streams */
+#endif
+
+
+static void stream_destructor(void *arg)
+{
+ struct srtp_stream *strm = arg;
+
+ list_unlink(&strm->le);
+}
+
+
+static struct srtp_stream *stream_find(struct srtp *srtp, uint32_t ssrc)
+{
+ struct le *le;
+
+ for (le = srtp->streaml.head; le; le = le->next) {
+
+ struct srtp_stream *strm = le->data;
+
+ if (strm->ssrc == ssrc)
+ return strm;
+ }
+
+ return NULL;
+}
+
+
+static int stream_new(struct srtp_stream **strmp, struct srtp *srtp,
+ uint32_t ssrc)
+{
+ struct srtp_stream *strm;
+
+ if (list_count(&srtp->streaml) >= SRTP_MAX_STREAMS)
+ return ENOSR;
+
+ strm = mem_zalloc(sizeof(*strm), stream_destructor);
+ if (!strm)
+ return ENOMEM;
+
+ strm->ssrc = ssrc;
+ srtp_replay_init(&strm->replay_rtp);
+ srtp_replay_init(&strm->replay_rtcp);
+
+ list_append(&srtp->streaml, &strm->le, strm);
+
+ if (strmp)
+ *strmp = strm;
+
+ return 0;
+}
+
+
+int stream_get(struct srtp_stream **strmp, struct srtp *srtp, uint32_t ssrc)
+{
+ struct srtp_stream *strm;
+
+ if (!strmp || !srtp)
+ return EINVAL;
+
+ strm = stream_find(srtp, ssrc);
+ if (strm) {
+ *strmp = strm;
+ return 0;
+ }
+
+ return stream_new(strmp, srtp, ssrc);
+}
+
+
+int stream_get_seq(struct srtp_stream **strmp, struct srtp *srtp,
+ uint32_t ssrc, uint16_t seq)
+{
+ struct srtp_stream *strm;
+ int err;
+
+ if (!strmp || !srtp)
+ return EINVAL;
+
+ err = stream_get(&strm, srtp, ssrc);
+ if (err)
+ return err;
+
+ /* Set the initial sequence number once only */
+ if (!strm->s_l_set) {
+ strm->s_l = seq;
+ strm->s_l_set = true;
+ }
+
+ *strmp = strm;
+
+ return 0;
+}
diff --git a/src/stun/addr.c b/src/stun/addr.c
new file mode 100644
index 0000000..812942a
--- /dev/null
+++ b/src/stun/addr.c
@@ -0,0 +1,124 @@
+/**
+ * @file stun/addr.c STUN Address encoding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+static void in6_xor_tid(uint8_t *in6, const uint8_t *tid)
+{
+ int i;
+
+ /* XOR with Magic Cookie (alignment safe) */
+ in6[0] ^= 0x21;
+ in6[1] ^= 0x12;
+ in6[2] ^= 0xa4;
+ in6[3] ^= 0x42;
+
+ for (i=0; i<STUN_TID_SIZE; i++)
+ in6[4+i] ^= tid[i];
+}
+
+
+int stun_addr_encode(struct mbuf *mb, const struct sa *addr,
+ const uint8_t *tid)
+{
+#ifdef HAVE_INET6
+ uint8_t addr6[16];
+#endif
+ uint16_t port;
+ uint32_t addr4;
+ int err = 0;
+
+ if (!mb || !addr)
+ return EINVAL;
+
+ port = tid ? sa_port(addr)^(STUN_MAGIC_COOKIE>>16) : sa_port(addr);
+
+ switch (sa_af(addr)) {
+
+ case AF_INET:
+ addr4 = tid ? sa_in(addr)^STUN_MAGIC_COOKIE : sa_in(addr);
+
+ err |= mbuf_write_u8(mb, 0);
+ err |= mbuf_write_u8(mb, STUN_AF_IPv4);
+ err |= mbuf_write_u16(mb, htons(port));
+ err |= mbuf_write_u32(mb, htonl(addr4));
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ sa_in6(addr, addr6);
+ if (tid)
+ in6_xor_tid(addr6, tid);
+
+ err |= mbuf_write_u8(mb, 0);
+ err |= mbuf_write_u8(mb, STUN_AF_IPv6);
+ err |= mbuf_write_u16(mb, htons(port));
+ err |= mbuf_write_mem(mb, addr6, 16);
+ break;
+#endif
+ default:
+ err = EAFNOSUPPORT;
+ break;
+ }
+
+ return err;
+}
+
+
+int stun_addr_decode(struct mbuf *mb, struct sa *addr, const uint8_t *tid)
+{
+ uint8_t family, addr6[16];
+ uint32_t addr4;
+ uint16_t port;
+
+ if (!mb || !addr)
+ return EINVAL;
+
+ if (mbuf_get_left(mb) < 4)
+ return EBADMSG;
+
+ (void)mbuf_read_u8(mb);
+ family = mbuf_read_u8(mb);
+ port = ntohs(mbuf_read_u16(mb));
+
+ if (tid)
+ port ^= STUN_MAGIC_COOKIE>>16;
+
+ switch (family) {
+
+ case STUN_AF_IPv4:
+ if (mbuf_get_left(mb) < 4)
+ return EBADMSG;
+
+ addr4 = ntohl(mbuf_read_u32(mb));
+ if (tid)
+ addr4 ^= STUN_MAGIC_COOKIE;
+
+ sa_set_in(addr, addr4, port);
+ break;
+
+ case STUN_AF_IPv6:
+ if (mbuf_get_left(mb) < 16)
+ return EBADMSG;
+
+ (void)mbuf_read_mem(mb, addr6, 16);
+ if (tid)
+ in6_xor_tid(addr6, tid);
+
+ sa_set_in6(addr, addr6, port);
+ break;
+
+ default:
+ return EAFNOSUPPORT;
+ }
+
+ return 0;
+}
diff --git a/src/stun/attr.c b/src/stun/attr.c
new file mode 100644
index 0000000..5dc3da9
--- /dev/null
+++ b/src/stun/attr.c
@@ -0,0 +1,502 @@
+/**
+ * @file stun/attr.c STUN Attributes
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_fmt.h>
+#include <re_sys.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+static int str_decode(struct mbuf *mb, char **str, size_t len)
+{
+ if (mbuf_get_left(mb) < len)
+ return EBADMSG;
+
+ return mbuf_strdup(mb, str, len);
+}
+
+
+static void destructor(void *arg)
+{
+ struct stun_attr *attr = arg;
+
+ switch (attr->type) {
+
+ case STUN_ATTR_USERNAME:
+ case STUN_ATTR_REALM:
+ case STUN_ATTR_NONCE:
+ case STUN_ATTR_SOFTWARE:
+ mem_deref(attr->v.str);
+ break;
+
+ case STUN_ATTR_ERR_CODE:
+ mem_deref(attr->v.err_code.reason);
+ break;
+
+ case STUN_ATTR_DATA:
+ case STUN_ATTR_PADDING:
+ mem_deref(attr->v.mb.buf);
+ break;
+ }
+
+ list_unlink(&attr->le);
+}
+
+
+int stun_attr_encode(struct mbuf *mb, uint16_t type, const void *v,
+ const uint8_t *tid, uint8_t padding)
+{
+ const struct stun_change_req *ch_req = v;
+ const struct stun_errcode *err_code = v;
+ const struct stun_unknown_attr *ua = v;
+ const uint32_t *num32 = v;
+ const uint16_t *num16 = v;
+ const uint8_t *num8 = v;
+ const struct mbuf *mbd = v;
+ size_t start, len;
+ uint32_t i, n;
+ int err = 0;
+
+ if (!mb || !v)
+ return EINVAL;
+
+ mb->pos += 4;
+ start = mb->pos;
+
+ switch (type) {
+
+ case STUN_ATTR_MAPPED_ADDR:
+ case STUN_ATTR_ALT_SERVER:
+ case STUN_ATTR_RESP_ORIGIN:
+ case STUN_ATTR_OTHER_ADDR:
+ tid = NULL;
+ /*@fallthrough@*/
+ case STUN_ATTR_XOR_PEER_ADDR:
+ case STUN_ATTR_XOR_RELAY_ADDR:
+ case STUN_ATTR_XOR_MAPPED_ADDR:
+ err |= stun_addr_encode(mb, v, tid);
+ break;
+
+ case STUN_ATTR_CHANGE_REQ:
+ n = (uint32_t)ch_req->ip << 2 | (uint32_t)ch_req->port << 1;
+ err |= mbuf_write_u32(mb, htonl(n));
+ break;
+
+ case STUN_ATTR_USERNAME:
+ case STUN_ATTR_REALM:
+ case STUN_ATTR_NONCE:
+ case STUN_ATTR_SOFTWARE:
+ err |= mbuf_write_str(mb, v);
+ break;
+
+ case STUN_ATTR_MSG_INTEGRITY:
+ err |= mbuf_write_mem(mb, v, 20);
+ break;
+
+ case STUN_ATTR_ERR_CODE:
+ err |= mbuf_write_u16(mb, 0x00);
+ err |= mbuf_write_u8(mb, err_code->code/100);
+ err |= mbuf_write_u8(mb, err_code->code%100);
+ err |= mbuf_write_str(mb, err_code->reason);
+ break;
+
+ case STUN_ATTR_UNKNOWN_ATTR:
+ for (i=0; i<ua->typec; i++)
+ err |= mbuf_write_u16(mb, htons(ua->typev[i]));
+ break;
+
+ case STUN_ATTR_CHANNEL_NUMBER:
+ case STUN_ATTR_RESP_PORT:
+ err |= mbuf_write_u16(mb, htons(*num16));
+ err |= mbuf_write_u16(mb, 0x0000);
+ break;
+
+ case STUN_ATTR_LIFETIME:
+ case STUN_ATTR_PRIORITY:
+ case STUN_ATTR_FINGERPRINT:
+ err |= mbuf_write_u32(mb, htonl(*num32));
+ break;
+
+ case STUN_ATTR_DATA:
+ case STUN_ATTR_PADDING:
+ if (mb == mbd) {
+ mb->pos = mb->end;
+ break;
+ }
+ err |= mbuf_write_mem(mb, mbuf_buf(mbd), mbuf_get_left(mbd));
+ break;
+
+ case STUN_ATTR_REQ_ADDR_FAMILY:
+ case STUN_ATTR_REQ_TRANSPORT:
+ err |= mbuf_write_u8(mb, *num8);
+ err |= mbuf_write_u8(mb, 0x00);
+ err |= mbuf_write_u16(mb, 0x0000);
+ break;
+
+ case STUN_ATTR_EVEN_PORT:
+ err |= mbuf_write_u8(mb, ((struct stun_even_port *)v)->r << 7);
+ break;
+
+ case STUN_ATTR_DONT_FRAGMENT:
+ case STUN_ATTR_USE_CAND:
+ /* no value */
+ break;
+
+ case STUN_ATTR_RSV_TOKEN:
+ case STUN_ATTR_CONTROLLED:
+ case STUN_ATTR_CONTROLLING:
+ err |= mbuf_write_u64(mb, sys_htonll(*(uint64_t *)v));
+ break;
+
+ default:
+ err = EINVAL;
+ break;
+ }
+
+ /* header */
+ len = mb->pos - start;
+
+ mb->pos = start - 4;
+ err |= mbuf_write_u16(mb, htons(type));
+ err |= mbuf_write_u16(mb, htons(len));
+ mb->pos += len;
+
+ /* padding */
+ while ((mb->pos - start) & 0x03)
+ err |= mbuf_write_u8(mb, padding);
+
+ return err;
+}
+
+
+int stun_attr_decode(struct stun_attr **attrp, struct mbuf *mb,
+ const uint8_t *tid, struct stun_unknown_attr *ua)
+{
+ struct stun_attr *attr;
+ size_t start, len;
+ uint32_t i, n;
+ int err = 0;
+
+ if (!mb || !attrp)
+ return EINVAL;
+
+ if (mbuf_get_left(mb) < 4)
+ return EBADMSG;
+
+ attr = mem_zalloc(sizeof(*attr), destructor);
+ if (!attr)
+ return ENOMEM;
+
+ attr->type = ntohs(mbuf_read_u16(mb));
+ len = ntohs(mbuf_read_u16(mb));
+
+ if (mbuf_get_left(mb) < len)
+ goto badmsg;
+
+ start = mb->pos;
+
+ switch (attr->type) {
+
+ case STUN_ATTR_MAPPED_ADDR:
+ case STUN_ATTR_ALT_SERVER:
+ case STUN_ATTR_RESP_ORIGIN:
+ case STUN_ATTR_OTHER_ADDR:
+ tid = NULL;
+ /*@fallthrough@*/
+ case STUN_ATTR_XOR_PEER_ADDR:
+ case STUN_ATTR_XOR_RELAY_ADDR:
+ case STUN_ATTR_XOR_MAPPED_ADDR:
+ err = stun_addr_decode(mb, &attr->v.sa, tid);
+ break;
+
+ case STUN_ATTR_CHANGE_REQ:
+ if (len != 4)
+ goto badmsg;
+
+ n = ntohl(mbuf_read_u32(mb));
+ attr->v.change_req.ip = (n >> 2) & 0x1;
+ attr->v.change_req.port = (n >> 1) & 0x1;
+ break;
+
+ case STUN_ATTR_USERNAME:
+ case STUN_ATTR_REALM:
+ case STUN_ATTR_NONCE:
+ case STUN_ATTR_SOFTWARE:
+ err = str_decode(mb, &attr->v.str, len);
+ break;
+
+ case STUN_ATTR_MSG_INTEGRITY:
+ if (len != 20)
+ goto badmsg;
+
+ err = mbuf_read_mem(mb, attr->v.msg_integrity, 20);
+ break;
+
+ case STUN_ATTR_ERR_CODE:
+ if (len < 4)
+ goto badmsg;
+
+ mb->pos += 2;
+ attr->v.err_code.code = (mbuf_read_u8(mb) & 0x7) * 100;
+ attr->v.err_code.code += mbuf_read_u8(mb);
+ err = str_decode(mb, &attr->v.err_code.reason, len - 4);
+ break;
+
+ case STUN_ATTR_UNKNOWN_ATTR:
+ for (i=0; i<len/2; i++) {
+ uint16_t type = ntohs(mbuf_read_u16(mb));
+
+ if (i >= ARRAY_SIZE(attr->v.unknown_attr.typev))
+ continue;
+
+ attr->v.unknown_attr.typev[i] = type;
+ attr->v.unknown_attr.typec++;
+ }
+ break;
+
+ case STUN_ATTR_CHANNEL_NUMBER:
+ case STUN_ATTR_RESP_PORT:
+ if (len < 2)
+ goto badmsg;
+
+ attr->v.uint16 = ntohs(mbuf_read_u16(mb));
+ break;
+
+ case STUN_ATTR_LIFETIME:
+ case STUN_ATTR_PRIORITY:
+ case STUN_ATTR_FINGERPRINT:
+ if (len != 4)
+ goto badmsg;
+
+ attr->v.uint32 = ntohl(mbuf_read_u32(mb));
+ break;
+
+ case STUN_ATTR_DATA:
+ case STUN_ATTR_PADDING:
+ attr->v.mb.buf = mem_ref(mb->buf);
+ attr->v.mb.size = mb->size;
+ attr->v.mb.pos = mb->pos;
+ attr->v.mb.end = mb->pos + len;
+ mb->pos += len;
+ break;
+
+ case STUN_ATTR_REQ_ADDR_FAMILY:
+ case STUN_ATTR_REQ_TRANSPORT:
+ if (len < 1)
+ goto badmsg;
+
+ attr->v.uint8 = mbuf_read_u8(mb);
+ break;
+
+ case STUN_ATTR_EVEN_PORT:
+ if (len < 1)
+ goto badmsg;
+
+ attr->v.even_port.r = (mbuf_read_u8(mb) >> 7) & 0x1;
+ break;
+
+ case STUN_ATTR_DONT_FRAGMENT:
+ case STUN_ATTR_USE_CAND:
+ if (len > 0)
+ goto badmsg;
+
+ /* no value */
+ break;
+
+ case STUN_ATTR_RSV_TOKEN:
+ case STUN_ATTR_CONTROLLING:
+ case STUN_ATTR_CONTROLLED:
+ if (len != 8)
+ goto badmsg;
+
+ attr->v.uint64 = sys_ntohll(mbuf_read_u64(mb));
+ break;
+
+ default:
+ mb->pos += len;
+
+ if (attr->type >= 0x8000)
+ break;
+
+ if (ua && ua->typec < ARRAY_SIZE(ua->typev))
+ ua->typev[ua->typec++] = attr->type;
+ break;
+ }
+
+ if (err)
+ goto error;
+
+ /* padding */
+ while (((mb->pos - start) & 0x03) && mbuf_get_left(mb))
+ ++mb->pos;
+
+ *attrp = attr;
+
+ return 0;
+
+ badmsg:
+ err = EBADMSG;
+ error:
+ mem_deref(attr);
+
+ return err;
+}
+
+
+/**
+ * Get the name of a STUN attribute
+ *
+ * @param type STUN attribute type
+ *
+ * @return String with attribute name
+ */
+const char *stun_attr_name(uint16_t type)
+{
+ switch (type) {
+
+ case STUN_ATTR_MAPPED_ADDR: return "MAPPED-ADDRESS";
+ case STUN_ATTR_CHANGE_REQ: return "CHANGE-REQUEST";
+ case STUN_ATTR_USERNAME: return "USERNAME";
+ case STUN_ATTR_MSG_INTEGRITY: return "MESSAGE-INTEGRITY";
+ case STUN_ATTR_ERR_CODE: return "ERROR-CODE";
+ case STUN_ATTR_UNKNOWN_ATTR: return "UNKNOWN-ATTRIBUTE";
+ case STUN_ATTR_CHANNEL_NUMBER: return "CHANNEL-NUMBER";
+ case STUN_ATTR_LIFETIME: return "LIFETIME";
+ case STUN_ATTR_XOR_PEER_ADDR: return "XOR-PEER-ADDRESS";
+ case STUN_ATTR_DATA: return "DATA";
+ case STUN_ATTR_REALM: return "REALM";
+ case STUN_ATTR_NONCE: return "NONCE";
+ case STUN_ATTR_XOR_RELAY_ADDR: return "XOR-RELAYED-ADDRESS";
+ case STUN_ATTR_REQ_ADDR_FAMILY: return "REQUESTED-ADDRESS-FAMILY";
+ case STUN_ATTR_EVEN_PORT: return "EVEN_PORT";
+ case STUN_ATTR_REQ_TRANSPORT: return "REQUESTED-TRANSPORT";
+ case STUN_ATTR_DONT_FRAGMENT: return "DONT-FRAGMENT";
+ case STUN_ATTR_XOR_MAPPED_ADDR: return "XOR-MAPPED-ADDRESS";
+ case STUN_ATTR_RSV_TOKEN: return "RESERVATION-TOKEN";
+ case STUN_ATTR_PRIORITY: return "PRIORITY";
+ case STUN_ATTR_USE_CAND: return "USE-CANDIDATE";
+ case STUN_ATTR_PADDING: return "PADDING";
+ case STUN_ATTR_RESP_PORT: return "RESPONSE-PORT";
+ case STUN_ATTR_SOFTWARE: return "SOFTWARE";
+ case STUN_ATTR_ALT_SERVER: return "ALTERNATE-SERVER";
+ case STUN_ATTR_FINGERPRINT: return "FINGERPRINT";
+ case STUN_ATTR_CONTROLLING: return "ICE-CONTROLLING";
+ case STUN_ATTR_CONTROLLED: return "ICE-CONTROLLED";
+ case STUN_ATTR_RESP_ORIGIN: return "RESPONSE-ORIGIN";
+ case STUN_ATTR_OTHER_ADDR: return "OTHER-ADDR";
+ default: return "???";
+ }
+}
+
+
+void stun_attr_dump(const struct stun_attr *a)
+{
+ uint32_t i;
+ size_t len;
+
+ if (!a)
+ return;
+
+ (void)re_printf(" %-25s", stun_attr_name(a->type));
+
+ switch (a->type) {
+
+ case STUN_ATTR_MAPPED_ADDR:
+ case STUN_ATTR_XOR_PEER_ADDR:
+ case STUN_ATTR_XOR_RELAY_ADDR:
+ case STUN_ATTR_XOR_MAPPED_ADDR:
+ case STUN_ATTR_ALT_SERVER:
+ case STUN_ATTR_RESP_ORIGIN:
+ case STUN_ATTR_OTHER_ADDR:
+ (void)re_printf("%J", &a->v.sa);
+ break;
+
+ case STUN_ATTR_CHANGE_REQ:
+ (void)re_printf("ip=%u port=%u", a->v.change_req.ip,
+ a->v.change_req.port);
+ break;
+
+ case STUN_ATTR_USERNAME:
+ case STUN_ATTR_REALM:
+ case STUN_ATTR_NONCE:
+ case STUN_ATTR_SOFTWARE:
+ (void)re_printf("%s", a->v.str);
+ break;
+
+ case STUN_ATTR_MSG_INTEGRITY:
+ (void)re_printf("%w", a->v.msg_integrity,
+ sizeof(a->v.msg_integrity));
+ break;
+
+ case STUN_ATTR_ERR_CODE:
+ (void)re_printf("%u %s", a->v.err_code.code,
+ a->v.err_code.reason);
+ break;
+
+ case STUN_ATTR_UNKNOWN_ATTR:
+ for (i=0; i<a->v.unknown_attr.typec; i++)
+ (void)re_printf("0x%04x ", a->v.unknown_attr.typev[i]);
+ break;
+
+ case STUN_ATTR_CHANNEL_NUMBER:
+ (void)re_printf("0x%04x", a->v.uint16);
+ break;
+
+ case STUN_ATTR_LIFETIME:
+ case STUN_ATTR_PRIORITY:
+ (void)re_printf("%u", a->v.uint32);
+ break;
+
+ case STUN_ATTR_DATA:
+ case STUN_ATTR_PADDING:
+ len = min(mbuf_get_left(&a->v.mb), 16);
+ (void)re_printf("%w%s (%zu bytes)", mbuf_buf(&a->v.mb), len,
+ mbuf_get_left(&a->v.mb) > 16 ? "..." : "",
+ mbuf_get_left(&a->v.mb));
+ break;
+
+ case STUN_ATTR_REQ_ADDR_FAMILY:
+ case STUN_ATTR_REQ_TRANSPORT:
+ (void)re_printf("%u", a->v.uint8);
+ break;
+
+ case STUN_ATTR_EVEN_PORT:
+ (void)re_printf("r=%u", a->v.even_port.r);
+ break;
+
+ case STUN_ATTR_DONT_FRAGMENT:
+ case STUN_ATTR_USE_CAND:
+ /* no value */
+ break;
+
+ case STUN_ATTR_RSV_TOKEN:
+ (void)re_printf("0x%016llx", a->v.rsv_token);
+ break;
+
+ case STUN_ATTR_RESP_PORT:
+ (void)re_printf("%u", a->v.uint16);
+ break;
+
+ case STUN_ATTR_FINGERPRINT:
+ (void)re_printf("0x%08x", a->v.fingerprint);
+ break;
+
+ case STUN_ATTR_CONTROLLING:
+ case STUN_ATTR_CONTROLLED:
+ (void)re_printf("%llu", a->v.uint64);
+ break;
+
+ default:
+ (void)re_printf("???");
+ break;
+ }
+
+ (void)re_printf("\n");
+}
diff --git a/src/stun/ctrans.c b/src/stun/ctrans.c
new file mode 100644
index 0000000..e164b9d
--- /dev/null
+++ b/src/stun/ctrans.c
@@ -0,0 +1,386 @@
+/**
+ * @file stun/ctrans.c STUN Client transactions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_srtp.h>
+#include <re_tls.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_md5.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+struct stun_ctrans {
+ struct le le;
+ struct tmr tmr;
+ struct sa dst;
+ uint8_t tid[STUN_TID_SIZE];
+ struct stun_ctrans **ctp;
+ uint8_t *key;
+ size_t keylen;
+ void *sock;
+ struct mbuf *mb;
+ size_t pos;
+ struct stun *stun;
+ stun_resp_h *resph;
+ void *arg;
+ int proto;
+ uint32_t txc;
+ uint32_t ival;
+ uint16_t met;
+};
+
+
+static void completed(struct stun_ctrans *ct, int err, uint16_t scode,
+ const char *reason, const struct stun_msg *msg)
+{
+ stun_resp_h *resph = ct->resph;
+ void *arg = ct->arg;
+
+ list_unlink(&ct->le);
+ tmr_cancel(&ct->tmr);
+
+ if (ct->ctp) {
+ *ct->ctp = NULL;
+ ct->ctp = NULL;
+ }
+
+ ct->resph = NULL;
+
+ /* must be destroyed before calling handler */
+ mem_deref(ct);
+
+ if (resph)
+ resph(err, scode, reason, msg, arg);
+}
+
+
+static void destructor(void *arg)
+{
+ struct stun_ctrans *ct = arg;
+
+ list_unlink(&ct->le);
+ tmr_cancel(&ct->tmr);
+ mem_deref(ct->key);
+ mem_deref(ct->sock);
+ mem_deref(ct->mb);
+}
+
+
+static void timeout_handler(void *arg)
+{
+ struct stun_ctrans *ct = arg;
+ const struct stun_conf *cfg = stun_conf(ct->stun);
+ int err = ETIMEDOUT;
+
+ if (ct->txc++ >= cfg->rc)
+ goto error;
+
+ ct->mb->pos = ct->pos;
+
+ err = stun_send(ct->proto, ct->sock, &ct->dst, ct->mb);
+ if (err)
+ goto error;
+
+ ct->ival = (ct->txc >= cfg->rc) ? cfg->rto * cfg->rm : ct->ival * 2;
+
+ tmr_start(&ct->tmr, ct->ival, timeout_handler, ct);
+ return;
+
+ error:
+ completed(ct, err, 0, NULL, NULL);
+}
+
+
+static bool match_handler(struct le *le, void *arg)
+{
+ struct stun_ctrans *ct = le->data;
+ struct stun_msg *msg = arg;
+
+ if (ct->met != stun_msg_method(msg))
+ return false;
+
+ if (memcmp(ct->tid, stun_msg_tid(msg), STUN_TID_SIZE))
+ return false;
+
+ return true;
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct stun *stun = arg;
+ (void)src;
+
+ (void)stun_recv(stun, mb);
+}
+
+
+static void tcp_recv_handler(struct mbuf *mb, void *arg)
+{
+ struct stun_ctrans *ct = arg;
+
+ (void)stun_recv(ct->stun, mb);
+}
+
+
+static void tcp_estab_handler(void *arg)
+{
+ struct stun_ctrans *ct = arg;
+ int err;
+
+ err = tcp_send(ct->sock, ct->mb);
+ if (!err)
+ return;
+
+ completed(ct, err, 0, NULL, NULL);
+}
+
+
+static void tcp_close_handler(int err, void *arg)
+{
+ struct stun_ctrans *ct = arg;
+
+ completed(ct, err, 0, NULL, NULL);
+}
+
+
+/**
+ * Handle an incoming STUN message to a Client Transaction
+ *
+ * @param stun STUN instance
+ * @param msg STUN message
+ * @param ua Unknown attributes
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_ctrans_recv(struct stun *stun, const struct stun_msg *msg,
+ const struct stun_unknown_attr *ua)
+{
+ struct stun_errcode ec = {0, "OK"};
+ struct stun_attr *errcode;
+ struct stun_ctrans *ct;
+ int err = 0, herr = 0;
+
+ if (!stun || !msg || !ua)
+ return EINVAL;
+
+ switch (stun_msg_class(msg)) {
+
+ case STUN_CLASS_ERROR_RESP:
+ errcode = stun_msg_attr(msg, STUN_ATTR_ERR_CODE);
+ if (!errcode)
+ herr = EPROTO;
+ else
+ ec = errcode->v.err_code;
+ /*@fallthrough@*/
+
+ case STUN_CLASS_SUCCESS_RESP:
+ ct = list_ledata(list_apply(&stun->ctl, true,
+ match_handler, (void *)msg));
+ if (!ct) {
+ err = ENOENT;
+ break;
+ }
+
+ switch (ec.code) {
+
+ case 401:
+ case 438:
+ break;
+
+ default:
+ if (!ct->key)
+ break;
+
+ err = stun_msg_chk_mi(msg, ct->key, ct->keylen);
+ break;
+ }
+
+ if (err)
+ break;
+
+ if (!herr && ua->typec > 0)
+ herr = EPROTO;
+
+ completed(ct, herr, ec.code, ec.reason, msg);
+ break;
+
+ default:
+ break;
+ }
+
+ return err;
+}
+
+
+int stun_ctrans_request(struct stun_ctrans **ctp, struct stun *stun, int proto,
+ void *sock, const struct sa *dst, struct mbuf *mb,
+ const uint8_t tid[], uint16_t met, const uint8_t *key,
+ size_t keylen, stun_resp_h *resph, void *arg)
+{
+ struct stun_ctrans *ct;
+ int err = 0;
+
+ if (!stun || !mb)
+ return EINVAL;
+
+ ct = mem_zalloc(sizeof(*ct), destructor);
+ if (!ct)
+ return ENOMEM;
+
+ list_append(&stun->ctl, &ct->le, ct);
+ memcpy(ct->tid, tid, STUN_TID_SIZE);
+ ct->proto = proto;
+ ct->sock = mem_ref(sock);
+ ct->mb = mem_ref(mb);
+ ct->pos = mb->pos;
+ ct->stun = stun;
+ ct->met = met;
+
+ if (key) {
+ ct->key = mem_alloc(keylen, NULL);
+ if (!ct->key) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ memcpy(ct->key, key, keylen);
+ ct->keylen = keylen;
+ }
+
+ switch (proto) {
+
+ case IPPROTO_UDP:
+ if (!dst) {
+ err = EINVAL;
+ break;
+ }
+
+ ct->dst = *dst;
+ ct->ival = stun_conf(stun)->rto;
+ tmr_start(&ct->tmr, ct->ival, timeout_handler, ct);
+
+ if (!sock) {
+ err = udp_listen((struct udp_sock **)&ct->sock, NULL,
+ udp_recv_handler, stun);
+ if (err)
+ break;
+ }
+
+ ct->txc = 1;
+ err = udp_send(ct->sock, dst, mb);
+ break;
+
+ case IPPROTO_TCP:
+ ct->txc = stun_conf(stun)->rc;
+ tmr_start(&ct->tmr, stun_conf(stun)->ti, timeout_handler, ct);
+ if (sock) {
+ err = tcp_send(sock, mb);
+ break;
+ }
+
+ err = tcp_connect((struct tcp_conn **)&ct->sock, dst,
+ tcp_estab_handler, tcp_recv_handler,
+ tcp_close_handler, ct);
+ break;
+
+#ifdef USE_DTLS
+ case STUN_TRANSP_DTLS:
+ if (!sock) {
+ err = EINVAL;
+ break;
+ }
+
+ ct->ival = stun_conf(stun)->rto;
+ tmr_start(&ct->tmr, ct->ival, timeout_handler, ct);
+
+ ct->txc = 1;
+ err = dtls_send(ct->sock, mb);
+ break;
+#endif
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ out:
+ if (!err) {
+ if (ctp) {
+ ct->ctp = ctp;
+ *ctp = ct;
+ }
+
+ ct->resph = resph;
+ ct->arg = arg;
+ }
+ else
+ mem_deref(ct);
+
+ return err;
+}
+
+
+static bool close_handler(struct le *le, void *arg)
+{
+ struct stun_ctrans *ct = le->data;
+ (void)arg;
+
+ completed(ct, ECONNABORTED, 0, NULL, NULL);
+
+ return false;
+}
+
+
+void stun_ctrans_close(struct stun *stun)
+{
+ if (!stun)
+ return;
+
+ (void)list_apply(&stun->ctl, true, close_handler, NULL);
+}
+
+
+static bool debug_handler(struct le *le, void *arg)
+{
+ struct stun_ctrans *ct = le->data;
+ struct re_printf *pf = arg;
+ int err = 0;
+
+ err |= re_hprintf(pf, " method=%s", stun_method_name(ct->met));
+ err |= re_hprintf(pf, " tid=%w", ct->tid, sizeof(ct->tid));
+ err |= re_hprintf(pf, " rto=%ums", stun_conf(ct->stun)->rto);
+ err |= re_hprintf(pf, " tmr=%llu", tmr_get_expire(&ct->tmr));
+ err |= re_hprintf(pf, " n=%u", ct->txc);
+ err |= re_hprintf(pf, " interval=%u", ct->ival);
+ err |= re_hprintf(pf, "\n");
+
+ return 0 != err;
+}
+
+
+int stun_ctrans_debug(struct re_printf *pf, const struct stun *stun)
+{
+ int err;
+
+ if (!stun)
+ return 0;
+
+ err = re_hprintf(pf, "STUN client transactions: (%u)\n",
+ list_count(&stun->ctl));
+
+ (void)list_apply(&stun->ctl, true, debug_handler, pf);
+
+ return err;
+}
diff --git a/src/stun/dnsdisc.c b/src/stun/dnsdisc.c
new file mode 100644
index 0000000..c79b2c4
--- /dev/null
+++ b/src/stun/dnsdisc.c
@@ -0,0 +1,293 @@
+/**
+ * @file dnsdisc.c DNS Discovery of a STUN Server
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_dns.h>
+#include <re_stun.h>
+
+
+#define DEBUG_MODULE "dnsdisc"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** DNS Query */
+struct stun_dns {
+ char domain[256]; /**< Cached domain name */
+ stun_dns_h *dnsh; /**< DNS Response handler */
+ void *arg; /**< Handler argument */
+ struct sa srv; /**< Resolved server address */
+ struct dnsc *dnsc; /**< DNS Client */
+ struct dns_query *dq; /**< Current DNS query */
+ int af; /**< Preferred Address family*/
+ uint16_t port; /**< Default Port */
+};
+
+const char *stun_proto_udp = "udp"; /**< UDP Protocol */
+const char *stun_proto_tcp = "tcp"; /**< TCP Protocol */
+
+const char *stun_usage_binding = "stun"; /**< Binding usage */
+const char *stuns_usage_binding = "stuns"; /**< Binding usage TLS */
+const char *stun_usage_relay = "turn";
+const char *stuns_usage_relay = "turns";
+const char *stun_usage_behavior = "stun-behavior";
+const char *stuns_usage_behavior = "stun-behaviors";
+
+
+static void resolved(const struct stun_dns *dns, int err)
+{
+ stun_dns_h *dnsh = dns->dnsh;
+ void *dnsh_arg = dns->arg;
+
+ DEBUG_INFO("resolved: %J (%m)\n", &dns->srv, err);
+
+ dnsh(err, &dns->srv, dnsh_arg);
+}
+
+
+static void a_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg)
+{
+ struct stun_dns *dns = arg;
+ struct dnsrr *rr;
+
+ (void)hdr;
+ (void)authl;
+ (void)addl;
+
+ /* Find A answers */
+ rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_A, DNS_CLASS_IN, false);
+ if (!rr) {
+ err = err ? err : EDESTADDRREQ;
+ goto out;
+ }
+
+ sa_set_in(&dns->srv, rr->rdata.a.addr, sa_port(&dns->srv));
+
+ DEBUG_INFO("A answer: %j\n", &dns->srv);
+
+ out:
+ resolved(dns, err);
+}
+
+
+#ifdef HAVE_INET6
+static void aaaa_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg)
+{
+ struct stun_dns *dns = arg;
+ struct dnsrr *rr;
+
+ (void)hdr;
+ (void)authl;
+ (void)addl;
+
+ /* Find A answers */
+ rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_AAAA, DNS_CLASS_IN, false);
+ if (!rr) {
+ err = err ? err : EDESTADDRREQ;
+ goto out;
+ }
+
+ sa_set_in6(&dns->srv, rr->rdata.aaaa.addr, sa_port(&dns->srv));
+
+ DEBUG_INFO("AAAA answer: %j\n", &dns->srv);
+
+ out:
+ resolved(dns, err);
+}
+#endif
+
+
+static int a_or_aaaa_query(struct stun_dns *dns, const char *name)
+{
+ dns->dq = mem_deref(dns->dq);
+
+ switch (dns->af) {
+
+ case AF_INET:
+ return dnsc_query(&dns->dq, dns->dnsc, name, DNS_TYPE_A,
+ DNS_CLASS_IN, true, a_handler, dns);
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ return dnsc_query(&dns->dq, dns->dnsc, name, DNS_TYPE_AAAA,
+ DNS_CLASS_IN, true, aaaa_handler, dns);
+#endif
+
+ default:
+ return EAFNOSUPPORT;
+ }
+}
+
+
+static void srv_handler(int err, const struct dnshdr *hdr, struct list *ansl,
+ struct list *authl, struct list *addl, void *arg)
+{
+ struct stun_dns *dns = arg;
+ struct dnsrr *rr, *arr;
+
+ (void)hdr;
+ (void)authl;
+
+ dns_rrlist_sort(ansl, DNS_TYPE_SRV, (size_t)dns->arg);
+
+ /* Find SRV answers */
+ rr = dns_rrlist_find(ansl, NULL, DNS_TYPE_SRV, DNS_CLASS_IN, false);
+ if (!rr) {
+ DEBUG_INFO("no SRV entry, trying A lookup on \"%s\"\n",
+ dns->domain);
+
+ sa_set_in(&dns->srv, 0, dns->port);
+
+ err = a_or_aaaa_query(dns, dns->domain);
+ if (err)
+ goto out;
+
+ return;
+ }
+
+ DEBUG_INFO("SRV answer: %s:%u\n", rr->rdata.srv.target,
+ rr->rdata.srv.port);
+
+ /* Look for Additional information */
+ switch (dns->af) {
+
+ case AF_INET:
+ arr = dns_rrlist_find(addl, rr->rdata.srv.target,
+ DNS_TYPE_A, DNS_CLASS_IN, true);
+ if (arr) {
+ sa_set_in(&dns->srv, arr->rdata.a.addr,
+ rr->rdata.srv.port);
+ DEBUG_INFO("additional A: %j\n", &dns->srv);
+ goto out;
+ }
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ arr = dns_rrlist_find(addl, rr->rdata.srv.target,
+ DNS_TYPE_AAAA, DNS_CLASS_IN, true);
+ if (arr) {
+ sa_set_in6(&dns->srv, arr->rdata.aaaa.addr,
+ rr->rdata.srv.port);
+ DEBUG_INFO("additional AAAA: %j\n", &dns->srv);
+ goto out;
+ }
+ break;
+#endif
+ }
+
+ sa_set_in(&dns->srv, 0, rr->rdata.srv.port);
+
+ err = a_or_aaaa_query(dns, rr->rdata.srv.target);
+ if (err) {
+ DEBUG_WARNING("SRV: A lookup failed (%m)\n", err);
+ goto out;
+ }
+
+ DEBUG_INFO("SRV handler: doing A/AAAA lookup..\n");
+
+ return;
+
+ out:
+ resolved(dns, err);
+}
+
+
+static void dnsdisc_destructor(void *data)
+{
+ struct stun_dns *dns = data;
+
+ mem_deref(dns->dq);
+}
+
+
+/**
+ * Do a DNS Discovery of a STUN Server
+ *
+ * @param dnsp Pointer to allocated DNS Discovery object
+ * @param dnsc DNS Client
+ * @param service Name of service to discover (e.g. "stun")
+ * @param proto Transport protocol (e.g. "udp")
+ * @param af Preferred Address Family
+ * @param domain Domain name or IP address of STUN server
+ * @param port Port number (if 0 do SRV lookup)
+ * @param dnsh DNS Response handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_server_discover(struct stun_dns **dnsp, struct dnsc *dnsc,
+ const char *service, const char *proto,
+ int af, const char *domain, uint16_t port,
+ stun_dns_h *dnsh, void *arg)
+{
+ struct stun_dns *dns;
+ int err;
+
+ if (!dnsp || !service || !proto || !domain || !domain[0] || !dnsh)
+ return EINVAL;
+
+ dns = mem_zalloc(sizeof(*dns), dnsdisc_destructor);
+ if (!dns)
+ return ENOMEM;
+
+ dns->port = service[strlen(service)-1] == 's' ? STUNS_PORT : STUN_PORT;
+ dns->dnsh = dnsh;
+ dns->arg = arg;
+ dns->dnsc = dnsc;
+ dns->af = af;
+
+ /* Numeric IP address - no lookup */
+ if (0 == sa_set_str(&dns->srv, domain, port ? port : dns->port)) {
+
+ DEBUG_INFO("IP (%s)\n", domain);
+
+ resolved(dns, 0);
+ err = 0;
+ goto out; /* free now */
+ }
+ /* Port specified - use AAAA or A lookup */
+ else if (port) {
+ sa_set_in(&dns->srv, 0, port);
+ DEBUG_INFO("resolving A query: (%s)\n", domain);
+
+ err = a_or_aaaa_query(dns, domain);
+ if (err) {
+ DEBUG_WARNING("%s: A/AAAA lookup failed (%m)\n",
+ domain, err);
+ goto out;
+ }
+ }
+ /* SRV lookup */
+ else {
+ char q[256];
+ str_ncpy(dns->domain, domain, sizeof(dns->domain));
+ (void)re_snprintf(q, sizeof(q), "_%s._%s.%s", service, proto,
+ domain);
+ DEBUG_INFO("resolving SRV query: (%s)\n", q);
+ err = dnsc_query(&dns->dq, dnsc, q, DNS_TYPE_SRV, DNS_CLASS_IN,
+ true, srv_handler, dns);
+ if (err) {
+ DEBUG_WARNING("%s: SRV lookup failed (%m)\n", q, err);
+ goto out;
+ }
+ }
+
+ *dnsp = dns;
+
+ return 0;
+
+ out:
+ mem_deref(dns);
+ return err;
+}
diff --git a/src/stun/hdr.c b/src/stun/hdr.c
new file mode 100644
index 0000000..d38e000
--- /dev/null
+++ b/src/stun/hdr.c
@@ -0,0 +1,82 @@
+/**
+ * @file stun/hdr.c STUN Header encoding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+int stun_hdr_encode(struct mbuf *mb, const struct stun_hdr *hdr)
+{
+ int err = 0;
+
+ if (!mb || !hdr)
+ return EINVAL;
+
+ err |= mbuf_write_u16(mb, htons(hdr->type & 0x3fff));
+ err |= mbuf_write_u16(mb, htons(hdr->len));
+ err |= mbuf_write_u32(mb, htonl(hdr->cookie));
+ err |= mbuf_write_mem(mb, hdr->tid, sizeof(hdr->tid));
+
+ return err;
+}
+
+
+int stun_hdr_decode(struct mbuf *mb, struct stun_hdr *hdr)
+{
+ if (!mb || !hdr)
+ return EINVAL;
+
+ if (mbuf_get_left(mb) < STUN_HEADER_SIZE)
+ return EBADMSG;
+
+ hdr->type = ntohs(mbuf_read_u16(mb));
+ if (hdr->type & 0xc000)
+ return EBADMSG;
+
+ hdr->len = ntohs(mbuf_read_u16(mb));
+ if (hdr->len & 0x3)
+ return EBADMSG;
+
+ hdr->cookie = ntohl(mbuf_read_u32(mb));
+ (void)mbuf_read_mem(mb, hdr->tid, sizeof(hdr->tid));
+
+ if (mbuf_get_left(mb) < hdr->len)
+ return EBADMSG;
+
+ return 0;
+}
+
+
+const char *stun_class_name(uint16_t class)
+{
+ switch (class) {
+
+ case STUN_CLASS_REQUEST: return "Request";
+ case STUN_CLASS_INDICATION: return "Indication";
+ case STUN_CLASS_SUCCESS_RESP: return "Success Response";
+ case STUN_CLASS_ERROR_RESP: return "Error Response";
+ default: return "???";
+ }
+}
+
+
+const char *stun_method_name(uint16_t method)
+{
+ switch (method) {
+
+ case STUN_METHOD_BINDING: return "Binding";
+ case STUN_METHOD_ALLOCATE: return "Allocate";
+ case STUN_METHOD_REFRESH: return "Refresh";
+ case STUN_METHOD_SEND: return "Send";
+ case STUN_METHOD_DATA: return "Data";
+ case STUN_METHOD_CREATEPERM: return "CreatePermission";
+ case STUN_METHOD_CHANBIND: return "ChannelBind";
+ default: return "???";
+ }
+}
diff --git a/src/stun/ind.c b/src/stun/ind.c
new file mode 100644
index 0000000..cd6f364
--- /dev/null
+++ b/src/stun/ind.c
@@ -0,0 +1,68 @@
+/**
+ * @file ind.c STUN Indication
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_sys.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+/**
+ * Send a STUN Indication message
+ *
+ * @param proto Transport Protocol
+ * @param sock Socket; UDP (struct udp_sock) or TCP (struct tcp_conn)
+ * @param dst Destination network address
+ * @param presz Number of bytes in preamble, if sending over TURN
+ * @param method STUN Method
+ * @param key Authentication key (optional)
+ * @param keylen Number of bytes in authentication key
+ * @param fp Use STUN Fingerprint attribute
+ * @param attrc Number of attributes to encode (variable arguments)
+ * @param ... Variable list of attribute-tuples
+ * Each attribute has 2 arguments, attribute type and value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_indication(int proto, void *sock, const struct sa *dst, size_t presz,
+ uint16_t method, const uint8_t *key, size_t keylen,
+ bool fp, uint32_t attrc, ...)
+{
+ uint8_t tid[STUN_TID_SIZE];
+ struct mbuf *mb;
+ va_list ap;
+ uint32_t i;
+ int err;
+
+ if (!sock)
+ return EINVAL;
+
+ mb = mbuf_alloc(2048);
+ if (!mb)
+ return ENOMEM;
+
+ for (i=0; i<STUN_TID_SIZE; i++)
+ tid[i] = rand_u32();
+
+ va_start(ap, attrc);
+ mb->pos = presz;
+ err = stun_msg_vencode(mb, method, STUN_CLASS_INDICATION, tid, NULL,
+ key, keylen, fp, 0x00, attrc, ap);
+ va_end(ap);
+ if (err)
+ goto out;
+
+ mb->pos = presz;
+ err = stun_send(proto, sock, dst, mb);
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
diff --git a/src/stun/keepalive.c b/src/stun/keepalive.c
new file mode 100644
index 0000000..f7337a4
--- /dev/null
+++ b/src/stun/keepalive.c
@@ -0,0 +1,248 @@
+/**
+ * @file stun/keepalive.c STUN usage for NAT Keepalives
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_stun.h>
+
+
+#define DEBUG_MODULE "keepalive"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Defines a STUN Keepalive session */
+struct stun_keepalive {
+ struct stun_ctrans *ct; /**< STUN client transaction */
+ struct stun *stun; /**< STUN instance */
+ struct udp_helper *uh;
+ int proto;
+ void *sock;
+ struct sa dst;
+ struct tmr tmr; /**< Refresh timer */
+ uint32_t interval; /**< Refresh interval in seconds */
+ stun_mapped_addr_h *mah; /**< Mapped address handler */
+ void *arg; /**< Handler argument */
+ struct sa map; /**< Mapped IP address and port */
+ struct sa xormap; /**< XOR-Mapped IP address and port */
+ struct sa curmap; /**< Currently mapped IP address and port */
+};
+
+static void timeout(void *arg);
+
+
+static void keepalive_destructor(void *data)
+{
+ struct stun_keepalive *ska = data;
+
+ tmr_cancel(&ska->tmr);
+
+ mem_deref(ska->ct);
+ mem_deref(ska->uh);
+ mem_deref(ska->sock);
+ mem_deref(ska->stun);
+}
+
+
+static void call_handler(struct stun_keepalive *ska, int err,
+ const struct sa *map)
+{
+ if (ska->mah)
+ ska->mah(err, map, ska->arg);
+}
+
+
+static void stun_response_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct stun_keepalive *ska = arg;
+ struct stun_attr *attr;
+ (void)reason;
+
+ /* Restart timer */
+ if (ska->interval > 0)
+ tmr_start(&ska->tmr, ska->interval*1000, timeout, ska);
+
+ if (err || scode) {
+ /* Clear current mapped addr to force new notification */
+ sa_set_in(&ska->curmap, 0, 0);
+
+ goto out;
+ }
+
+ attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ if (!attr)
+ attr = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
+
+ if (!attr) {
+ err = ENOENT;
+ goto out;
+ }
+
+ if (!sa_cmp(&ska->curmap, &attr->v.sa, SA_ALL)) {
+ ska->curmap = attr->v.sa;
+ call_handler(ska, 0, &ska->curmap);
+ }
+
+ out:
+ if (err)
+ call_handler(ska, err, NULL);
+}
+
+
+static void timeout(void *arg)
+{
+ struct stun_keepalive *ska = arg;
+ int err;
+
+ if (ska->ct)
+ ska->ct = mem_deref(ska->ct);
+
+ err = stun_request(&ska->ct, ska->stun, ska->proto, ska->sock,
+ &ska->dst, 0, STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler, ska, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+ if (0 == err)
+ return;
+
+ /* Restart timer */
+ if (ska->interval > 0)
+ tmr_start(&ska->tmr, ska->interval*1000, timeout, ska);
+
+ /* Error */
+ call_handler(ska, err, NULL);
+}
+
+
+static bool udp_recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct stun_keepalive *ska = arg;
+ struct stun_unknown_attr ua;
+ struct stun_msg *msg;
+ size_t pos = mb->pos;
+ bool hdld;
+
+ if (!sa_cmp(&ska->dst, src, SA_ALL))
+ return false;
+
+ if (stun_msg_decode(&msg, mb, &ua))
+ return false;
+
+ if (stun_msg_method(msg) != STUN_METHOD_BINDING) {
+ hdld = false;
+ mb->pos = pos;
+ goto out;
+ }
+
+ switch (stun_msg_class(msg)) {
+
+ case STUN_CLASS_ERROR_RESP:
+ case STUN_CLASS_SUCCESS_RESP:
+ (void)stun_ctrans_recv(ska->stun, msg, &ua);
+ hdld = true;
+ break;
+
+ default:
+ hdld = false;
+ mb->pos = pos;
+ break;
+ }
+
+ out:
+ mem_deref(msg);
+
+ return hdld;
+}
+
+
+/**
+ * Allocate a new STUN keepalive session
+ *
+ * @param skap Pointer to keepalive object
+ * @param proto Transport protocol
+ * @param sock Socket
+ * @param layer Protocol layer
+ * @param dst Destination address
+ * @param conf Configuration
+ * @param mah Mapped address handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_keepalive_alloc(struct stun_keepalive **skap,
+ int proto, void *sock, int layer,
+ const struct sa *dst, const struct stun_conf *conf,
+ stun_mapped_addr_h *mah, void *arg)
+{
+ struct stun_keepalive *ska;
+ int err;
+
+ if (!skap)
+ return EINVAL;
+
+ ska = mem_zalloc(sizeof(*ska), keepalive_destructor);
+ if (!ska)
+ return ENOMEM;
+
+ err = stun_alloc(&ska->stun, conf, NULL, NULL);
+ if (err)
+ goto out;
+
+ tmr_init(&ska->tmr);
+
+ ska->proto = proto;
+ ska->sock = mem_ref(sock);
+ ska->mah = mah;
+ ska->arg = arg;
+
+ if (dst)
+ ska->dst = *dst;
+
+ switch (proto) {
+
+ case IPPROTO_UDP:
+ err = udp_register_helper(&ska->uh, sock, layer,
+ NULL, udp_recv_handler, ska);
+ break;
+
+ default:
+ err = 0;
+ break;
+ }
+
+ out:
+ if (err)
+ mem_deref(ska);
+ else
+ *skap = ska;
+
+ return err;
+}
+
+
+/**
+ * Enable or disable keepalive timer
+ *
+ * @param ska Keepalive object
+ * @param interval Interval in seconds (0 to disable)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+void stun_keepalive_enable(struct stun_keepalive *ska, uint32_t interval)
+{
+ if (!ska)
+ return;
+
+ ska->interval = interval;
+
+ tmr_cancel(&ska->tmr);
+ if (interval > 0)
+ tmr_start(&ska->tmr, 1, timeout, ska);
+}
diff --git a/src/stun/mod.mk b/src/stun/mod.mk
new file mode 100644
index 0000000..945c77f
--- /dev/null
+++ b/src/stun/mod.mk
@@ -0,0 +1,18 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += stun/addr.c
+SRCS += stun/attr.c
+SRCS += stun/ctrans.c
+SRCS += stun/dnsdisc.c
+SRCS += stun/hdr.c
+SRCS += stun/ind.c
+SRCS += stun/keepalive.c
+SRCS += stun/msg.c
+SRCS += stun/rep.c
+SRCS += stun/req.c
+SRCS += stun/stun.c
+SRCS += stun/stunstr.c
diff --git a/src/stun/msg.c b/src/stun/msg.c
new file mode 100644
index 0000000..dda3d8d
--- /dev/null
+++ b/src/stun/msg.c
@@ -0,0 +1,485 @@
+/**
+ * @file stun/msg.c STUN message encoding
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_fmt.h>
+#include <re_md5.h>
+#include <re_sha.h>
+#include <re_hmac.h>
+#include <re_crc32.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+enum {
+ MI_SIZE = 24,
+ FP_SIZE = 8
+};
+
+
+/**
+ Defines a STUN Message object
+
+ <pre>
+
+ .---------------------. /|\ /|\
+ | STUN Header | | |
+ |---------------------| | |
+ | .... | |--------. |
+ | N Attributes | | | |----.
+ | .... | \|/ | | |
+ |---------------------| | | |
+ | MESSAGE-INTEGRITY | <-(HMAC-SHA1)--' \|/ |
+ |---------------------| |
+ | FINGERPRINT | <-(CRC-32)---------------'
+ '---------------------'
+ </pre>
+*/
+struct stun_msg {
+ struct stun_hdr hdr;
+ struct list attrl;
+ struct mbuf *mb;
+ size_t start;
+};
+
+
+static uint32_t fingerprint(const uint8_t *buf, size_t len)
+{
+ return (uint32_t)crc32(0, buf, (unsigned int)len) ^ 0x5354554e;
+}
+
+
+static void destructor(void *arg)
+{
+ struct stun_msg *msg = arg;
+
+ list_flush(&msg->attrl);
+ mem_deref(msg->mb);
+}
+
+
+/**
+ * Decode a buffer to a STUN Message
+ *
+ * @param msgpp Pointer to allocation STUN message
+ * @param mb Buffer containing the raw STUN packet
+ * @param ua Unknown attributes (optional)
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * @note `mb' will be referenced
+ */
+int stun_msg_decode(struct stun_msg **msgpp, struct mbuf *mb,
+ struct stun_unknown_attr *ua)
+{
+ struct stun_msg *msg;
+ struct stun_hdr hdr;
+ size_t start, extra;
+ int err;
+
+ if (!msgpp || !mb)
+ return EINVAL;
+
+ start = mb->pos;
+
+ err = stun_hdr_decode(mb, &hdr);
+ if (err) {
+ mb->pos = start;
+ return err;
+ }
+
+ msg = mem_zalloc(sizeof(*msg), destructor);
+ if (!msg) {
+ mb->pos = start;
+ return ENOMEM;
+ }
+
+ msg->hdr = hdr;
+ msg->mb = mem_ref(mb);
+ msg->start = start;
+
+ if (ua)
+ ua->typec = 0;
+
+ /* mbuf_get_left(mb) >= hdr.len checked in stun_hdr_decode() above */
+ extra = mbuf_get_left(mb) - hdr.len;
+
+ while (mbuf_get_left(mb) - extra >= 4) {
+
+ struct stun_attr *attr;
+
+ err = stun_attr_decode(&attr, mb, hdr.tid, ua);
+ if (err)
+ break;
+
+ list_append(&msg->attrl, &attr->le, attr);
+ }
+
+ if (err)
+ mem_deref(msg);
+ else
+ *msgpp = msg;
+
+ mb->pos = start;
+
+ return err;
+}
+
+
+/**
+ * Get the STUN message type
+ *
+ * @param msg STUN Message
+ *
+ * @return STUN Message type
+ */
+uint16_t stun_msg_type(const struct stun_msg *msg)
+{
+ return msg ? msg->hdr.type : 0;
+}
+
+
+/**
+ * Get the STUN message class
+ *
+ * @param msg STUN Message
+ *
+ * @return STUN Message class
+ */
+uint16_t stun_msg_class(const struct stun_msg *msg)
+{
+ return STUN_CLASS(stun_msg_type(msg));
+}
+
+
+/**
+ * Get the STUN message method
+ *
+ * @param msg STUN Message
+ *
+ * @return STUN Message method
+ */
+uint16_t stun_msg_method(const struct stun_msg *msg)
+{
+ return STUN_METHOD(stun_msg_type(msg));
+}
+
+
+/**
+ * Get the STUN message Transaction-ID
+ *
+ * @param msg STUN Message
+ *
+ * @return STUN Message Transaction-ID
+ */
+const uint8_t *stun_msg_tid(const struct stun_msg *msg)
+{
+ return msg ? msg->hdr.tid : NULL;
+}
+
+
+/**
+ * Check if a STUN Message has the magic cookie
+ *
+ * @param msg STUN Message
+ *
+ * @return true if Magic Cookie, otherwise false
+ */
+bool stun_msg_mcookie(const struct stun_msg *msg)
+{
+ return msg && (STUN_MAGIC_COOKIE == msg->hdr.cookie);
+}
+
+
+/**
+ * Lookup a STUN attribute in a STUN message
+ *
+ * @param msg STUN Message
+ * @param type STUN Attribute type
+ *
+ * @return STUN Attribute if found, otherwise NULL
+ */
+struct stun_attr *stun_msg_attr(const struct stun_msg *msg, uint16_t type)
+{
+ struct le *le = msg ? list_head(&msg->attrl) : NULL;
+
+ while (le) {
+ struct stun_attr *attr = le->data;
+
+ le = le->next;
+
+ if (attr->type == type)
+ return attr;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Apply a function handler to all STUN attribute
+ *
+ * @param msg STUN Message
+ * @param h Attribute handler
+ * @param arg Handler argument
+ *
+ * @return STUN attribute if handler returned true, otherwise NULL
+ */
+struct stun_attr *stun_msg_attr_apply(const struct stun_msg *msg,
+ stun_attr_h *h, void *arg)
+{
+ struct le *le = msg ? list_head(&msg->attrl) : NULL;
+
+ while (le) {
+ struct stun_attr *attr = le->data;
+
+ le = le->next;
+
+ if (h && h(attr, arg))
+ return (attr);
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Encode a STUN message
+ *
+ * @param mb Buffer to encode message into
+ * @param method STUN Method
+ * @param class STUN Method class
+ * @param tid Transaction ID
+ * @param ec STUN error code (optional)
+ * @param key Authentication key (optional)
+ * @param keylen Number of bytes in authentication key
+ * @param fp Use STUN Fingerprint attribute
+ * @param padding Padding byte
+ * @param attrc Number of attributes to encode (variable arguments)
+ * @param ap Variable list of attribute-tuples
+ * Each attribute has 2 arguments, attribute type and value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_msg_vencode(struct mbuf *mb, uint16_t method, uint8_t class,
+ const uint8_t *tid, const struct stun_errcode *ec,
+ const uint8_t *key, size_t keylen, bool fp,
+ uint8_t padding, uint32_t attrc, va_list ap)
+{
+ struct stun_hdr hdr;
+ size_t start;
+ int err = 0;
+ uint32_t i;
+
+ if (!mb || !tid)
+ return EINVAL;
+
+ start = mb->pos;
+ mb->pos += STUN_HEADER_SIZE;
+
+ hdr.type = STUN_TYPE(method, class);
+ hdr.cookie = STUN_MAGIC_COOKIE;
+ memcpy(hdr.tid, tid, STUN_TID_SIZE);
+
+ if (ec)
+ err |= stun_attr_encode(mb, STUN_ATTR_ERR_CODE, ec,
+ NULL, padding);
+
+ for (i=0; i<attrc; i++) {
+
+ uint16_t type = va_arg(ap, int);
+ const void *v = va_arg(ap, const void *);
+
+ if (!v)
+ continue;
+
+ err |= stun_attr_encode(mb, type, v, hdr.tid, padding);
+ }
+
+ /* header */
+ hdr.len = mb->pos - start - STUN_HEADER_SIZE + (key ? MI_SIZE : 0);
+ mb->pos = start;
+ err |= stun_hdr_encode(mb, &hdr);
+ mb->pos += hdr.len - (key ? MI_SIZE : 0);
+
+ if (key) {
+ uint8_t mi[20];
+
+ mb->pos = start;
+ hmac_sha1(key, keylen, mbuf_buf(mb), mbuf_get_left(mb),
+ mi, sizeof(mi));
+
+ mb->pos += STUN_HEADER_SIZE + hdr.len - MI_SIZE;
+ err |= stun_attr_encode(mb, STUN_ATTR_MSG_INTEGRITY, mi,
+ NULL, padding);
+ }
+
+ if (fp) {
+ uint32_t fprnt;
+
+ /* header */
+ hdr.len = mb->pos - start - STUN_HEADER_SIZE + FP_SIZE;
+ mb->pos = start;
+ err |= stun_hdr_encode(mb, &hdr);
+
+ mb->pos = start;
+ fprnt = fingerprint(mbuf_buf(mb), mbuf_get_left(mb));
+
+ mb->pos += STUN_HEADER_SIZE + hdr.len - FP_SIZE;
+ err |= stun_attr_encode(mb, STUN_ATTR_FINGERPRINT, &fprnt,
+ NULL, padding);
+ }
+
+ return err;
+}
+
+
+/**
+ * Encode a STUN message
+ *
+ * @param mb Buffer to encode message into
+ * @param method STUN Method
+ * @param class STUN Method class
+ * @param tid Transaction ID
+ * @param ec STUN error code (optional)
+ * @param key Authentication key (optional)
+ * @param keylen Number of bytes in authentication key
+ * @param fp Use STUN Fingerprint attribute
+ * @param padding Padding byte
+ * @param attrc Number of attributes to encode (variable arguments)
+ * @param ... Variable list of attribute-tuples
+ * Each attribute has 2 arguments, attribute type and value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_msg_encode(struct mbuf *mb, uint16_t method, uint8_t class,
+ const uint8_t *tid, const struct stun_errcode *ec,
+ const uint8_t *key, size_t keylen, bool fp,
+ uint8_t padding, uint32_t attrc, ...)
+{
+ va_list ap;
+ int err;
+
+ va_start(ap, attrc);
+ err = stun_msg_vencode(mb, method, class, tid, ec, key, keylen, fp,
+ padding, attrc, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+/**
+ * Verify the Message-Integrity of a STUN message
+ *
+ * @param msg STUN Message
+ * @param key Authentication key
+ * @param keylen Number of bytes in authentication key
+ *
+ * @return 0 if verified, otherwise errorcode
+ */
+int stun_msg_chk_mi(const struct stun_msg *msg, const uint8_t *key,
+ size_t keylen)
+{
+ uint8_t hmac[SHA_DIGEST_LENGTH];
+ struct stun_attr *mi, *fp;
+
+ if (!msg)
+ return EINVAL;
+
+ mi = stun_msg_attr(msg, STUN_ATTR_MSG_INTEGRITY);
+ if (!mi)
+ return EPROTO;
+
+ msg->mb->pos = msg->start;
+
+ fp = stun_msg_attr(msg, STUN_ATTR_FINGERPRINT);
+ if (fp) {
+ ((struct stun_msg *)msg)->hdr.len -= FP_SIZE;
+ (void)stun_hdr_encode(msg->mb, &msg->hdr);
+ msg->mb->pos -= STUN_HEADER_SIZE;
+ }
+
+ hmac_sha1(key, keylen, mbuf_buf(msg->mb),
+ STUN_HEADER_SIZE + msg->hdr.len - MI_SIZE,
+ hmac, sizeof(hmac));
+
+ if (fp) {
+ ((struct stun_msg *)msg)->hdr.len += FP_SIZE;
+ (void)stun_hdr_encode(msg->mb, &msg->hdr);
+ msg->mb->pos -= STUN_HEADER_SIZE;
+ }
+
+ if (memcmp(mi->v.msg_integrity, hmac, SHA_DIGEST_LENGTH))
+ return EBADMSG;
+
+ return 0;
+}
+
+
+/**
+ * Check the Fingerprint of a STUN message
+ *
+ * @param msg STUN Message
+ *
+ * @return 0 if fingerprint matches, otherwise errorcode
+ */
+int stun_msg_chk_fingerprint(const struct stun_msg *msg)
+{
+ struct stun_attr *fp;
+ uint32_t fprnt;
+
+ if (!msg)
+ return EINVAL;
+
+ fp = stun_msg_attr(msg, STUN_ATTR_FINGERPRINT);
+ if (!fp)
+ return EPROTO;
+
+ msg->mb->pos = msg->start;
+
+ fprnt = fingerprint(mbuf_buf(msg->mb),
+ STUN_HEADER_SIZE + msg->hdr.len - FP_SIZE);
+
+ if (fprnt != fp->v.fingerprint)
+ return EBADMSG;
+
+ return 0;
+}
+
+
+static bool attr_print(const struct stun_attr *attr, void *arg)
+{
+ (void)arg;
+
+ stun_attr_dump(attr);
+
+ return false;
+}
+
+
+/**
+ * Print a STUN message to STDOUT
+ *
+ * @param msg STUN Message
+ */
+void stun_msg_dump(const struct stun_msg *msg)
+{
+ if (!msg)
+ return;
+
+ (void)re_printf("%s %s (len=%u cookie=%08x tid=%w)\n",
+ stun_method_name(stun_msg_method(msg)),
+ stun_class_name(stun_msg_class(msg)),
+ msg->hdr.len, msg->hdr.cookie,
+ msg->hdr.tid, sizeof(msg->hdr.tid));
+
+ stun_msg_attr_apply(msg, attr_print, NULL);
+}
diff --git a/src/stun/rep.c b/src/stun/rep.c
new file mode 100644
index 0000000..11ffbfd
--- /dev/null
+++ b/src/stun/rep.c
@@ -0,0 +1,121 @@
+/**
+ * @file stun/rep.c STUN reply
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+/**
+ * Send a STUN response message
+ *
+ * @param proto Transport Protocol
+ * @param sock Socket; UDP (struct udp_sock) or TCP (struct tcp_conn)
+ * @param dst Destination network address
+ * @param presz Number of bytes in preamble, if sending over TURN
+ * @param req Matching STUN request
+ * @param key Authentication key (optional)
+ * @param keylen Number of bytes in authentication key
+ * @param fp Use STUN Fingerprint attribute
+ * @param attrc Number of attributes to encode (variable arguments)
+ * @param ... Variable list of attribute-tuples
+ * Each attribute has 2 arguments, attribute type and value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_reply(int proto, void *sock, const struct sa *dst, size_t presz,
+ const struct stun_msg *req, const uint8_t *key,
+ size_t keylen, bool fp, uint32_t attrc, ...)
+{
+ struct mbuf *mb = NULL;
+ int err = ENOMEM;
+ va_list ap;
+
+ if (!sock || !req)
+ return EINVAL;
+
+ mb = mbuf_alloc(256);
+ if (!mb)
+ goto out;
+
+ va_start(ap, attrc);
+ mb->pos = presz;
+ err = stun_msg_vencode(mb, stun_msg_method(req),
+ STUN_CLASS_SUCCESS_RESP, stun_msg_tid(req),
+ NULL, key, keylen, fp, 0x00, attrc, ap);
+ va_end(ap);
+ if (err)
+ goto out;
+
+ mb->pos = presz;
+ err = stun_send(proto, sock, dst, mb);
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Send a STUN error response
+ *
+ * @param proto Transport Protocol
+ * @param sock Socket; UDP (struct udp_sock) or TCP (struct tcp_conn)
+ * @param dst Destination network address
+ * @param presz Number of bytes in preamble, if sending over TURN
+ * @param req Matching STUN request
+ * @param scode Status code
+ * @param reason Reason string
+ * @param key Authentication key (optional)
+ * @param keylen Number of bytes in authentication key
+ * @param fp Use STUN Fingerprint attribute
+ * @param attrc Number of attributes to encode (variable arguments)
+ * @param ... Variable list of attribute-tuples
+ * Each attribute has 2 arguments, attribute type and value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_ereply(int proto, void *sock, const struct sa *dst, size_t presz,
+ const struct stun_msg *req, uint16_t scode,
+ const char *reason, const uint8_t *key, size_t keylen,
+ bool fp, uint32_t attrc, ...)
+{
+ struct stun_errcode ec;
+ struct mbuf *mb = NULL;
+ int err = ENOMEM;
+ va_list ap;
+
+ if (!sock || !req || !scode || !reason)
+ return EINVAL;
+
+ mb = mbuf_alloc(256);
+ if (!mb)
+ goto out;
+
+ ec.code = scode;
+ ec.reason = (char *)reason;
+
+ va_start(ap, attrc);
+ mb->pos = presz;
+ err = stun_msg_vencode(mb, stun_msg_method(req), STUN_CLASS_ERROR_RESP,
+ stun_msg_tid(req), &ec, key, keylen,
+ fp, 0x00, attrc, ap);
+ va_end(ap);
+ if (err)
+ goto out;
+
+ mb->pos = presz;
+ err = stun_send(proto, sock, dst, mb);
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
diff --git a/src/stun/req.c b/src/stun/req.c
new file mode 100644
index 0000000..32e127d
--- /dev/null
+++ b/src/stun/req.c
@@ -0,0 +1,76 @@
+/**
+ * @file stun/req.c STUN request
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_sys.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+/**
+ * Send a STUN request using a client transaction
+ *
+ * @param ctp Pointer to allocated client transaction (optional)
+ * @param stun STUN Instance
+ * @param proto Transport Protocol
+ * @param sock Socket; UDP (struct udp_sock) or TCP (struct tcp_conn)
+ * @param dst Destination network address
+ * @param presz Number of bytes in preamble, if sending over TURN
+ * @param method STUN Method
+ * @param key Authentication key (optional)
+ * @param keylen Number of bytes in authentication key
+ * @param fp Use STUN Fingerprint attribute
+ * @param resph Response handler
+ * @param arg Response handler argument
+ * @param attrc Number of attributes to encode (variable arguments)
+ * @param ... Variable list of attribute-tuples
+ * Each attribute has 2 arguments, attribute type and value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_request(struct stun_ctrans **ctp, struct stun *stun, int proto,
+ void *sock, const struct sa *dst, size_t presz,
+ uint16_t method, const uint8_t *key, size_t keylen, bool fp,
+ stun_resp_h *resph, void *arg, uint32_t attrc, ...)
+{
+ uint8_t tid[STUN_TID_SIZE];
+ struct mbuf *mb;
+ uint32_t i;
+ va_list ap;
+ int err;
+
+ if (!stun)
+ return EINVAL;
+
+ mb = mbuf_alloc(512);
+ if (!mb)
+ return ENOMEM;
+
+ for (i=0; i<STUN_TID_SIZE; i++)
+ tid[i] = rand_u32();
+
+ va_start(ap, attrc);
+ mb->pos = presz;
+ err = stun_msg_vencode(mb, method, STUN_CLASS_REQUEST,
+ tid, NULL, key, keylen, fp, 0x00, attrc, ap);
+ va_end(ap);
+ if (err)
+ goto out;
+
+ mb->pos = presz;
+ err = stun_ctrans_request(ctp, stun, proto, sock, dst, mb, tid, method,
+ key, keylen, resph, arg);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
diff --git a/src/stun/stun.c b/src/stun/stun.c
new file mode 100644
index 0000000..6dfdd97
--- /dev/null
+++ b/src/stun/stun.c
@@ -0,0 +1,188 @@
+/**
+ * @file stun.c STUN stack
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_srtp.h>
+#include <re_tls.h>
+#include <re_sys.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include "stun.h"
+
+
+const char *stun_software = "libre v" VERSION " (" ARCH "/" OS ")";
+
+
+static const struct stun_conf conf_default = {
+ STUN_DEFAULT_RTO,
+ STUN_DEFAULT_RC,
+ STUN_DEFAULT_RM,
+ STUN_DEFAULT_TI,
+ 0x00
+};
+
+
+static void destructor(void *arg)
+{
+ struct stun *stun = arg;
+
+ stun_ctrans_close(stun);
+}
+
+
+/**
+ * Allocate a new STUN instance
+ *
+ * @param stunp Pointer to allocated STUN instance
+ * @param conf STUN configuration (optional)
+ * @param indh STUN Indication handler (optional)
+ * @param arg STUN Indication handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_alloc(struct stun **stunp, const struct stun_conf *conf,
+ stun_ind_h *indh, void *arg)
+{
+ struct stun *stun;
+
+ if (!stunp)
+ return EINVAL;
+
+ stun = mem_zalloc(sizeof(*stun), destructor);
+ if (!stun)
+ return ENOMEM;
+
+ stun->conf = conf ? *conf : conf_default;
+ stun->indh = indh;
+ stun->arg = arg;
+
+ *stunp = stun;
+
+ return 0;
+}
+
+
+/**
+ * Get STUN configuration object
+ *
+ * @param stun STUN Instance
+ *
+ * @return STUN configuration
+ */
+struct stun_conf *stun_conf(struct stun *stun)
+{
+ return stun ? &stun->conf : NULL;
+}
+
+
+/**
+ * Send a STUN message
+ *
+ * @param proto Transport protocol (IPPROTO_UDP or IPPROTO_TCP)
+ * @param sock Socket, UDP (struct udp_sock) or TCP (struct tcp_conn)
+ * @param dst Destination network address (UDP only)
+ * @param mb Buffer containing the STUN message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_send(int proto, void *sock, const struct sa *dst, struct mbuf *mb)
+{
+ int err;
+
+ if (!sock || !mb)
+ return EINVAL;
+
+ switch (proto) {
+
+ case IPPROTO_UDP:
+ err = udp_send(sock, dst, mb);
+ break;
+
+ case IPPROTO_TCP:
+ err = tcp_send(sock, mb);
+ break;
+
+#ifdef USE_DTLS
+ case STUN_TRANSP_DTLS:
+ err = dtls_send(sock, mb);
+ break;
+#endif
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ return err;
+}
+
+
+/**
+ * Receive a STUN message
+ *
+ * @param stun STUN Instance
+ * @param mb Buffer containing STUN message
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_recv(struct stun *stun, struct mbuf *mb)
+{
+ struct stun_unknown_attr ua;
+ struct stun_msg *msg;
+ int err;
+
+ if (!stun || !mb)
+ return EINVAL;
+
+ err = stun_msg_decode(&msg, mb, &ua);
+ if (err)
+ return err;
+
+ switch (stun_msg_class(msg)) {
+
+ case STUN_CLASS_INDICATION:
+ if (ua.typec > 0)
+ break;
+
+ if (stun->indh)
+ stun->indh(msg, stun->arg);
+ break;
+
+ case STUN_CLASS_ERROR_RESP:
+ case STUN_CLASS_SUCCESS_RESP:
+ err = stun_ctrans_recv(stun, msg, &ua);
+ break;
+
+ default:
+ break;
+ }
+
+ mem_deref(msg);
+
+ return err;
+}
+
+
+/**
+ * Print STUN instance debug information
+ *
+ * @param pf Print function
+ * @param stun STUN Instance
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int stun_debug(struct re_printf *pf, const struct stun *stun)
+{
+ if (!stun)
+ return 0;
+
+ return re_hprintf(pf, "STUN debug:\n%H", stun_ctrans_debug, stun);
+}
diff --git a/src/stun/stun.h b/src/stun/stun.h
new file mode 100644
index 0000000..cda84fa
--- /dev/null
+++ b/src/stun/stun.h
@@ -0,0 +1,64 @@
+/**
+ * @file stun.h Internal STUN interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** STUN Protocol values */
+enum {
+ STUN_MAGIC_COOKIE = 0x2112a442 /**< Magic Cookie for 3489bis */
+};
+
+
+/** Calculate STUN message type from method and class */
+#define STUN_TYPE(method, class) \
+ ((method)&0x0f80) << 2 | \
+ ((method)&0x0070) << 1 | \
+ ((method)&0x000f) << 0 | \
+ ((class)&0x2) << 7 | \
+ ((class)&0x1) << 4
+
+
+#define STUN_CLASS(type) \
+ ((type >> 7 | type >> 4) & 0x3)
+
+
+#define STUN_METHOD(type) \
+ ((type&0x3e00)>>2 | (type&0x00e0)>>1 | (type&0x000f))
+
+
+struct stun_hdr {
+ uint16_t type; /**< Message type */
+ uint16_t len; /**< Payload length */
+ uint32_t cookie; /**< Magic cookie */
+ uint8_t tid[STUN_TID_SIZE]; /**< Transaction ID */
+};
+
+
+struct stun {
+ struct list ctl;
+ struct stun_conf conf;
+ stun_ind_h *indh;
+ void *arg;
+};
+
+int stun_hdr_encode(struct mbuf *mb, const struct stun_hdr *hdr);
+int stun_hdr_decode(struct mbuf *mb, struct stun_hdr *hdr);
+
+int stun_attr_encode(struct mbuf *mb, uint16_t type, const void *v,
+ const uint8_t *tid, uint8_t padding);
+int stun_attr_decode(struct stun_attr **attrp, struct mbuf *mb,
+ const uint8_t *tid, struct stun_unknown_attr *ua);
+void stun_attr_dump(const struct stun_attr *a);
+
+int stun_addr_encode(struct mbuf *mb, const struct sa *addr,
+ const uint8_t *tid);
+int stun_addr_decode(struct mbuf *mb, struct sa *addr, const uint8_t *tid);
+
+int stun_ctrans_request(struct stun_ctrans **ctp, struct stun *stun, int proto,
+ void *sock, const struct sa *dst, struct mbuf *mb,
+ const uint8_t tid[], uint16_t met, const uint8_t *key,
+ size_t keylen, stun_resp_h *resph, void *arg);
+void stun_ctrans_close(struct stun *stun);
+int stun_ctrans_debug(struct re_printf *pf, const struct stun *stun);
diff --git a/src/stun/stunstr.c b/src/stun/stunstr.c
new file mode 100644
index 0000000..2d0de09
--- /dev/null
+++ b/src/stun/stunstr.c
@@ -0,0 +1,46 @@
+/**
+ * @file stunstr.c STUN Strings
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_stun.h>
+
+
+/* STUN Reason Phrase */
+const char *stun_reason_300 = "Try Alternate";
+const char *stun_reason_400 = "Bad Request";
+const char *stun_reason_401 = "Unauthorized";
+const char *stun_reason_403 = "Forbidden";
+const char *stun_reason_420 = "Unknown Attribute";
+const char *stun_reason_437 = "Allocation Mismatch";
+const char *stun_reason_438 = "Stale Nonce";
+const char *stun_reason_440 = "Address Family not Supported";
+const char *stun_reason_441 = "Wrong Credentials";
+const char *stun_reason_442 = "Unsupported Transport Protocol";
+const char *stun_reason_443 = "Peer Address Family Mismatch";
+const char *stun_reason_486 = "Allocation Quota Reached";
+const char *stun_reason_500 = "Server Error";
+const char *stun_reason_508 = "Insufficient Capacity";
+
+
+/**
+ * Get the name of a given STUN Transport
+ *
+ * @param tp STUN Transport
+ *
+ * @return Name of the corresponding STUN Transport
+ */
+const char *stun_transp_name(enum stun_transp tp)
+{
+ switch (tp) {
+
+ case STUN_TRANSP_UDP: return "UDP";
+ case STUN_TRANSP_TCP: return "TCP";
+ case STUN_TRANSP_DTLS: return "DTLS";
+ default: return "???";
+ }
+}
diff --git a/src/sys/daemon.c b/src/sys/daemon.c
new file mode 100644
index 0000000..7ba14db
--- /dev/null
+++ b/src/sys/daemon.c
@@ -0,0 +1,62 @@
+/**
+ * @file daemon.c Daemonize process
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <signal.h>
+#include <stdio.h>
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_sys.h>
+
+
+/**
+ * Daemonize process
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sys_daemon(void)
+{
+#ifdef HAVE_FORK
+ pid_t pid;
+
+ pid = fork();
+ if (-1 == pid)
+ return errno;
+ else if (pid > 0)
+ exit(0);
+
+ if (-1 == setsid())
+ return errno;
+
+ (void)signal(SIGHUP, SIG_IGN);
+
+ pid = fork();
+ if (-1 == pid)
+ return errno;
+ else if (pid > 0)
+ exit(0);
+
+ if (-1 == chdir("/"))
+ return errno;
+ (void)umask(0);
+
+ /* Redirect standard files to /dev/null */
+ if (freopen("/dev/null", "r", stdin) == NULL)
+ return errno;
+ if (freopen("/dev/null", "w", stdout) == NULL)
+ return errno;
+ if (freopen("/dev/null", "w", stderr) == NULL)
+ return errno;
+
+ return 0;
+#else
+ return ENOSYS;
+#endif
+}
diff --git a/src/sys/endian.c b/src/sys/endian.c
new file mode 100644
index 0000000..c43c262
--- /dev/null
+++ b/src/sys/endian.c
@@ -0,0 +1,143 @@
+/**
+ * @file endian.c Endianness converting routines
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_sys.h>
+
+
+/*
+ * These routes are working on both little-endian and big-endian platforms.
+ */
+
+
+/**
+ * Convert a 16-bit value from host order to little endian
+ *
+ * @param v 16-bit in host order
+ *
+ * @return 16-bit little endian value
+ */
+uint16_t sys_htols(uint16_t v)
+{
+ uint8_t *p = (uint8_t *)&v;
+ uint16_t l = 0;
+
+ l |= (uint16_t)*p++ << 0;
+ l |= (uint16_t)*p << 8;
+
+ return l;
+}
+
+
+/**
+ * Convert a 32-bit value from host order to little endian
+ *
+ * @param v 32-bit in host order
+ *
+ * @return 32-bit little endian value
+ */
+uint32_t sys_htoll(uint32_t v)
+{
+ uint8_t *p = (uint8_t *)&v;
+ uint32_t l = 0;
+
+ l |= (uint32_t)*p++ << 0;
+ l |= (uint32_t)*p++ << 8;
+ l |= (uint32_t)*p++ << 16;
+ l |= (uint32_t)*p << 24;
+
+ return l;
+}
+
+
+/**
+ * Convert a 16-bit value from little endian to host order
+ *
+ * @param v 16-bit little endian value
+ *
+ * @return 16-bit value in host order
+ */
+uint16_t sys_ltohs(uint16_t v)
+{
+ uint16_t s;
+ uint8_t *p = (uint8_t *)&s;
+
+ *p++ = v>>0 & 0xff;
+ *p = v>>8 & 0xff;
+
+ return s;
+}
+
+
+/**
+ * Convert a 32-bit value from little endian to host order
+ *
+ * @param v 32-bit little endian value
+ *
+ * @return 32-bit value in host order
+ */
+uint32_t sys_ltohl(uint32_t v)
+{
+ uint32_t h;
+ uint8_t *p = (uint8_t *)&h;
+
+ *p++ = v>>0 & 0xff;
+ *p++ = v>>8 & 0xff;
+ *p++ = v>>16 & 0xff;
+ *p = v>>24 & 0xff;
+
+ return h;
+}
+
+
+/**
+ * Convert a 64-bit value from host to network byte-order
+ *
+ * @param v 64-bit host byte-order value
+ *
+ * @return 64-bit value in network byte-order
+ */
+uint64_t sys_htonll(uint64_t v)
+{
+ uint64_t h = 0;
+ uint8_t *p = (uint8_t *)&v;
+
+ h |= (uint64_t)*p++ << 56;
+ h |= (uint64_t)*p++ << 48;
+ h |= (uint64_t)*p++ << 40;
+ h |= (uint64_t)*p++ << 32;
+ h |= (uint64_t)*p++ << 24;
+ h |= (uint64_t)*p++ << 16;
+ h |= (uint64_t)*p++ << 8;
+ h |= (uint64_t)*p << 0;
+
+ return h;
+}
+
+
+/**
+ * Convert a 64-bit value from network to host byte-order
+ *
+ * @param v 64-bit network byte-order value
+ *
+ * @return 64-bit value in host byte-order
+ */
+uint64_t sys_ntohll(uint64_t v)
+{
+ uint64_t h;
+ uint8_t *p = (uint8_t *)&h;
+
+ *p++ = (uint8_t) (v>>56 & 0xff);
+ *p++ = (uint8_t) (v>>48 & 0xff);
+ *p++ = (uint8_t) (v>>40 & 0xff);
+ *p++ = (uint8_t) (v>>32 & 0xff);
+ *p++ = (uint8_t) (v>>24 & 0xff);
+ *p++ = (uint8_t) (v>>16 & 0xff);
+ *p++ = (uint8_t) (v>>8 & 0xff);
+ *p = (uint8_t) (v>>0 & 0xff);
+
+ return h;
+}
diff --git a/src/sys/fs.c b/src/sys/fs.c
new file mode 100644
index 0000000..033e636
--- /dev/null
+++ b/src/sys/fs.c
@@ -0,0 +1,107 @@
+/**
+ * @file fs.c File-system functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+#ifdef WIN32
+#include <windows.h>
+#include <shlobj.h>
+#include <direct.h>
+#include <lmaccess.h>
+#endif
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_sys.h>
+
+
+/**
+ * Create a directory with full path
+ *
+ * @param path Directory path
+ * @param mode Access permissions
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int fs_mkdir(const char *path, uint16_t mode)
+{
+ int ret;
+
+ if (!path)
+ return EINVAL;
+
+#if defined (WIN32)
+ (void)mode;
+ ret = _mkdir(path);
+#else
+ ret = mkdir(path, mode);
+#endif
+ if (ret < 0)
+ return errno;
+
+ return 0;
+}
+
+
+/**
+ * Get the home directory for the current user
+ *
+ * @param path String to write home directory
+ * @param sz Size of path string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int fs_gethome(char *path, size_t sz)
+{
+#ifdef WIN32
+ char win32_path[MAX_PATH];
+
+ if (!path || !sz)
+ return EINVAL;
+
+ if (S_OK != SHGetFolderPath(NULL,
+ CSIDL_APPDATA | CSIDL_FLAG_CREATE,
+ NULL,
+ 0,
+ win32_path)) {
+ return ENOENT;
+ }
+
+ str_ncpy(path, win32_path, sz);
+
+ return 0;
+
+#elif defined(HAVE_PWD_H)
+ const char *loginname;
+ struct passwd *pw;
+
+ if (!path || !sz)
+ return EINVAL;
+
+ loginname = sys_username();
+ if (!loginname)
+ return ENOENT;
+
+ pw = getpwnam(loginname);
+ if (!pw)
+ return errno;
+
+ str_ncpy(path, pw->pw_dir, sz);
+
+ return 0;
+#else
+ (void)path;
+ (void)sz;
+ return ENOSYS;
+#endif
+}
diff --git a/src/sys/mod.mk b/src/sys/mod.mk
new file mode 100644
index 0000000..616751e
--- /dev/null
+++ b/src/sys/mod.mk
@@ -0,0 +1,12 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += sys/daemon.c
+SRCS += sys/endian.c
+SRCS += sys/fs.c
+SRCS += sys/rand.c
+SRCS += sys/sleep.c
+SRCS += sys/sys.c
diff --git a/src/sys/rand.c b/src/sys/rand.c
new file mode 100644
index 0000000..d09fb93
--- /dev/null
+++ b/src/sys/rand.c
@@ -0,0 +1,180 @@
+/**
+ * @file rand.c Random generator
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#ifdef USE_OPENSSL
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#endif
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_sys.h>
+
+
+#define DEBUG_MODULE "rand"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+#ifndef RELEASE
+#define RAND_DEBUG 1 /**< Enable random debugging */
+#endif
+
+static const char alphanum[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789";
+
+#if RAND_DEBUG
+static bool inited = false;
+/** Check random state */
+#define RAND_CHECK \
+ if (!inited) { \
+ DEBUG_WARNING("%s: random not inited\n", __REFUNC__); \
+ }
+#else
+#define RAND_CHECK if (0) {}
+#endif
+
+
+/**
+ * Initialise random number generator
+ */
+void rand_init(void)
+{
+#ifndef USE_OPENSSL
+ srand((uint32_t) tmr_jiffies());
+#endif
+
+#if RAND_DEBUG
+ inited = true;
+#endif
+}
+
+
+/**
+ * Generate an unsigned 16-bit random value
+ *
+ * @return 16-bit random value
+ */
+uint16_t rand_u16(void)
+{
+ RAND_CHECK;
+
+ /* Use higher-order bits (see man 3 rand) */
+ return rand_u32() >> 16;
+}
+
+
+/**
+ * Generate an unsigned 32-bit random value
+ *
+ * @return 32-bit random value
+ */
+uint32_t rand_u32(void)
+{
+ uint32_t v;
+
+ RAND_CHECK;
+
+#ifdef USE_OPENSSL
+ v = 0;
+ if (RAND_bytes((unsigned char *)&v, sizeof(v)) <= 0) {
+ DEBUG_WARNING("RAND_bytes() error: %i\n",
+ ERR_GET_REASON(ERR_get_error()));
+ ERR_clear_error();
+ }
+#elif defined(HAVE_ARC4RANDOM)
+ v = arc4random();
+#elif defined(WIN32)
+ v = (rand() << 16) + rand(); /* note: 16-bit rand */
+#else
+ v = rand();
+#endif
+
+ return v;
+}
+
+
+/**
+ * Generate an unsigned 64-bit random value
+ *
+ * @return 64-bit random value
+ */
+uint64_t rand_u64(void)
+{
+ RAND_CHECK;
+
+ return (uint64_t)rand_u32()<<32 | rand_u32();
+}
+
+
+/**
+ * Generate a random printable character
+ *
+ * @return Random printable character
+ */
+char rand_char(void)
+{
+ char s[2];
+
+ RAND_CHECK;
+
+ rand_str(s, sizeof(s));
+
+ return s[0];
+}
+
+
+/**
+ * Generate a string of random characters
+ *
+ * @param str Pointer to string
+ * @param size Size of string
+ */
+void rand_str(char *str, size_t size)
+{
+ size_t i;
+
+ if (!str || !size)
+ return;
+
+ RAND_CHECK;
+
+ --size;
+
+ rand_bytes((uint8_t *)str, size);
+
+ for (i=0; i<size; i++)
+ str[i] = alphanum[((uint8_t)str[i]) % (sizeof(alphanum)-1)];
+
+ str[size] = '\0';
+}
+
+
+/**
+ * Generate a set of random bytes
+ *
+ * @param p Pointer to buffer
+ * @param size Size of buffer
+ */
+void rand_bytes(uint8_t *p, size_t size)
+{
+#ifdef USE_OPENSSL
+ if (RAND_bytes(p, (int)size) <= 0) {
+ DEBUG_WARNING("RAND_bytes() error: %i\n",
+ ERR_GET_REASON(ERR_get_error()));
+ ERR_clear_error();
+ }
+#elif defined (HAVE_ARC4RANDOM)
+ arc4random_buf(p, size);
+#else
+ while (size--) {
+ p[size] = rand_u32();
+ }
+#endif
+}
diff --git a/src/sys/sleep.c b/src/sys/sleep.c
new file mode 100644
index 0000000..33cdc08
--- /dev/null
+++ b/src/sys/sleep.c
@@ -0,0 +1,45 @@
+/**
+ * @file sleep.c System sleep functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_sys.h>
+#ifdef WIN32
+#include <windows.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#define _BSD_SOURCE 1
+#include <unistd.h>
+#endif
+#ifdef HAVE_SELECT_H
+#include <sys/select.h>
+#endif
+
+
+/**
+ * Blocking sleep for [us] number of microseconds
+ *
+ * @param us Number of microseconds to sleep
+ */
+void sys_usleep(unsigned int us)
+{
+ if (!us)
+ return;
+
+#ifdef WIN32
+ Sleep(us / 1000);
+#elif defined(HAVE_SELECT)
+ do {
+ struct timeval tv;
+
+ tv.tv_sec = us / 1000000;
+ tv.tv_usec = us % 1000000;
+
+ (void)select(0, NULL, NULL, NULL, &tv);
+ } while (0);
+#else
+ (void)usleep(us);
+#endif
+}
diff --git a/src/sys/sys.c b/src/sys/sys.c
new file mode 100644
index 0000000..a68f377
--- /dev/null
+++ b/src/sys/sys.c
@@ -0,0 +1,233 @@
+/**
+ * @file sys.c System information
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_sys.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_UNAME
+#include <sys/utsname.h>
+#endif
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#ifdef HAVE_SETRLIMIT
+#include <sys/resource.h>
+#endif
+
+
+/**
+ * Get system release version
+ *
+ * @param rel Binary encoded release
+ * @param maj Major version number
+ * @param min Minor version number
+ * @param patch Patch number
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sys_rel_get(uint32_t *rel, uint32_t *maj, uint32_t *min, uint32_t *patch)
+{
+#ifdef HAVE_UNAME
+ struct utsname u;
+ struct pl pl_mj, pl_mn, pl_p;
+ uint32_t mj, mn, p;
+ int err;
+
+ if (0 != uname(&u))
+ return errno;
+
+ err = re_regex(u.release, strlen(u.release),
+ "[0-9]+.[0-9]+[.\\-]1[0-9]+",
+ &pl_mj, &pl_mn, NULL, &pl_p);
+ if (err)
+ return err;
+
+ mj = pl_u32(&pl_mj);
+ mn = pl_u32(&pl_mn);
+ p = pl_u32(&pl_p);
+
+ if (rel)
+ *rel = mj<<16 | mn<<8 | p;
+ if (maj)
+ *maj = mj;
+ if (min)
+ *min = mn;
+ if (patch)
+ *patch = p;
+
+ return 0;
+#else
+ (void)rel;
+ (void)maj;
+ (void)min;
+ (void)patch;
+ return EINVAL;
+#endif
+}
+
+
+/**
+ * Get kernel name and version
+ *
+ * @param pf Print function for output
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sys_kernel_get(struct re_printf *pf, void *unused)
+{
+#ifdef HAVE_UNAME
+ struct utsname u;
+
+ (void)unused;
+
+ if (0 != uname(&u))
+ return errno;
+
+ return re_hprintf(pf, "%s %s %s %s %s", u.sysname, u.nodename,
+ u.release, u.version, u.machine);
+#else
+ const char *str;
+
+ (void)unused;
+
+#if defined(WIN32)
+ str = "Win32";
+#else
+ str = "?";
+#endif
+
+ return re_hprintf(pf, "%s", str);
+#endif
+}
+
+
+/**
+ * Get build info
+ *
+ * @param pf Print function for output
+ * @param unused Unused parameter
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sys_build_get(struct re_printf *pf, void *unused)
+{
+ const unsigned int bus_width = 8*sizeof(void *);
+ const char *endian = "unknown";
+
+ const uint32_t a = 0x12345678;
+ const uint8_t b0 = ((uint8_t *)&a)[0];
+ const uint8_t b1 = ((uint8_t *)&a)[1];
+ const uint8_t b2 = ((uint8_t *)&a)[2];
+ const uint8_t b3 = ((uint8_t *)&a)[3];
+
+ (void)unused;
+
+ if (0x12==b0 && 0x34==b1 && 0x56==b2 && 0x78==b3)
+ endian = "big";
+ else if (0x12==b3 && 0x34==b2 && 0x56==b1 && 0x78==b0)
+ endian = "little";
+
+ return re_hprintf(pf, "%u-bit %s endian", bus_width, endian);
+}
+
+
+/**
+ * Get architecture
+ *
+ * @return Architecture string
+ */
+const char *sys_arch_get(void)
+{
+#ifdef ARCH
+ return ARCH;
+#else
+ return "?";
+#endif
+}
+
+
+/**
+ * Get name of Operating System
+ *
+ * @return Operating System string
+ */
+const char *sys_os_get(void)
+{
+#ifdef OS
+ return OS;
+#else
+ return "?";
+#endif
+}
+
+
+/**
+ * Get libre version
+ *
+ * @return libre version string
+ */
+const char *sys_libre_version_get(void)
+{
+#ifdef VERSION
+ return VERSION;
+#else
+ return "?";
+#endif
+}
+
+
+/**
+ * Return the username (login name) for the current user
+ *
+ * @return Username or NULL if not available
+ */
+const char *sys_username(void)
+{
+#ifdef HAVE_PWD_H
+ char *login;
+
+ login = getenv("LOGNAME");
+ if (!login)
+ login = getenv("USER");
+#ifdef HAVE_UNISTD_H
+ if (!login) {
+ login = getlogin();
+ }
+#endif
+
+ return str_isset(login) ? login : NULL;
+#else
+ return NULL;
+#endif
+}
+
+
+/**
+ * Enable or disable coredump
+ *
+ * @param enable true to enable, false to disable coredump
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int sys_coredump_set(bool enable)
+{
+#ifdef HAVE_SETRLIMIT
+ const struct rlimit rlim = {
+ enable ? RLIM_INFINITY : 0,
+ enable ? RLIM_INFINITY : 0
+ };
+
+ return 0 == setrlimit(RLIMIT_CORE, &rlim) ? 0 : errno;
+#else
+ (void)enable;
+ return ENOSYS;
+#endif
+}
diff --git a/src/tcp/mod.mk b/src/tcp/mod.mk
new file mode 100644
index 0000000..5f52740
--- /dev/null
+++ b/src/tcp/mod.mk
@@ -0,0 +1,8 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += tcp/tcp.c
+SRCS += tcp/tcp_high.c
diff --git a/src/tcp/tcp.c b/src/tcp/tcp.c
new file mode 100644
index 0000000..f146b87
--- /dev/null
+++ b/src/tcp/tcp.c
@@ -0,0 +1,1378 @@
+/**
+ * @file tcp.c Transport Control Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_IO_H
+#include <io.h>
+#endif
+#if !defined(WIN32)
+#define __USE_POSIX 1 /**< Use POSIX flag */
+#define __USE_XOPEN2K 1/**< Use POSIX.1:2001 code */
+#define __USE_MISC 1
+#include <netdb.h>
+#endif
+#ifdef __APPLE__
+#include "TargetConditionals.h"
+#endif
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_main.h>
+#include <re_sa.h>
+#include <re_net.h>
+#include <re_tcp.h>
+
+
+#define DEBUG_MODULE "tcp"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Platform independent buffer type cast */
+#ifdef WIN32
+#define BUF_CAST (char *)
+#define SOK_CAST (int)
+#define SIZ_CAST (int)
+#define close closesocket
+#else
+#define BUF_CAST
+#define SOK_CAST
+#define SIZ_CAST
+#endif
+
+
+enum {
+ TCP_TXQSZ_DEFAULT = 524288,
+ TCP_RXSZ_DEFAULT = 8192
+};
+
+
+/** Defines a listening TCP socket */
+struct tcp_sock {
+ int fd; /**< Listening file descriptor */
+ int fdc; /**< Cached connection file descriptor */
+ tcp_conn_h *connh; /**< TCP Connect handler */
+ void *arg; /**< Handler argument */
+};
+
+
+/** Defines a TCP connection */
+struct tcp_conn {
+ struct list helpers; /**< List of TCP-helpers */
+ struct list sendq; /**< Sending queue */
+ int fdc; /**< Connection file descriptor */
+ tcp_estab_h *estabh; /**< Connection established handler */
+ tcp_send_h *sendh; /**< Data send handler */
+ tcp_recv_h *recvh; /**< Data receive handler */
+ tcp_close_h *closeh; /**< Connection close handler */
+ void *arg; /**< Handler argument */
+ size_t rxsz; /**< Maximum receive chunk size */
+ size_t txqsz;
+ size_t txqsz_max;
+ bool active; /**< We are connecting flag */
+ bool connected; /**< Connection is connected flag */
+};
+
+
+/** Defines a TCP-Connection Helper */
+struct tcp_helper {
+ struct le le;
+ int layer;
+ tcp_helper_estab_h *estabh;
+ tcp_helper_send_h *sendh;
+ tcp_helper_recv_h *recvh;
+ void *arg;
+};
+
+
+struct tcp_qent {
+ struct le le;
+ struct mbuf mb;
+};
+
+
+static void tcp_recv_handler(int flags, void *arg);
+
+
+static bool helper_estab_handler(int *err, bool active, void *arg)
+{
+ (void)err;
+ (void)active;
+ (void)arg;
+ return false;
+}
+
+
+static bool helper_send_handler(int *err, struct mbuf *mb, void *arg)
+{
+ (void)err;
+ (void)mb;
+ (void)arg;
+ return false;
+}
+
+
+static bool helper_recv_handler(int *err, struct mbuf *mb, bool *estab,
+ void *arg)
+{
+ (void)err;
+ (void)mb;
+ (void)estab;
+ (void)arg;
+ return false;
+}
+
+
+static void sock_destructor(void *data)
+{
+ struct tcp_sock *ts = data;
+
+ if (ts->fd >= 0) {
+ fd_close(ts->fd);
+ (void)close(ts->fd);
+ }
+ if (ts->fdc >= 0)
+ (void)close(ts->fdc);
+}
+
+
+static void conn_destructor(void *data)
+{
+ struct tcp_conn *tc = data;
+
+ list_flush(&tc->helpers);
+ list_flush(&tc->sendq);
+
+ if (tc->fdc >= 0) {
+ fd_close(tc->fdc);
+ (void)close(tc->fdc);
+ }
+}
+
+
+static void helper_destructor(void *data)
+{
+ struct tcp_helper *th = data;
+
+ list_unlink(&th->le);
+}
+
+
+static void qent_destructor(void *arg)
+{
+ struct tcp_qent *qe = arg;
+
+ list_unlink(&qe->le);
+ mem_deref(qe->mb.buf);
+}
+
+
+static int enqueue(struct tcp_conn *tc, struct mbuf *mb)
+{
+ const size_t n = mbuf_get_left(mb);
+ struct tcp_qent *qe;
+ int err;
+
+ if (tc->txqsz + n > tc->txqsz_max)
+ return ENOSPC;
+
+ if (!tc->sendq.head && !tc->sendh) {
+
+ err = fd_listen(tc->fdc, FD_READ | FD_WRITE,
+ tcp_recv_handler, tc);
+ if (err)
+ return err;
+ }
+
+ qe = mem_zalloc(sizeof(*qe), qent_destructor);
+ if (!qe)
+ return ENOMEM;
+
+ list_append(&tc->sendq, &qe->le, qe);
+
+ mbuf_init(&qe->mb);
+
+ err = mbuf_write_mem(&qe->mb, mbuf_buf(mb), n);
+ qe->mb.pos = 0;
+
+ if (err)
+ mem_deref(qe);
+ else
+ tc->txqsz += qe->mb.end;
+
+ return err;
+}
+
+
+static int dequeue(struct tcp_conn *tc)
+{
+ struct tcp_qent *qe = list_ledata(tc->sendq.head);
+ ssize_t n;
+#ifdef MSG_NOSIGNAL
+ const int flags = MSG_NOSIGNAL; /* disable SIGPIPE signal */
+#else
+ const int flags = 0;
+#endif
+ if (!qe) {
+ if (tc->sendh)
+ tc->sendh(tc->arg);
+
+ return 0;
+ }
+
+ n = send(tc->fdc, BUF_CAST mbuf_buf(&qe->mb),
+ qe->mb.end - qe->mb.pos, flags);
+ if (n < 0) {
+ if (EAGAIN == errno)
+ return 0;
+#ifdef WIN32
+ if (WSAEWOULDBLOCK == WSAGetLastError())
+ return 0;
+#endif
+ return errno;
+ }
+
+ tc->txqsz -= n;
+ qe->mb.pos += n;
+
+ if (qe->mb.pos >= qe->mb.end)
+ mem_deref(qe);
+
+ return 0;
+}
+
+
+static void conn_close(struct tcp_conn *tc, int err)
+{
+ list_flush(&tc->sendq);
+ tc->txqsz = 0;
+
+ /* Stop polling */
+ if (tc->fdc >= 0) {
+ fd_close(tc->fdc);
+ (void)close(tc->fdc);
+ tc->fdc = -1;
+ }
+
+ if (tc->closeh)
+ tc->closeh(err, tc->arg);
+}
+
+
+static void tcp_recv_handler(int flags, void *arg)
+{
+ struct tcp_conn *tc = arg;
+ struct mbuf *mb = NULL;
+ bool hlp_estab = false;
+ struct le *le;
+ ssize_t n;
+ int err;
+ socklen_t err_len = sizeof(err);
+
+ if (flags & FD_EXCEPT) {
+ DEBUG_INFO("recv handler: got FD_EXCEPT on fd=%d\n", tc->fdc);
+ }
+
+ /* check for any errors */
+ if (-1 == getsockopt(tc->fdc, SOL_SOCKET, SO_ERROR,
+ BUF_CAST &err, &err_len)) {
+ DEBUG_WARNING("recv handler: getsockopt: (%m)\n", errno);
+ return;
+ }
+
+ if (err) {
+ conn_close(tc, err);
+ return;
+ }
+#if 0
+ if (EINPROGRESS != err && EALREADY != err) {
+ DEBUG_WARNING("recv handler: Socket error (%m)\n", err);
+ return;
+ }
+#endif
+
+ if (flags & FD_WRITE) {
+
+ if (tc->connected) {
+
+ uint32_t nrefs;
+
+ mem_ref(tc);
+
+ err = dequeue(tc);
+
+ nrefs = mem_nrefs(tc);
+ mem_deref(tc);
+
+ /* check if connection was deref'd from send handler */
+ if (nrefs == 1)
+ return;
+
+ if (err) {
+ conn_close(tc, err);
+ return;
+ }
+
+ if (!tc->sendq.head && !tc->sendh) {
+
+ err = fd_listen(tc->fdc, FD_READ,
+ tcp_recv_handler, tc);
+ if (err) {
+ conn_close(tc, err);
+ return;
+ }
+ }
+
+ if (flags & FD_READ)
+ goto read;
+
+ return;
+ }
+
+ tc->connected = true;
+
+ err = fd_listen(tc->fdc, FD_READ, tcp_recv_handler, tc);
+ if (err) {
+ DEBUG_WARNING("recv handler: fd_listen(): %m\n", err);
+ conn_close(tc, err);
+ return;
+ }
+
+ le = tc->helpers.head;
+ while (le) {
+ struct tcp_helper *th = le->data;
+
+ le = le->next;
+
+ if (th->estabh(&err, tc->active, th->arg) || err) {
+ if (err)
+ conn_close(tc, err);
+ return;
+ }
+ }
+
+ if (tc->estabh)
+ tc->estabh(tc->arg);
+
+ return;
+ }
+
+ read:
+ mb = mbuf_alloc(tc->rxsz);
+ if (!mb)
+ return;
+
+ n = recv(tc->fdc, BUF_CAST mb->buf, mb->size, 0);
+ if (0 == n) {
+ mem_deref(mb);
+ conn_close(tc, 0);
+ return;
+ }
+ else if (n < 0) {
+ DEBUG_WARNING("recv handler: recv(): %m\n", errno);
+ goto out;
+ }
+
+ mb->end = n;
+
+ le = tc->helpers.head;
+ while (le) {
+ struct tcp_helper *th = le->data;
+ bool hdld = false;
+
+ le = le->next;
+
+ if (hlp_estab) {
+
+ hdld |= th->estabh(&err, tc->active, th->arg);
+ if (err) {
+ conn_close(tc, err);
+ goto out;
+ }
+ }
+
+ if (mb->pos < mb->end) {
+
+ hdld |= th->recvh(&err, mb, &hlp_estab, th->arg);
+ if (err) {
+ conn_close(tc, err);
+ goto out;
+ }
+ }
+
+ if (hdld)
+ goto out;
+ }
+
+ mbuf_trim(mb);
+
+ if (hlp_estab && tc->estabh) {
+
+ uint32_t nrefs;
+
+ mem_ref(tc);
+
+ tc->estabh(tc->arg);
+
+ nrefs = mem_nrefs(tc);
+ mem_deref(tc);
+
+ /* check if connection was deref'ed from establish handler */
+ if (nrefs == 1)
+ goto out;
+ }
+
+ if (mb->pos < mb->end && tc->recvh) {
+ tc->recvh(mb, tc->arg);
+ }
+
+ out:
+ mem_deref(mb);
+}
+
+
+static struct tcp_conn *conn_alloc(tcp_estab_h *eh, tcp_recv_h *rh,
+ tcp_close_h *ch, void *arg)
+{
+ struct tcp_conn *tc;
+
+ tc = mem_zalloc(sizeof(*tc), conn_destructor);
+ if (!tc)
+ return NULL;
+
+ list_init(&tc->helpers);
+
+ tc->fdc = -1;
+ tc->rxsz = TCP_RXSZ_DEFAULT;
+ tc->txqsz_max = TCP_TXQSZ_DEFAULT;
+ tc->estabh = eh;
+ tc->recvh = rh;
+ tc->closeh = ch;
+ tc->arg = arg;
+
+ return tc;
+}
+
+
+static void tcp_sockopt_set(int fd)
+{
+#ifdef SO_LINGER
+ const struct linger dl = {0, 0};
+ int err;
+
+ err = setsockopt(fd, SOL_SOCKET, SO_LINGER, BUF_CAST &dl, sizeof(dl));
+ if (err) {
+ DEBUG_WARNING("sockopt: SO_LINGER (%m)\n", err);
+ }
+#else
+ (void)fd;
+#endif
+}
+
+
+/**
+ * Handler for incoming TCP connections.
+ *
+ * @param flags Event flags.
+ * @param arg Handler argument.
+ */
+static void tcp_conn_handler(int flags, void *arg)
+{
+ struct sa peer;
+ struct tcp_sock *ts = arg;
+ int err;
+
+ (void)flags;
+
+ sa_init(&peer, AF_UNSPEC);
+
+ if (ts->fdc >= 0)
+ (void)close(ts->fdc);
+
+ ts->fdc = SOK_CAST accept(ts->fd, &peer.u.sa, &peer.len);
+ if (-1 == ts->fdc) {
+
+#if TARGET_OS_IPHONE
+ if (EAGAIN == errno) {
+
+ struct tcp_sock *ts_new;
+ struct sa laddr;
+
+ err = tcp_sock_local_get(ts, &laddr);
+ if (err)
+ return;
+
+ if (ts->fd >= 0) {
+ fd_close(ts->fd);
+ (void)close(ts->fd);
+ ts->fd = -1;
+ }
+
+ err = tcp_listen(&ts_new, &laddr, NULL, NULL);
+ if (err)
+ return;
+
+ ts->fd = ts_new->fd;
+ ts_new->fd = -1;
+
+ mem_deref(ts_new);
+
+ fd_listen(ts->fd, FD_READ, tcp_conn_handler, ts);
+ }
+#endif
+
+ return;
+ }
+
+ err = net_sockopt_blocking_set(ts->fdc, false);
+ if (err) {
+ DEBUG_WARNING("conn handler: nonblock set: %m\n", err);
+ (void)close(ts->fdc);
+ ts->fdc = -1;
+ return;
+ }
+
+ tcp_sockopt_set(ts->fdc);
+
+ if (ts->connh)
+ ts->connh(&peer, ts->arg);
+}
+
+
+/**
+ * Create a TCP Socket
+ *
+ * @param tsp Pointer to returned TCP Socket
+ * @param local Local listen address (NULL for any)
+ * @param ch Incoming connection handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_sock_alloc(struct tcp_sock **tsp, const struct sa *local,
+ tcp_conn_h *ch, void *arg)
+{
+ struct addrinfo hints, *res = NULL, *r;
+ char addr[64] = "";
+ char serv[6] = "0";
+ struct tcp_sock *ts = NULL;
+ int error, err;
+
+ if (!tsp)
+ return EINVAL;
+
+ ts = mem_zalloc(sizeof(*ts), sock_destructor);
+ if (!ts)
+ return ENOMEM;
+
+ ts->fd = -1;
+ ts->fdc = -1;
+
+ if (local) {
+ (void)re_snprintf(addr, sizeof(addr), "%H",
+ sa_print_addr, local);
+ (void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local));
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ /* set-up hints structure */
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ error = getaddrinfo(addr[0] ? addr : NULL, serv, &hints, &res);
+ if (error) {
+#ifdef WIN32
+ DEBUG_WARNING("listen: getaddrinfo: wsaerr=%d\n",
+ WSAGetLastError());
+#endif
+ DEBUG_WARNING("listen: getaddrinfo: %s:%s error=%d (%s)\n",
+ addr, serv, error, gai_strerror(error));
+ err = EADDRNOTAVAIL;
+ goto out;
+ }
+
+ err = EINVAL;
+ for (r = res; r; r = r->ai_next) {
+ int fd = -1;
+
+ if (ts->fd >= 0)
+ continue;
+
+ fd = SOK_CAST socket(r->ai_family, SOCK_STREAM, IPPROTO_TCP);
+ if (fd < 0) {
+ err = errno;
+ continue;
+ }
+
+ (void)net_sockopt_reuse_set(fd, true);
+
+ err = net_sockopt_blocking_set(fd, false);
+ if (err) {
+ DEBUG_WARNING("listen: nonblock set: %m\n", err);
+ (void)close(fd);
+ continue;
+ }
+
+ tcp_sockopt_set(fd);
+
+ /* OK */
+ ts->fd = fd;
+ err = 0;
+ break;
+ }
+
+ freeaddrinfo(res);
+
+ if (-1 == ts->fd)
+ goto out;
+
+ ts->connh = ch;
+ ts->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(ts);
+ else
+ *tsp = ts;
+
+ return err;
+}
+
+
+/**
+ * Bind to a TCP Socket
+ *
+ * @param ts TCP Socket
+ * @param local Local bind address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_sock_bind(struct tcp_sock *ts, const struct sa *local)
+{
+ struct addrinfo hints, *res = NULL, *r;
+ char addr[64] = "";
+ char serv[NI_MAXSERV] = "0";
+ int error, err;
+
+ if (!ts || ts->fd<0)
+ return EINVAL;
+
+ if (local) {
+ (void)re_snprintf(addr, sizeof(addr), "%H",
+ sa_print_addr, local);
+ (void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local));
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ /* set-up hints structure */
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ error = getaddrinfo(addr[0] ? addr : NULL, serv, &hints, &res);
+ if (error) {
+#ifdef WIN32
+ DEBUG_WARNING("sock_bind: getaddrinfo: wsaerr=%d\n",
+ WSAGetLastError());
+#endif
+ DEBUG_WARNING("sock_bind: getaddrinfo: %s:%s error=%d (%s)\n",
+ addr, serv, error, gai_strerror(error));
+ return EADDRNOTAVAIL;
+ }
+
+ err = EINVAL;
+ for (r = res; r; r = r->ai_next) {
+
+ if (bind(ts->fd, r->ai_addr, SIZ_CAST r->ai_addrlen) < 0) {
+ err = errno;
+ DEBUG_WARNING("sock_bind: bind: %m (af=%d, %J)\n",
+ err, r->ai_family, local);
+ continue;
+ }
+
+ /* OK */
+ err = 0;
+ break;
+ }
+
+ freeaddrinfo(res);
+
+ return err;
+}
+
+
+/**
+ * Listen on a TCP Socket
+ *
+ * @param ts TCP Socket
+ * @param backlog Maximum length the queue of pending connections
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_sock_listen(struct tcp_sock *ts, int backlog)
+{
+ int err;
+
+ if (!ts)
+ return EINVAL;
+
+ if (ts->fd < 0) {
+ DEBUG_WARNING("sock_listen: invalid fd\n");
+ return EBADF;
+ }
+
+ if (listen(ts->fd, backlog) < 0) {
+ err = errno;
+ DEBUG_WARNING("sock_listen: listen(): %m\n", err);
+ return err;
+ }
+
+ return fd_listen(ts->fd, FD_READ, tcp_conn_handler, ts);
+}
+
+
+/**
+ * Accept an incoming TCP Connection
+ *
+ * @param tcp Returned TCP Connection object
+ * @param ts Corresponding TCP Socket
+ * @param eh TCP Connection Established handler
+ * @param rh TCP Connection Receive data handler
+ * @param ch TCP Connection close handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_accept(struct tcp_conn **tcp, struct tcp_sock *ts, tcp_estab_h *eh,
+ tcp_recv_h *rh, tcp_close_h *ch, void *arg)
+{
+ struct tcp_conn *tc;
+ int err;
+
+ if (!tcp || !ts || ts->fdc < 0)
+ return EINVAL;
+
+ tc = conn_alloc(eh, rh, ch, arg);
+ if (!tc)
+ return ENOMEM;
+
+ /* Transfer ownership to TCP connection */
+ tc->fdc = ts->fdc;
+ ts->fdc = -1;
+
+ err = fd_listen(tc->fdc, FD_READ | FD_WRITE | FD_EXCEPT,
+ tcp_recv_handler, tc);
+ if (err) {
+ DEBUG_WARNING("accept: fd_listen(): %m\n", err);
+ }
+
+ if (err)
+ mem_deref(tc);
+ else
+ *tcp = tc;
+
+ return err;
+}
+
+
+/**
+ * Reject an incoming TCP Connection
+ *
+ * @param ts Corresponding TCP Socket
+ */
+void tcp_reject(struct tcp_sock *ts)
+{
+ if (!ts)
+ return;
+
+ if (ts->fdc >= 0) {
+ (void)close(ts->fdc);
+ ts->fdc = -1;
+ }
+}
+
+
+/**
+ * Allocate a TCP Connection
+ *
+ * @param tcp Returned TCP Connection object
+ * @param peer Network address of peer
+ * @param eh TCP Connection Established handler
+ * @param rh TCP Connection Receive data handler
+ * @param ch TCP Connection close handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_conn_alloc(struct tcp_conn **tcp,
+ const struct sa *peer, tcp_estab_h *eh,
+ tcp_recv_h *rh, tcp_close_h *ch, void *arg)
+{
+ struct tcp_conn *tc;
+ struct addrinfo hints, *res = NULL, *r;
+ char addr[64];
+ char serv[NI_MAXSERV] = "0";
+ int error, err;
+
+ if (!tcp || !sa_isset(peer, SA_ALL))
+ return EINVAL;
+
+ tc = conn_alloc(eh, rh, ch, arg);
+ if (!tc)
+ return ENOMEM;
+
+ memset(&hints, 0, sizeof(hints));
+ /* set-up hints structure */
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ (void)re_snprintf(addr, sizeof(addr), "%H",
+ sa_print_addr, peer);
+ (void)re_snprintf(serv, sizeof(serv), "%u", sa_port(peer));
+
+ error = getaddrinfo(addr, serv, &hints, &res);
+ if (error) {
+ DEBUG_WARNING("connect: getaddrinfo(): (%s)\n",
+ gai_strerror(error));
+ err = EADDRNOTAVAIL;
+ goto out;
+ }
+
+ err = EINVAL;
+ for (r = res; r; r = r->ai_next) {
+
+ tc->fdc = SOK_CAST socket(r->ai_family, SOCK_STREAM,
+ IPPROTO_TCP);
+ if (tc->fdc < 0) {
+ err = errno;
+ continue;
+ }
+
+ err = net_sockopt_blocking_set(tc->fdc, false);
+ if (err) {
+ DEBUG_WARNING("connect: nonblock set: %m\n", err);
+ (void)close(tc->fdc);
+ tc->fdc = -1;
+ continue;
+ }
+
+ tcp_sockopt_set(tc->fdc);
+
+ err = 0;
+ break;
+ }
+
+ freeaddrinfo(res);
+
+ out:
+ if (err)
+ mem_deref(tc);
+ else
+ *tcp = tc;
+
+ return err;
+}
+
+
+/**
+ * Bind a TCP Connection to a local address
+ *
+ * @param tc TCP Connection object
+ * @param local Local bind address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_conn_bind(struct tcp_conn *tc, const struct sa *local)
+{
+ struct addrinfo hints, *res = NULL, *r;
+ char addr[64] = "";
+ char serv[NI_MAXSERV] = "0";
+ int error, err;
+
+ if (!tc)
+ return EINVAL;
+
+ if (local) {
+ (void)re_snprintf(addr, sizeof(addr), "%H",
+ sa_print_addr, local);
+ (void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local));
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ /* set-up hints structure */
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ error = getaddrinfo(addr[0] ? addr : NULL, serv, &hints, &res);
+ if (error) {
+ DEBUG_WARNING("conn_bind: getaddrinfo(): (%s)\n",
+ gai_strerror(error));
+ return EADDRNOTAVAIL;
+ }
+
+ err = EINVAL;
+ for (r = res; r; r = r->ai_next) {
+
+ (void)net_sockopt_reuse_set(tc->fdc, true);
+
+ /* bind to local address */
+ if (bind(tc->fdc, r->ai_addr, SIZ_CAST r->ai_addrlen) < 0) {
+
+ /* Special case for mingw32/wine */
+ if (0 == errno) {
+ goto ok;
+ }
+
+ err = errno;
+ DEBUG_WARNING("conn_bind: bind(): %J: %m\n",
+ local, err);
+ continue;
+ }
+
+ ok:
+ /* OK */
+ err = 0;
+ break;
+ }
+
+ freeaddrinfo(res);
+
+ if (err) {
+ DEBUG_WARNING("conn_bind failed: %J (%m)\n", local, err);
+ }
+
+ return err;
+}
+
+
+/**
+ * Connect to a remote peer
+ *
+ * @param tc TCP Connection object
+ * @param peer Network address of peer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_conn_connect(struct tcp_conn *tc, const struct sa *peer)
+{
+ struct addrinfo hints, *res = NULL, *r;
+ char addr[64];
+ char serv[NI_MAXSERV];
+ int error, err = 0;
+
+ if (!tc || !sa_isset(peer, SA_ALL))
+ return EINVAL;
+
+ tc->active = true;
+
+ if (tc->fdc < 0) {
+ DEBUG_WARNING("invalid fd\n");
+ return EBADF;
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ /* set-up hints structure */
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ (void)re_snprintf(addr, sizeof(addr), "%H",
+ sa_print_addr, peer);
+ (void)re_snprintf(serv, sizeof(serv), "%u", sa_port(peer));
+
+ error = getaddrinfo(addr, serv, &hints, &res);
+ if (error) {
+ DEBUG_WARNING("connect: getaddrinfo(): (%s)\n",
+ gai_strerror(error));
+ return EADDRNOTAVAIL;
+ }
+
+ for (r = res; r; r = r->ai_next) {
+ struct sockaddr *sa = r->ai_addr;
+
+ again:
+ if (0 == connect(tc->fdc, sa, SIZ_CAST r->ai_addrlen)) {
+ err = 0;
+ goto out;
+ }
+ else {
+#ifdef WIN32
+ /* Special error handling for Windows */
+ if (WSAEWOULDBLOCK == WSAGetLastError()) {
+ err = 0;
+ goto out;
+ }
+#endif
+
+ /* Special case for mingw32/wine */
+ if (0 == errno) {
+ err = 0;
+ goto out;
+ }
+
+ if (EINTR == errno)
+ goto again;
+
+ if (EINPROGRESS != errno && EALREADY != errno) {
+ err = errno;
+ DEBUG_INFO("connect: connect() %J: %m\n",
+ peer, err);
+ }
+ }
+ }
+
+ out:
+ freeaddrinfo(res);
+
+ if (err)
+ return err;
+
+ return fd_listen(tc->fdc, FD_READ | FD_WRITE | FD_EXCEPT,
+ tcp_recv_handler, tc);
+}
+
+
+static int tcp_send_internal(struct tcp_conn *tc, struct mbuf *mb,
+ struct le *le)
+{
+ int err = 0;
+ ssize_t n;
+#ifdef MSG_NOSIGNAL
+ const int flags = MSG_NOSIGNAL; /* disable SIGPIPE signal */
+#else
+ const int flags = 0;
+#endif
+
+ if (tc->fdc < 0)
+ return ENOTCONN;
+
+ if (!mbuf_get_left(mb)) {
+ DEBUG_WARNING("send: empty mbuf (pos=%u end=%u)\n",
+ mb->pos, mb->end);
+ return EINVAL;
+ }
+
+ /* call helpers in reverse order */
+ while (le) {
+ struct tcp_helper *th = le->data;
+
+ le = le->prev;
+
+ if (th->sendh(&err, mb, th->arg) || err)
+ return err;
+ }
+
+ if (tc->sendq.head)
+ return enqueue(tc, mb);
+
+ n = send(tc->fdc, BUF_CAST mbuf_buf(mb), mb->end - mb->pos, flags);
+ if (n < 0) {
+
+ if (EAGAIN == errno)
+ return enqueue(tc, mb);
+
+#ifdef WIN32
+ if (WSAEWOULDBLOCK == WSAGetLastError())
+ return enqueue(tc, mb);
+#endif
+ err = errno;
+
+ DEBUG_WARNING("send: write(): %m (fdc=%d)\n", err, tc->fdc);
+
+#ifdef WIN32
+ DEBUG_WARNING("WIN32 error: %d\n", WSAGetLastError());
+#endif
+
+ return err;
+ }
+
+ if ((size_t)n < mb->end - mb->pos) {
+
+ mb->pos += n;
+ err = enqueue(tc, mb);
+ mb->pos -= n;
+
+ return err;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Send data on a TCP Connection to a remote peer
+ *
+ * @param tc TCP Connection
+ * @param mb Buffer to send
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_send(struct tcp_conn *tc, struct mbuf *mb)
+{
+ if (!tc || !mb)
+ return EINVAL;
+
+ return tcp_send_internal(tc, mb, tc->helpers.tail);
+}
+
+
+/**
+ * Send data on a TCP Connection to a remote peer bypassing this
+ * helper and the helpers above it.
+ *
+ * @param tc TCP Connection
+ * @param mb Buffer to send
+ * @param th TCP Helper
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_send_helper(struct tcp_conn *tc, struct mbuf *mb,
+ struct tcp_helper *th)
+{
+ if (!tc || !mb || !th)
+ return EINVAL;
+
+ return tcp_send_internal(tc, mb, th->le.prev);
+}
+
+
+/**
+ * Set the send handler on a TCP Connection, which will be called
+ * every time it is ready to send data
+ *
+ * @param tc TCP Connection
+ * @param sendh TCP Send handler
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_set_send(struct tcp_conn *tc, tcp_send_h *sendh)
+{
+ if (!tc)
+ return EINVAL;
+
+ tc->sendh = sendh;
+
+ if (tc->sendq.head || !sendh)
+ return 0;
+
+ return fd_listen(tc->fdc, FD_READ | FD_WRITE, tcp_recv_handler, tc);
+}
+
+
+/**
+ * Set handlers on a TCP Connection
+ *
+ * @param tc TCP Connection
+ * @param eh TCP Connection Established handler
+ * @param rh TCP Connection Receive data handler
+ * @param ch TCP Connection Close handler
+ * @param arg Handler argument
+ */
+void tcp_set_handlers(struct tcp_conn *tc, tcp_estab_h *eh, tcp_recv_h *rh,
+ tcp_close_h *ch, void *arg)
+{
+ if (!tc)
+ return;
+
+ tc->estabh = eh;
+ tc->recvh = rh;
+ tc->closeh = ch;
+ tc->arg = arg;
+}
+
+
+/**
+ * Get local network address of TCP Socket
+ *
+ * @param ts TCP Socket
+ * @param local Returned local network address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_sock_local_get(const struct tcp_sock *ts, struct sa *local)
+{
+ if (!ts || !local)
+ return EINVAL;
+
+ sa_init(local, AF_UNSPEC);
+
+ if (getsockname(ts->fd, &local->u.sa, &local->len) < 0) {
+ DEBUG_WARNING("local get: getsockname(): %m\n", errno);
+ return errno;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Get local network address of TCP Connection
+ *
+ * @param tc TCP Connection
+ * @param local Returned local network address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_conn_local_get(const struct tcp_conn *tc, struct sa *local)
+{
+ if (!tc || !local)
+ return EINVAL;
+
+ sa_init(local, AF_UNSPEC);
+
+ if (getsockname(tc->fdc, &local->u.sa, &local->len) < 0) {
+ DEBUG_WARNING("conn local get: getsockname(): %m\n", errno);
+ return errno;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Get remote peer network address of TCP Connection
+ *
+ * @param tc TCP Connection
+ * @param peer Returned remote peer network address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_conn_peer_get(const struct tcp_conn *tc, struct sa *peer)
+{
+ if (!tc || !peer)
+ return EINVAL;
+
+ sa_init(peer, AF_UNSPEC);
+
+ if (getpeername(tc->fdc, &peer->u.sa, &peer->len) < 0) {
+ DEBUG_WARNING("conn peer get: getpeername(): %m\n", errno);
+ return errno;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Set the maximum receive chunk size on a TCP Connection
+ *
+ * @param tc TCP Connection
+ * @param rxsz Maximum receive chunk size
+ */
+void tcp_conn_rxsz_set(struct tcp_conn *tc, size_t rxsz)
+{
+ if (!tc)
+ return;
+
+ tc->rxsz = rxsz;
+}
+
+
+/**
+ * Set the maximum send queue size on a TCP Connection
+ *
+ * @param tc TCP Connection
+ * @param txqsz Maximum send queue size
+ */
+void tcp_conn_txqsz_set(struct tcp_conn *tc, size_t txqsz)
+{
+ if (!tc)
+ return;
+
+ tc->txqsz_max = txqsz;
+}
+
+
+/**
+ * Get the file descriptor of a TCP Connection
+ *
+ * @param tc TCP-Connection
+ *
+ * @return File destriptor, or -1 if errors
+ */
+int tcp_conn_fd(const struct tcp_conn *tc)
+{
+ return tc ? tc->fdc : -1;
+}
+
+
+/**
+ * Get the current length of the transmit queue on a TCP Connection
+ *
+ * @param tc TCP-Connection
+ *
+ * @return Current transmit queue length, or 0 if errors
+ */
+size_t tcp_conn_txqsz(const struct tcp_conn *tc)
+{
+ return tc ? tc->txqsz : 0;
+}
+
+
+static bool sort_handler(struct le *le1, struct le *le2, void *arg)
+{
+ struct tcp_helper *th1 = le1->data, *th2 = le2->data;
+ (void)arg;
+
+ return th1->layer <= th2->layer;
+}
+
+
+/**
+ * Register a new TCP-helper on a TCP-Connection
+ *
+ * @param thp Pointer to allocated TCP helper
+ * @param tc TCP Connection
+ * @param layer Protocol layer; higher number means higher up in stack
+ * @param eh Established handler
+ * @param sh Send handler
+ * @param rh Receive handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_register_helper(struct tcp_helper **thp, struct tcp_conn *tc,
+ int layer,
+ tcp_helper_estab_h *eh, tcp_helper_send_h *sh,
+ tcp_helper_recv_h *rh, void *arg)
+{
+ struct tcp_helper *th;
+
+ if (!tc)
+ return EINVAL;
+
+ th = mem_zalloc(sizeof(*th), helper_destructor);
+ if (!th)
+ return ENOMEM;
+
+ list_append(&tc->helpers, &th->le, th);
+
+ th->layer = layer;
+ th->estabh = eh ? eh : helper_estab_handler;
+ th->sendh = sh ? sh : helper_send_handler;
+ th->recvh = rh ? rh : helper_recv_handler;
+ th->arg = arg;
+
+ list_sort(&tc->helpers, sort_handler, NULL);
+
+ if (thp)
+ *thp = th;
+
+ return 0;
+}
diff --git a/src/tcp/tcp_high.c b/src/tcp/tcp_high.c
new file mode 100644
index 0000000..7dd9d48
--- /dev/null
+++ b/src/tcp/tcp_high.c
@@ -0,0 +1,103 @@
+/**
+ * @file tcp_high.c High-level TCP functions
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_tcp.h>
+
+
+/**
+ * Create and listen on a TCP Socket
+ *
+ * @param tsp Pointer to returned TCP Socket
+ * @param local Local listen address (NULL for any)
+ * @param ch Incoming connection handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_listen(struct tcp_sock **tsp, const struct sa *local,
+ tcp_conn_h *ch, void *arg)
+{
+ struct tcp_sock *ts = NULL;
+ int err;
+
+ if (!tsp)
+ return EINVAL;
+
+ err = tcp_sock_alloc(&ts, local, ch, arg);
+ if (err)
+ goto out;
+
+ err = tcp_sock_bind(ts, local);
+ if (err)
+ goto out;
+
+ err = tcp_sock_listen(ts, 5);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ ts = mem_deref(ts);
+ else
+ *tsp = ts;
+
+ return err;
+}
+
+
+/**
+ * Make a TCP Connection to a remote peer
+ *
+ * @param tcp Returned TCP Connection object
+ * @param peer Network address of peer
+ * @param eh TCP Connection Established handler
+ * @param rh TCP Connection Receive data handler
+ * @param ch TCP Connection close handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_connect(struct tcp_conn **tcp, const struct sa *peer,
+ tcp_estab_h *eh, tcp_recv_h *rh, tcp_close_h *ch, void *arg)
+{
+ struct tcp_conn *tc = NULL;
+ int err;
+
+ if (!tcp || !peer)
+ return EINVAL;
+
+ err = tcp_conn_alloc(&tc, peer, eh,rh, ch, arg);
+ if (err)
+ goto out;
+
+ err = tcp_conn_connect(tc, peer);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ tc = mem_deref(tc);
+ else
+ *tcp = tc;
+
+ return err;
+}
+
+
+/**
+ * Get local network address of TCP Socket
+ *
+ * @param ts TCP Socket
+ * @param local Returned local network address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tcp_local_get(const struct tcp_sock *ts, struct sa *local)
+{
+ return tcp_sock_local_get(ts, local);
+}
diff --git a/src/telev/mod.mk b/src/telev/mod.mk
new file mode 100644
index 0000000..d3e43a5
--- /dev/null
+++ b/src/telev/mod.mk
@@ -0,0 +1,7 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += telev/telev.c
diff --git a/src/telev/telev.c b/src/telev/telev.c
new file mode 100644
index 0000000..4e4d71f
--- /dev/null
+++ b/src/telev/telev.c
@@ -0,0 +1,383 @@
+/**
+ * @file telev.c Telephony Events implementation (RFC 4733)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <ctype.h>
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_net.h>
+#include <re_telev.h>
+
+
+/*
+ * Implements a subset of RFC 4733,
+ * "RTP Payload for DTMF Digits, Telephony Tones, and Telephony Signals"
+ *
+ * Call telev_send() to send telephony events, and telev_recv() to receive.
+ * The function telev_poll() must be called periodically, with a minimum
+ * and stable interval of less than or equal to packet time (e.g. 50ms)
+ *
+ * NOTE: RTP timestamp and sequence number is kept in application
+ *
+ *
+ * Example sending:
+ *
+ * ms M ts seq ev dur End
+ *
+ * press X -->
+ * 50 1 0 1 X 400 0
+ * 100 0 0 2 X 800 0
+ * 150 0 0 3 X 1200 0
+ * 200 0 0 4 X 1600 0
+ * ... . . . . .... .
+ * release X -->
+ * 250 0 0 5 X 1600 1
+ * 300 0 0 6 X 1600 1
+ * press Y -->
+ * 350 1 2000 7 Y 400 0
+ * ... . .... . . ... .
+ */
+
+
+enum {
+ TXC_DIGIT_MIN = 9,
+ TXC_END = 3,
+ EVENT_END = 0xff,
+ PAYLOAD_SIZE = 4,
+ VOLUME = 10,
+ QUEUE_SIZE = 8192,
+};
+
+
+enum state {
+ IDLE,
+ SENDING,
+ ENDING,
+};
+
+
+struct telev_fmt {
+ uint8_t event;
+ bool end;
+ uint8_t vol;
+ uint16_t dur;
+};
+
+
+/** Defines a Telephony Events state */
+struct telev {
+ /* tx */
+ struct mbuf *mb;
+ uint32_t ptime;
+ uint16_t pdur;
+ enum state state;
+ int event;
+ uint16_t dur;
+ uint32_t txc;
+
+ /* rx */
+ int rx_event;
+ bool rx_end;
+};
+
+
+const char telev_rtpfmt[] = "telephone-event";
+
+
+static int payload_encode(struct mbuf *mb, int event, bool end, uint16_t dur)
+{
+ size_t pos;
+ int err;
+
+ if (!mb)
+ return EINVAL;
+
+ pos = mb->pos;
+
+ err = mbuf_write_u8(mb, event);
+ err |= mbuf_write_u8(mb, (end ? 0x80 : 0x00) | (VOLUME & 0x3f));
+ err |= mbuf_write_u16(mb, htons(dur));
+
+ if (err)
+ mb->pos = pos;
+
+ return err;
+}
+
+
+static int payload_decode(struct mbuf *mb, struct telev_fmt *fmt)
+{
+ uint8_t b;
+
+ if (!mb || !fmt)
+ return EINVAL;
+
+ if (mbuf_get_left(mb) < PAYLOAD_SIZE)
+ return ENOENT;
+
+ fmt->event = mbuf_read_u8(mb);
+ b = mbuf_read_u8(mb);
+ fmt->end = (b & 0x80) == 0x80;
+ fmt->vol = (b & 0x3f);
+ fmt->dur = ntohs(mbuf_read_u16(mb));
+
+ return 0;
+}
+
+
+static void destructor(void *arg)
+{
+ struct telev *t = arg;
+
+ mem_deref(t->mb);
+}
+
+
+/**
+ * Allocate a new Telephony Events state
+ *
+ * @param tp Pointer to allocated object
+ * @param ptime Packet time in [ms]
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int telev_alloc(struct telev **tp, uint32_t ptime)
+{
+ struct telev *t;
+ int err = 0;
+
+ if (!tp || !ptime)
+ return EINVAL;
+
+ t = mem_zalloc(sizeof(*t), destructor);
+ if (!t)
+ return ENOMEM;
+
+ t->mb = mbuf_alloc(16);
+ if (!t->mb) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ t->state = IDLE;
+ t->ptime = ptime;
+ t->pdur = ptime * 8;
+ t->rx_event = -1;
+
+ out:
+ if (err)
+ mem_deref(t);
+ else
+ *tp = t;
+
+ return err;
+}
+
+
+/**
+ * Sets the sampling rate
+ *
+ * @param tel Telephony Event state
+ * @param srate Sampling rate in [Hz]
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int telev_set_srate(struct telev *tel, uint32_t srate)
+{
+ if (!tel || !srate)
+ return EINVAL;
+
+ tel->pdur = tel->ptime * srate / 1000;
+
+ return 0;
+}
+
+
+/**
+ * Send a Telephony Event
+ *
+ * @param tel Telephony Event state
+ * @param event The Event to send
+ * @param end End-of-event flag
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int telev_send(struct telev *tel, int event, bool end)
+{
+ size_t pos;
+ int err;
+
+ if (!tel)
+ return EINVAL;
+
+ if (tel->mb->end >= QUEUE_SIZE)
+ return EOVERFLOW;
+
+ pos = tel->mb->pos;
+
+ tel->mb->pos = tel->mb->end;
+ err = mbuf_write_u8(tel->mb, end ? EVENT_END : event);
+ tel->mb->pos = pos;
+
+ return err;
+}
+
+
+/**
+ * Receive a Telephony Event
+ *
+ * @param tel Telephony Event state
+ * @param mb Buffer to decode
+ * @param event The received event, set on return
+ * @param end End-of-event flag, set on return
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int telev_recv(struct telev *tel, struct mbuf *mb, int *event, bool *end)
+{
+ struct telev_fmt fmt;
+ int err;
+
+ if (!tel || !mb || !event || !end)
+ return EINVAL;
+
+ err = payload_decode(mb, &fmt);
+ if (err)
+ return err;
+
+ if (fmt.end) {
+ if (tel->rx_end)
+ return EALREADY;
+
+ *event = fmt.event;
+ *end = true;
+ tel->rx_event = -1;
+ tel->rx_end = true;
+ return 0;
+ }
+
+ if (fmt.event == tel->rx_event)
+ return EALREADY;
+
+ *event = tel->rx_event = fmt.event;
+ *end = tel->rx_end = false;
+
+ return 0;
+}
+
+
+/**
+ * Poll a Telephony Event state for sending
+ *
+ * @param tel Telephony Event state
+ * @param marker Marker bit, set on return
+ * @param mb Buffer with encoded data to send
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int telev_poll(struct telev *tel, bool *marker, struct mbuf *mb)
+{
+ bool mrk = false;
+ int err = ENOENT;
+
+ if (!tel || !marker || !mb)
+ return EINVAL;
+
+ switch (tel->state) {
+
+ case IDLE:
+ if (!mbuf_get_left(tel->mb))
+ break;
+
+ mrk = true;
+
+ tel->event = mbuf_read_u8(tel->mb);
+ tel->dur = tel->pdur;
+ tel->state = SENDING;
+ tel->txc = 1;
+
+ err = payload_encode(mb, tel->event, false, tel->dur);
+ break;
+
+ case SENDING:
+ tel->dur += tel->pdur;
+
+ err = payload_encode(mb, tel->event, false, tel->dur);
+
+ if (++tel->txc < TXC_DIGIT_MIN)
+ break;
+
+ if (!mbuf_get_left(tel->mb))
+ break;
+
+ if (mbuf_read_u8(tel->mb) != EVENT_END)
+ tel->mb->pos--;
+
+ tel->state = ENDING;
+ tel->txc = 0;
+ break;
+
+ case ENDING:
+ err = payload_encode(mb, tel->event, true, tel->dur);
+
+ if (++tel->txc < TXC_END)
+ break;
+
+ if (!mbuf_get_left(tel->mb))
+ tel->mb->pos = tel->mb->end = 0;
+
+ tel->state = IDLE;
+ break;
+ }
+
+ if (!err)
+ *marker = mrk;
+
+ return err;
+}
+
+
+/**
+ * Convert DTMF digit to Event code
+ *
+ * @param digit DTMF Digit
+ *
+ * @return Event code, or -1 if error
+ */
+int telev_digit2code(int digit)
+{
+ if (isdigit(digit))
+ return digit - '0';
+ else if (digit == '*')
+ return 10;
+ else if (digit == '#')
+ return 11;
+ else if ('a' <= digit && digit <= 'd')
+ return digit - 'a' + 12;
+ else if ('A' <= digit && digit <= 'D')
+ return digit - 'A' + 12;
+ else
+ return -1;
+}
+
+
+/**
+ * Convert Event code to DTMF digit
+ *
+ * @param code Event code
+ *
+ * @return DTMF Digit, or -1 if error
+ */
+int telev_code2digit(int code)
+{
+ static const char map[] = "0123456789*#ABCD";
+
+ if (code < 0 || code >= 16)
+ return -1;
+
+ return map[code];
+}
diff --git a/src/tls/mod.mk b/src/tls/mod.mk
new file mode 100644
index 0000000..f653395
--- /dev/null
+++ b/src/tls/mod.mk
@@ -0,0 +1,11 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+ifneq ($(USE_OPENSSL),)
+SRCS += tls/openssl/tls.c
+SRCS += tls/openssl/tls_tcp.c
+SRCS += tls/openssl/tls_udp.c
+endif
diff --git a/src/tls/openssl/tls.c b/src/tls/openssl/tls.c
new file mode 100644
index 0000000..d84e65f
--- /dev/null
+++ b/src/tls/openssl/tls.c
@@ -0,0 +1,904 @@
+/**
+ * @file openssl/tls.c TLS backend using OpenSSL
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rsa.h>
+#include <openssl/bn.h>
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_main.h>
+#include <re_sa.h>
+#include <re_net.h>
+#include <re_srtp.h>
+#include <re_sys.h>
+#include <re_tcp.h>
+#include <re_tls.h>
+#include "tls.h"
+
+
+/* also defined by wincrypt.h */
+#ifdef WIN32
+#undef X509_NAME
+#endif
+
+
+#define DEBUG_MODULE "tls"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/* NOTE: shadow struct defined in tls_*.c */
+struct tls_conn {
+ SSL *ssl;
+};
+
+
+static void destructor(void *data)
+{
+ struct tls *tls = data;
+
+ if (tls->ctx)
+ SSL_CTX_free(tls->ctx);
+
+ if (tls->cert)
+ X509_free(tls->cert);
+
+ mem_deref(tls->pass);
+}
+
+
+/*The password code is not thread safe*/
+static int password_cb(char *buf, int size, int rwflag, void *userdata)
+{
+ struct tls *tls = userdata;
+
+ (void)rwflag;
+
+ DEBUG_NOTICE("password callback\n");
+
+ if (size < (int)strlen(tls->pass)+1)
+ return 0;
+
+ strncpy(buf, tls->pass, size);
+
+ return (int)strlen(tls->pass);
+}
+
+
+static int keytype2int(enum tls_keytype type)
+{
+ switch (type) {
+ case TLS_KEYTYPE_EC:
+ return EVP_PKEY_EC;
+ case TLS_KEYTYPE_RSA:
+ return EVP_PKEY_RSA;
+ default:
+ return EVP_PKEY_NONE;
+ }
+}
+
+
+/**
+ * Allocate a new TLS context
+ *
+ * @param tlsp Pointer to allocated TLS context
+ * @param method TLS method
+ * @param keyfile Optional private key file
+ * @param pwd Optional password
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_alloc(struct tls **tlsp, enum tls_method method, const char *keyfile,
+ const char *pwd)
+{
+ struct tls *tls;
+ int r, err;
+
+ if (!tlsp)
+ return EINVAL;
+
+ tls = mem_zalloc(sizeof(*tls), destructor);
+ if (!tls)
+ return ENOMEM;
+
+ switch (method) {
+
+ case TLS_METHOD_SSLV23:
+ tls->ctx = SSL_CTX_new(SSLv23_method());
+ break;
+
+#ifdef USE_OPENSSL_DTLS
+ case TLS_METHOD_DTLSV1:
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+
+ tls->ctx = SSL_CTX_new(DTLS_method());
+#else
+ tls->ctx = SSL_CTX_new(DTLSv1_method());
+#endif
+ break;
+
+#ifdef SSL_OP_NO_DTLSv1_2
+ /* DTLS v1.2 is available in OpenSSL 1.0.2 and later */
+
+ case TLS_METHOD_DTLS:
+ tls->ctx = SSL_CTX_new(DTLS_method());
+ break;
+
+ case TLS_METHOD_DTLSV1_2:
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+
+ tls->ctx = SSL_CTX_new(DTLS_method());
+#else
+ tls->ctx = SSL_CTX_new(DTLSv1_2_method());
+#endif
+ break;
+#endif
+
+#endif
+
+ default:
+ DEBUG_WARNING("tls method %d not supported\n", method);
+ err = ENOSYS;
+ goto out;
+ }
+
+ if (!tls->ctx) {
+ ERR_clear_error();
+ err = ENOMEM;
+ goto out;
+ }
+
+#if (OPENSSL_VERSION_NUMBER < 0x00905100L)
+ SSL_CTX_set_verify_depth(tls->ctx, 1);
+#endif
+
+ /* Load our keys and certificates */
+ if (keyfile) {
+ if (pwd) {
+ err = str_dup(&tls->pass, pwd);
+ if (err)
+ goto out;
+
+ SSL_CTX_set_default_passwd_cb(tls->ctx, password_cb);
+ SSL_CTX_set_default_passwd_cb_userdata(tls->ctx, tls);
+ }
+
+ r = SSL_CTX_use_certificate_chain_file(tls->ctx, keyfile);
+ if (r <= 0) {
+ DEBUG_WARNING("Can't read certificate file: %s (%d)\n",
+ keyfile, r);
+ ERR_clear_error();
+ err = EINVAL;
+ goto out;
+ }
+
+ r = SSL_CTX_use_PrivateKey_file(tls->ctx, keyfile,
+ SSL_FILETYPE_PEM);
+ if (r <= 0) {
+ DEBUG_WARNING("Can't read key file: %s (%d)\n",
+ keyfile, r);
+ ERR_clear_error();
+ err = EINVAL;
+ goto out;
+ }
+ }
+
+ err = 0;
+ out:
+ if (err)
+ mem_deref(tls);
+ else
+ *tlsp = tls;
+
+ return err;
+}
+
+
+/**
+ * Set default locations for trusted CA certificates
+ *
+ * @param tls TLS Context
+ * @param cafile PEM file with CA certificates
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_add_ca(struct tls *tls, const char *cafile)
+{
+ if (!tls || !cafile)
+ return EINVAL;
+
+ /* Load the CAs we trust */
+ if (!(SSL_CTX_load_verify_locations(tls->ctx, cafile, NULL))) {
+ DEBUG_WARNING("Can't read CA file: %s\n", cafile);
+ ERR_clear_error();
+ return EINVAL;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Generate and set selfsigned certificate on TLS context
+ *
+ * @param tls TLS Context
+ * @param cn Common Name
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_selfsigned(struct tls *tls, const char *cn)
+{
+ X509_NAME *subj = NULL;
+ EVP_PKEY *key = NULL;
+ X509 *cert = NULL;
+ BIGNUM *bn = NULL;
+ RSA *rsa = NULL;
+ int r, err = ENOMEM;
+
+ if (!tls || !cn)
+ return EINVAL;
+
+ rsa = RSA_new();
+ if (!rsa)
+ goto out;
+
+ bn = BN_new();
+ if (!bn)
+ goto out;
+
+ BN_set_word(bn, RSA_F4);
+ if (!RSA_generate_key_ex(rsa, 1024, bn, NULL))
+ goto out;
+
+ key = EVP_PKEY_new();
+ if (!key)
+ goto out;
+
+ if (!EVP_PKEY_set1_RSA(key, rsa))
+ goto out;
+
+ cert = X509_new();
+ if (!cert)
+ goto out;
+
+ if (!X509_set_version(cert, 2))
+ goto out;
+
+ if (!ASN1_INTEGER_set(X509_get_serialNumber(cert), rand_u32()))
+ goto out;
+
+ subj = X509_NAME_new();
+ if (!subj)
+ goto out;
+
+ if (!X509_NAME_add_entry_by_txt(subj, "CN", MBSTRING_ASC,
+ (unsigned char *)cn,
+ (int)strlen(cn), -1, 0))
+ goto out;
+
+ if (!X509_set_issuer_name(cert, subj) ||
+ !X509_set_subject_name(cert, subj))
+ goto out;
+
+ if (!X509_gmtime_adj(X509_get_notBefore(cert), -3600*24*365) ||
+ !X509_gmtime_adj(X509_get_notAfter(cert), 3600*24*365*10))
+ goto out;
+
+ if (!X509_set_pubkey(cert, key))
+ goto out;
+
+ if (!X509_sign(cert, key, EVP_sha1()))
+ goto out;
+
+ r = SSL_CTX_use_certificate(tls->ctx, cert);
+ if (r != 1)
+ goto out;
+
+ r = SSL_CTX_use_PrivateKey(tls->ctx, key);
+ if (r != 1)
+ goto out;
+
+ if (tls->cert)
+ X509_free(tls->cert);
+
+ tls->cert = cert;
+ cert = NULL;
+
+ err = 0;
+
+ out:
+ if (subj)
+ X509_NAME_free(subj);
+
+ if (cert)
+ X509_free(cert);
+
+ if (key)
+ EVP_PKEY_free(key);
+
+ if (rsa)
+ RSA_free(rsa);
+
+ if (bn)
+ BN_free(bn);
+
+ if (err)
+ ERR_clear_error();
+
+ return err;
+}
+
+
+/**
+ * Set the certificate and private key on a TLS context
+ *
+ * @param tls TLS Context
+ * @param cert Certificate in PEM format
+ * @param len_cert Length of certificate PEM string
+ * @param key Private key in PEM format, will be read from cert if NULL
+ * @param len_key Length of private key PEM string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_certificate_pem(struct tls *tls, const char *cert, size_t len_cert,
+ const char *key, size_t len_key)
+{
+ BIO *bio = NULL, *kbio = NULL;
+ X509 *x509 = NULL;
+ EVP_PKEY *pkey = NULL;
+ int r, err = ENOMEM;
+
+ if (!tls || !cert || !len_cert || (key && !len_key))
+ return EINVAL;
+
+ if (!key) {
+ key = cert;
+ len_key = len_cert;
+ }
+
+ bio = BIO_new_mem_buf((char *)cert, (int)len_cert);
+ kbio = BIO_new_mem_buf((char *)key, (int)len_key);
+ if (!bio || !kbio)
+ goto out;
+
+ x509 = PEM_read_bio_X509(bio, NULL, 0, NULL);
+ pkey = PEM_read_bio_PrivateKey(kbio, NULL, 0, NULL);
+ if (!x509 || !pkey)
+ goto out;
+
+ r = SSL_CTX_use_certificate(tls->ctx, x509);
+ if (r != 1)
+ goto out;
+
+ r = SSL_CTX_use_PrivateKey(tls->ctx, pkey);
+ if (r != 1) {
+ DEBUG_WARNING("set_certificate_pem: use_PrivateKey failed\n");
+ goto out;
+ }
+
+ if (tls->cert)
+ X509_free(tls->cert);
+
+ tls->cert = x509;
+ x509 = NULL;
+
+ err = 0;
+
+ out:
+ if (x509)
+ X509_free(x509);
+ if (pkey)
+ EVP_PKEY_free(pkey);
+ if (bio)
+ BIO_free(bio);
+ if (kbio)
+ BIO_free(kbio);
+ if (err)
+ ERR_clear_error();
+
+ return err;
+}
+
+
+/**
+ * Set the certificate and private key on a TLS context
+ *
+ * @param tls TLS Context
+ * @param keytype Private key type
+ * @param cert Certificate in DER format
+ * @param len_cert Length of certificate DER bytes
+ * @param key Private key in DER format, will be read from cert if NULL
+ * @param len_key Length of private key DER bytes
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_certificate_der(struct tls *tls, enum tls_keytype keytype,
+ const uint8_t *cert, size_t len_cert,
+ const uint8_t *key, size_t len_key)
+{
+ const uint8_t *buf_cert;
+ X509 *x509 = NULL;
+ EVP_PKEY *pkey = NULL;
+ int r, type, err = ENOMEM;
+
+ if (!tls || !cert || !len_cert || (key && !len_key))
+ return EINVAL;
+
+ type = keytype2int(keytype);
+ if (type == EVP_PKEY_NONE)
+ return EINVAL;
+
+ buf_cert = cert;
+
+ x509 = d2i_X509(NULL, &buf_cert, len_cert);
+ if (!x509)
+ goto out;
+
+ if (!key) {
+ key = buf_cert;
+ len_key = len_cert - (buf_cert - cert);
+ }
+
+ pkey = d2i_PrivateKey(type, NULL, &key, len_key);
+ if (!pkey)
+ goto out;
+
+ r = SSL_CTX_use_certificate(tls->ctx, x509);
+ if (r != 1)
+ goto out;
+
+ r = SSL_CTX_use_PrivateKey(tls->ctx, pkey);
+ if (r != 1) {
+ DEBUG_WARNING("set_certificate_der: use_PrivateKey failed\n");
+ goto out;
+ }
+
+ if (tls->cert)
+ X509_free(tls->cert);
+
+ tls->cert = x509;
+ x509 = NULL;
+
+ err = 0;
+
+ out:
+ if (x509)
+ X509_free(x509);
+ if (pkey)
+ EVP_PKEY_free(pkey);
+ if (err)
+ ERR_clear_error();
+
+ return err;
+}
+
+
+/**
+ * Set the certificate and private key on a TLS context
+ *
+ * @param tls TLS Context
+ * @param pem Certificate and private key in PEM format
+ * @param len Length of PEM string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_certificate(struct tls *tls, const char *pem, size_t len)
+{
+ return tls_set_certificate_pem(tls, pem, len, NULL, 0);
+}
+
+
+static int verify_handler(int ok, X509_STORE_CTX *ctx)
+{
+ (void)ok;
+ (void)ctx;
+
+ return 1; /* We trust the certificate from peer */
+}
+
+
+/**
+ * Set TLS server context to request certificate from client
+ *
+ * @param tls TLS Context
+ */
+void tls_set_verify_client(struct tls *tls)
+{
+ if (!tls)
+ return;
+
+ SSL_CTX_set_verify_depth(tls->ctx, 0);
+ SSL_CTX_set_verify(tls->ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE,
+ verify_handler);
+}
+
+
+/**
+ * Set SRTP suites on TLS context
+ *
+ * @param tls TLS Context
+ * @param suites Secure-RTP Profiles
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_srtp(struct tls *tls, const char *suites)
+{
+#ifdef USE_OPENSSL_SRTP
+ if (!tls || !suites)
+ return EINVAL;
+
+ if (0 != SSL_CTX_set_tlsext_use_srtp(tls->ctx, suites)) {
+ ERR_clear_error();
+ return ENOSYS;
+ }
+
+ return 0;
+#else
+ (void)tls;
+ (void)suites;
+
+ return ENOSYS;
+#endif
+}
+
+
+static int cert_fingerprint(X509 *cert, enum tls_fingerprint type,
+ uint8_t *md, size_t size)
+{
+ unsigned int len = (unsigned int)size;
+ int n;
+
+ switch (type) {
+
+ case TLS_FINGERPRINT_SHA1:
+ if (size < 20)
+ return EOVERFLOW;
+
+ n = X509_digest(cert, EVP_sha1(), md, &len);
+ break;
+
+ case TLS_FINGERPRINT_SHA256:
+ if (size < 32)
+ return EOVERFLOW;
+
+ n = X509_digest(cert, EVP_sha256(), md, &len);
+ break;
+
+ default:
+ return ENOSYS;
+ }
+
+ if (n != 1) {
+ ERR_clear_error();
+ return ENOENT;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Get fingerprint of local certificate
+ *
+ * @param tls TLS Context
+ * @param type Digest type
+ * @param md Buffer for fingerprint digest
+ * @param size Buffer size
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_fingerprint(const struct tls *tls, enum tls_fingerprint type,
+ uint8_t *md, size_t size)
+{
+ if (!tls || !tls->cert || !md)
+ return EINVAL;
+
+ return cert_fingerprint(tls->cert, type, md, size);
+}
+
+
+/**
+ * Get fingerprint of peer certificate of a TLS connection
+ *
+ * @param tc TLS Connection
+ * @param type Digest type
+ * @param md Buffer for fingerprint digest
+ * @param size Buffer size
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_peer_fingerprint(const struct tls_conn *tc, enum tls_fingerprint type,
+ uint8_t *md, size_t size)
+{
+ X509 *cert;
+ int err;
+
+ if (!tc || !md)
+ return EINVAL;
+
+ cert = SSL_get_peer_certificate(tc->ssl);
+ if (!cert)
+ return ENOENT;
+
+ err = cert_fingerprint(cert, type, md, size);
+
+ X509_free(cert);
+
+ return err;
+}
+
+
+/**
+ * Get common name of peer certificate of a TLS connection
+ *
+ * @param tc TLS Connection
+ * @param cn Returned common name
+ * @param size Size of common name
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_peer_common_name(const struct tls_conn *tc, char *cn, size_t size)
+{
+ X509 *cert;
+ int n;
+
+ if (!tc || !cn || !size)
+ return EINVAL;
+
+ cert = SSL_get_peer_certificate(tc->ssl);
+ if (!cert)
+ return ENOENT;
+
+ n = X509_NAME_get_text_by_NID(X509_get_subject_name(cert),
+ NID_commonName, cn, (int)size);
+
+ X509_free(cert);
+
+ if (n < 0) {
+ ERR_clear_error();
+ return ENOENT;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Verify peer certificate of a TLS connection
+ *
+ * @param tc TLS Connection
+ *
+ * @return 0 if verified, otherwise errorcode
+ */
+int tls_peer_verify(const struct tls_conn *tc)
+{
+ if (!tc)
+ return EINVAL;
+
+ if (SSL_get_verify_result(tc->ssl) != X509_V_OK)
+ return EAUTH;
+
+ return 0;
+}
+
+
+/**
+ * Get SRTP suite and keying material of a TLS connection
+ *
+ * @param tc TLS Connection
+ * @param suite Returned SRTP suite
+ * @param cli_key Client key
+ * @param cli_key_size Client key size
+ * @param srv_key Server key
+ * @param srv_key_size Server key size
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_srtp_keyinfo(const struct tls_conn *tc, enum srtp_suite *suite,
+ uint8_t *cli_key, size_t cli_key_size,
+ uint8_t *srv_key, size_t srv_key_size)
+{
+#ifdef USE_OPENSSL_SRTP
+ static const char *label = "EXTRACTOR-dtls_srtp";
+ size_t key_size, salt_size, size;
+ SRTP_PROTECTION_PROFILE *sel;
+ uint8_t keymat[256], *p;
+
+ if (!tc || !suite || !cli_key || !srv_key)
+ return EINVAL;
+
+ sel = SSL_get_selected_srtp_profile(tc->ssl);
+ if (!sel)
+ return ENOENT;
+
+ switch (sel->id) {
+
+ case SRTP_AES128_CM_SHA1_80:
+ *suite = SRTP_AES_CM_128_HMAC_SHA1_80;
+ key_size = 16;
+ salt_size = 14;
+ break;
+
+ case SRTP_AES128_CM_SHA1_32:
+ *suite = SRTP_AES_CM_128_HMAC_SHA1_32;
+ key_size = 16;
+ salt_size = 14;
+ break;
+
+#ifdef SRTP_AEAD_AES_128_GCM
+ case SRTP_AEAD_AES_128_GCM:
+ *suite = SRTP_AES_128_GCM;
+ key_size = 16;
+ salt_size = 12;
+ break;
+#endif
+
+#ifdef SRTP_AEAD_AES_256_GCM
+ case SRTP_AEAD_AES_256_GCM:
+ *suite = SRTP_AES_256_GCM;
+ key_size = 32;
+ salt_size = 12;
+ break;
+#endif
+
+ default:
+ return ENOSYS;
+ }
+
+ size = key_size + salt_size;
+
+ if (cli_key_size < size || srv_key_size < size)
+ return EOVERFLOW;
+
+ if (sizeof(keymat) < 2*size)
+ return EOVERFLOW;
+
+ if (1 != SSL_export_keying_material(tc->ssl, keymat, 2*size, label,
+ strlen(label), NULL, 0, 0)) {
+ ERR_clear_error();
+ return ENOENT;
+ }
+
+ p = keymat;
+
+ memcpy(cli_key, p, key_size); p += key_size;
+ memcpy(srv_key, p, key_size); p += key_size;
+ memcpy(cli_key + key_size, p, salt_size); p += salt_size;
+ memcpy(srv_key + key_size, p, salt_size);
+
+ return 0;
+#else
+ (void)tc;
+ (void)suite;
+ (void)cli_key;
+ (void)cli_key_size;
+ (void)srv_key;
+ (void)srv_key_size;
+
+ return ENOSYS;
+#endif
+}
+
+
+/**
+ * Get cipher name of a TLS connection
+ *
+ * @param tc TLS Connection
+ *
+ * @return name of cipher actually used.
+ */
+const char *tls_cipher_name(const struct tls_conn *tc)
+{
+ if (!tc)
+ return NULL;
+
+ return SSL_get_cipher_name(tc->ssl);
+}
+
+
+/**
+ * Set the ciphers to use for this TLS context
+ *
+ * @param tls TLS Context
+ * @param cipherv Vector of cipher names, in order of priority
+ * @param count Number of cipher names in the vector
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_ciphers(struct tls *tls, const char *cipherv[], size_t count)
+{
+ struct mbuf *mb;
+ int r, err;
+ size_t i;
+
+ if (!tls || !cipherv || !count)
+ return EINVAL;
+
+ mb = mbuf_alloc(32 * count);
+ if (!mb)
+ return ENOMEM;
+
+ for (i=0; i<count; i++) {
+
+ err = mbuf_printf(mb, "%s%s", i>0 ? ":" : "", cipherv[i]);
+ if (err)
+ goto out;
+ }
+
+ err = mbuf_write_u8(mb, '\0');
+ if (err)
+ goto out;
+
+ r = SSL_CTX_set_cipher_list(tls->ctx, (char *)mb->buf);
+ if (r <= 0) {
+ ERR_clear_error();
+ err = EPROTO;
+ goto out;
+ }
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+/**
+ * Set the server name on a TLS Connection, using TLS SNI extension.
+ *
+ * @param tc TLS Connection
+ * @param servername Server name
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_servername(struct tls_conn *tc, const char *servername)
+{
+ if (!tc || !servername)
+ return EINVAL;
+
+ if (1 != SSL_set_tlsext_host_name(tc->ssl, servername)) {
+ DEBUG_WARNING("tls: SSL_set_tlsext_host_name error\n");
+ ERR_clear_error();
+ return EPROTO;
+ }
+
+ return 0;
+}
+
+
+static int print_error(const char *str, size_t len, void *unused)
+{
+ (void)unused;
+ DEBUG_WARNING("%b", str, len);
+
+ return 1;
+}
+
+
+void tls_flush_error(void)
+{
+ ERR_print_errors_cb(print_error, NULL);
+}
+
+
+/**
+ * Get the backend-specific (OpenSSL) context
+ *
+ * @param tls Generic TLS Context
+ *
+ * @return OpenSSL context
+ */
+struct ssl_ctx_st *tls_openssl_context(const struct tls *tls)
+{
+ return tls ? tls->ctx : NULL;
+}
diff --git a/src/tls/openssl/tls.h b/src/tls/openssl/tls.h
new file mode 100644
index 0000000..2c621d5
--- /dev/null
+++ b/src/tls/openssl/tls.h
@@ -0,0 +1,35 @@
+/**
+ * @file openssl/tls.h TLS backend using OpenSSL (Internal API)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/*
+ * Mapping of feature macros
+ */
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+#define TLS_BIO_OPAQUE 1
+#endif
+
+#if defined (LIBRESSL_VERSION_NUMBER)
+#undef TLS_BIO_OPAQUE
+#endif
+
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+#define SSL_state SSL_get_state
+#define SSL_ST_OK TLS_ST_OK
+#endif
+
+
+struct tls {
+ SSL_CTX *ctx;
+ X509 *cert;
+ char *pass; /* password for private key */
+};
+
+
+void tls_flush_error(void);
diff --git a/src/tls/openssl/tls_tcp.c b/src/tls/openssl/tls_tcp.c
new file mode 100644
index 0000000..ef3601e
--- /dev/null
+++ b/src/tls/openssl/tls_tcp.c
@@ -0,0 +1,437 @@
+/**
+ * @file openssl/tls_tcp.c TLS/TCP backend using OpenSSL
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_main.h>
+#include <re_sa.h>
+#include <re_net.h>
+#include <re_srtp.h>
+#include <re_tcp.h>
+#include <re_tls.h>
+#include "tls.h"
+
+
+#define DEBUG_MODULE "tls"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/* NOTE: shadow struct defined in tls_*.c */
+struct tls_conn {
+ SSL *ssl;
+#ifdef TLS_BIO_OPAQUE
+ BIO_METHOD *biomet;
+#endif
+ BIO *sbio_out;
+ BIO *sbio_in;
+ struct tcp_helper *th;
+ struct tcp_conn *tcp;
+ bool active;
+ bool up;
+};
+
+
+static void destructor(void *arg)
+{
+ struct tls_conn *tc = arg;
+
+ if (tc->ssl) {
+ int r = SSL_shutdown(tc->ssl);
+ if (r <= 0)
+ ERR_clear_error();
+
+ SSL_free(tc->ssl);
+ }
+
+#ifdef TLS_BIO_OPAQUE
+ if (tc->biomet)
+ BIO_meth_free(tc->biomet);
+#endif
+
+ mem_deref(tc->th);
+ mem_deref(tc->tcp);
+}
+
+
+static int bio_create(BIO *b)
+{
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_init(b, 1);
+ BIO_set_data(b, NULL);
+ BIO_set_flags(b, 0);
+#else
+ b->init = 1;
+ b->num = 0;
+ b->ptr = NULL;
+ b->flags = 0;
+#endif
+
+ return 1;
+}
+
+
+static int bio_destroy(BIO *b)
+{
+ if (!b)
+ return 0;
+
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_init(b, 0);
+ BIO_set_data(b, NULL);
+ BIO_set_flags(b, 0);
+#else
+ b->ptr = NULL;
+ b->init = 0;
+ b->flags = 0;
+#endif
+
+ return 1;
+}
+
+
+static int bio_write(BIO *b, const char *buf, int len)
+{
+#ifdef TLS_BIO_OPAQUE
+ struct tls_conn *tc = BIO_get_data(b);
+#else
+ struct tls_conn *tc = b->ptr;
+#endif
+ struct mbuf mb;
+ int err;
+
+ mb.buf = (void *)buf;
+ mb.pos = 0;
+ mb.end = mb.size = len;
+
+ err = tcp_send_helper(tc->tcp, &mb, tc->th);
+ if (err)
+ return -1;
+
+ return len;
+}
+
+
+static long bio_ctrl(BIO *b, int cmd, long num, void *ptr)
+{
+ (void)b;
+ (void)num;
+ (void)ptr;
+
+ if (cmd == BIO_CTRL_FLUSH) {
+ /* The OpenSSL library needs this */
+ return 1;
+ }
+
+ return 0;
+}
+
+
+#ifdef TLS_BIO_OPAQUE
+
+static BIO_METHOD *bio_method_tcp(void)
+{
+ BIO_METHOD *method;
+
+ method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "tcp_send");
+ if (!method) {
+ DEBUG_WARNING("alloc: BIO_meth_new() failed\n");
+ ERR_clear_error();
+ return NULL;
+ }
+
+ BIO_meth_set_write(method, bio_write);
+ BIO_meth_set_ctrl(method, bio_ctrl);
+ BIO_meth_set_create(method, bio_create);
+ BIO_meth_set_destroy(method, bio_destroy);
+
+ return method;
+}
+
+#else
+
+static struct bio_method_st bio_tcp_send = {
+ BIO_TYPE_SOURCE_SINK,
+ "tcp_send",
+ bio_write,
+ 0,
+ 0,
+ 0,
+ bio_ctrl,
+ bio_create,
+ bio_destroy,
+ 0
+};
+
+#endif
+
+
+static int tls_connect(struct tls_conn *tc)
+{
+ int err = 0, r;
+
+ ERR_clear_error();
+
+ r = SSL_connect(tc->ssl);
+ if (r <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, r);
+
+ ERR_clear_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ default:
+ DEBUG_WARNING("connect: error (r=%d, ssl_err=%d)\n",
+ r, ssl_err);
+ err = EPROTO;
+ break;
+ }
+ }
+
+ return err;
+}
+
+
+static int tls_accept(struct tls_conn *tc)
+{
+ int err = 0, r;
+
+ ERR_clear_error();
+
+ r = SSL_accept(tc->ssl);
+ if (r <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, r);
+
+ ERR_clear_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ default:
+ DEBUG_WARNING("accept error: (r=%d, ssl_err=%d)\n",
+ r, ssl_err);
+ err = EPROTO;
+ break;
+ }
+ }
+
+ return err;
+}
+
+
+static bool estab_handler(int *err, bool active, void *arg)
+{
+ struct tls_conn *tc = arg;
+
+ DEBUG_INFO("tcp established (active=%u)\n", active);
+
+ if (!active)
+ return true;
+
+ tc->active = true;
+ *err = tls_connect(tc);
+
+ return true;
+}
+
+
+static bool recv_handler(int *err, struct mbuf *mb, bool *estab, void *arg)
+{
+ struct tls_conn *tc = arg;
+ int r;
+
+ /* feed SSL data to the BIO */
+ r = BIO_write(tc->sbio_in, mbuf_buf(mb), (int)mbuf_get_left(mb));
+ if (r <= 0) {
+ DEBUG_WARNING("recv: BIO_write %d\n", r);
+ ERR_clear_error();
+ *err = ENOMEM;
+ return true;
+ }
+
+ if (SSL_state(tc->ssl) != SSL_ST_OK) {
+
+ if (tc->up) {
+ *err = EPROTO;
+ return true;
+ }
+
+ if (tc->active) {
+ *err = tls_connect(tc);
+ }
+ else {
+ *err = tls_accept(tc);
+ }
+
+ DEBUG_INFO("state=0x%04x\n", SSL_state(tc->ssl));
+
+ /* TLS connection is established */
+ if (SSL_state(tc->ssl) != SSL_ST_OK)
+ return true;
+
+ *estab = true;
+ tc->up = true;
+ }
+
+ mbuf_set_pos(mb, 0);
+
+ for (;;) {
+ int n;
+
+ if (mbuf_get_space(mb) < 4096) {
+ *err = mbuf_resize(mb, mb->size + 8192);
+ if (*err)
+ return true;
+ }
+
+ ERR_clear_error();
+
+ n = SSL_read(tc->ssl, mbuf_buf(mb), (int)mbuf_get_space(mb));
+ if (n <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, n);
+
+ ERR_clear_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ *err = ECONNRESET;
+ return true;
+
+ default:
+ *err = EPROTO;
+ return true;
+ }
+
+ break;
+ }
+
+ mb->pos += n;
+ }
+
+ mbuf_set_end(mb, mb->pos);
+ mbuf_set_pos(mb, 0);
+
+ return false;
+}
+
+
+static bool send_handler(int *err, struct mbuf *mb, void *arg)
+{
+ struct tls_conn *tc = arg;
+ int r;
+
+ ERR_clear_error();
+
+ r = SSL_write(tc->ssl, mbuf_buf(mb), (int)mbuf_get_left(mb));
+ if (r <= 0) {
+ DEBUG_WARNING("SSL_write: %d\n", SSL_get_error(tc->ssl, r));
+ ERR_clear_error();
+ *err = EPROTO;
+ }
+
+ return true;
+}
+
+
+/**
+ * Start TLS on a TCP-connection
+ *
+ * @param ptc Pointer to allocated TLS connectioon
+ * @param tls TLS Context
+ * @param tcp TCP Connection
+ * @param layer Protocol stack layer
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_start_tcp(struct tls_conn **ptc, struct tls *tls, struct tcp_conn *tcp,
+ int layer)
+{
+ struct tls_conn *tc;
+ int err;
+
+ if (!ptc || !tls || !tcp)
+ return EINVAL;
+
+ tc = mem_zalloc(sizeof(*tc), destructor);
+ if (!tc)
+ return ENOMEM;
+
+ err = tcp_register_helper(&tc->th, tcp, layer, estab_handler,
+ send_handler, recv_handler, tc);
+ if (err)
+ goto out;
+
+ tc->tcp = mem_ref(tcp);
+
+#ifdef TLS_BIO_OPAQUE
+ tc->biomet = bio_method_tcp();
+ if (!tc->biomet) {
+ err = ENOMEM;
+ goto out;
+ }
+#endif
+
+ err = ENOMEM;
+
+ /* Connect the SSL socket */
+ tc->ssl = SSL_new(tls->ctx);
+ if (!tc->ssl) {
+ DEBUG_WARNING("alloc: SSL_new() failed (ctx=%p)\n", tls->ctx);
+ ERR_clear_error();
+ goto out;
+ }
+
+ tc->sbio_in = BIO_new(BIO_s_mem());
+ if (!tc->sbio_in) {
+ DEBUG_WARNING("alloc: BIO_new() failed\n");
+ ERR_clear_error();
+ goto out;
+ }
+
+
+#ifdef TLS_BIO_OPAQUE
+ tc->sbio_out = BIO_new(tc->biomet);
+#else
+ tc->sbio_out = BIO_new(&bio_tcp_send);
+#endif
+ if (!tc->sbio_out) {
+ DEBUG_WARNING("alloc: BIO_new_socket() failed\n");
+ ERR_clear_error();
+ BIO_free(tc->sbio_in);
+ goto out;
+ }
+
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_data(tc->sbio_out, tc);
+#else
+ tc->sbio_out->ptr = tc;
+#endif
+
+ SSL_set_bio(tc->ssl, tc->sbio_in, tc->sbio_out);
+
+ err = 0;
+
+ out:
+ if (err)
+ mem_deref(tc);
+ else
+ *ptc = tc;
+
+ return err;
+}
diff --git a/src/tls/openssl/tls_udp.c b/src/tls/openssl/tls_udp.c
new file mode 100644
index 0000000..4ec81a3
--- /dev/null
+++ b/src/tls/openssl/tls_udp.c
@@ -0,0 +1,883 @@
+/**
+ * @file openssl/tls_udp.c DTLS backend using OpenSSL
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <sys/time.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_sa.h>
+#include <re_srtp.h>
+#include <re_udp.h>
+#include <re_tmr.h>
+#include <re_tls.h>
+#include "tls.h"
+
+
+#define DEBUG_MODULE "dtls"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+enum {
+ MTU_DEFAULT = 1400,
+ MTU_FALLBACK = 548,
+};
+
+
+struct dtls_sock {
+ struct sa peer;
+ struct udp_helper *uh;
+ struct udp_sock *us;
+ struct hash *ht;
+ struct mbuf *mb;
+ dtls_conn_h *connh;
+ void *arg;
+ size_t mtu;
+};
+
+
+/* NOTE: shadow struct defined in tls_*.c */
+struct tls_conn {
+ SSL *ssl; /* inheritance */
+#ifdef TLS_BIO_OPAQUE
+ BIO_METHOD *biomet;
+#endif
+ BIO *sbio_out;
+ BIO *sbio_in;
+ struct tmr tmr;
+ struct sa peer;
+ struct le he;
+ struct dtls_sock *sock;
+ dtls_estab_h *estabh;
+ dtls_recv_h *recvh;
+ dtls_close_h *closeh;
+ void *arg;
+ bool active;
+ bool up;
+};
+
+
+static int bio_create(BIO *b)
+{
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_init(b, 1);
+ BIO_set_data(b, NULL);
+ BIO_set_flags(b, 0);
+#else
+ b->init = 1;
+ b->num = 0;
+ b->ptr = NULL;
+ b->flags = 0;
+#endif
+
+ return 1;
+}
+
+
+static int bio_destroy(BIO *b)
+{
+ if (!b)
+ return 0;
+
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_init(b, 0);
+ BIO_set_data(b, NULL);
+ BIO_set_flags(b, 0);
+#else
+ b->ptr = NULL;
+ b->init = 0;
+ b->flags = 0;
+#endif
+
+ return 1;
+}
+
+
+static int bio_write(BIO *b, const char *buf, int len)
+{
+#ifdef TLS_BIO_OPAQUE
+ struct tls_conn *tc = BIO_get_data(b);
+#else
+ struct tls_conn *tc = b->ptr;
+#endif
+ struct mbuf *mb;
+ enum {SPACE = 4};
+ int err;
+
+ mb = mbuf_alloc(SPACE + len);
+ if (!mb)
+ return -1;
+
+ mb->pos = SPACE;
+ (void)mbuf_write_mem(mb, (void *)buf, len);
+ mb->pos = SPACE;
+
+ err = udp_send_helper(tc->sock->us, &tc->peer, mb, tc->sock->uh);
+
+ mem_deref(mb);
+
+ return err ? -1 : len;
+}
+
+
+static long bio_ctrl(BIO *b, int cmd, long num, void *ptr)
+{
+#ifdef TLS_BIO_OPAQUE
+ struct tls_conn *tc = BIO_get_data(b);
+#else
+ struct tls_conn *tc = b->ptr;
+#endif
+ (void)num;
+ (void)ptr;
+
+ switch (cmd) {
+
+ case BIO_CTRL_FLUSH:
+ /* The OpenSSL library needs this */
+ return 1;
+
+#if defined (BIO_CTRL_DGRAM_QUERY_MTU)
+ case BIO_CTRL_DGRAM_QUERY_MTU:
+ return tc ? tc->sock->mtu : MTU_DEFAULT;
+#endif
+
+#if defined (BIO_CTRL_DGRAM_GET_FALLBACK_MTU)
+ case BIO_CTRL_DGRAM_GET_FALLBACK_MTU:
+ return MTU_FALLBACK;
+#endif
+ }
+
+ return 0;
+}
+
+
+#ifdef TLS_BIO_OPAQUE
+
+static BIO_METHOD *bio_method_udp(void)
+{
+ BIO_METHOD *method;
+
+ method = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "udp_send");
+ if (!method) {
+ DEBUG_WARNING("alloc: BIO_meth_new() failed\n");
+ ERR_clear_error();
+ return NULL;
+ }
+
+ BIO_meth_set_write(method, bio_write);
+ BIO_meth_set_ctrl(method, bio_ctrl);
+ BIO_meth_set_create(method, bio_create);
+ BIO_meth_set_destroy(method, bio_destroy);
+
+ return method;
+}
+
+#else
+
+static struct bio_method_st bio_udp_send = {
+ BIO_TYPE_SOURCE_SINK,
+ "udp_send",
+ bio_write,
+ 0,
+ 0,
+ 0,
+ bio_ctrl,
+ bio_create,
+ bio_destroy,
+ 0
+};
+
+#endif
+
+
+static void tls_close(struct tls_conn *tc)
+{
+ int r;
+
+ if (!tc->ssl)
+ return;
+
+ r = SSL_shutdown(tc->ssl);
+ if (r <= 0)
+ ERR_clear_error();
+
+ SSL_free(tc->ssl);
+ tc->ssl = NULL;
+}
+
+
+static void conn_destructor(void *arg)
+{
+ struct tls_conn *tc = arg;
+
+ hash_unlink(&tc->he);
+ tmr_cancel(&tc->tmr);
+ tls_close(tc);
+
+#ifdef TLS_BIO_OPAQUE
+ if (tc->biomet)
+ BIO_meth_free(tc->biomet);
+#endif
+
+ mem_deref(tc->sock);
+}
+
+
+static void conn_close(struct tls_conn *tc, int err)
+{
+ tmr_cancel(&tc->tmr);
+ tls_close(tc);
+ tc->up = false;
+
+ if (tc->closeh)
+ tc->closeh(err, tc->arg);
+}
+
+
+#if defined (DTLS_CTRL_HANDLE_TIMEOUT) && defined(DTLS_CTRL_GET_TIMEOUT)
+
+static void check_timer(struct tls_conn *tc);
+
+
+static void timeout(void *arg)
+{
+ struct tls_conn *tc = arg;
+
+ DEBUG_INFO("timeout\n");
+
+ if (0 <= DTLSv1_handle_timeout(tc->ssl)) {
+
+ check_timer(tc);
+ }
+ else {
+ ERR_clear_error();
+ conn_close(tc, ETIMEDOUT);
+ }
+}
+
+
+static void check_timer(struct tls_conn *tc)
+{
+ struct timeval tv = {0, 0};
+
+ if (1 == DTLSv1_get_timeout(tc->ssl, &tv)) {
+
+ tmr_start(&tc->tmr, tv.tv_sec * 1000 + tv.tv_usec / 1000,
+ timeout, tc);
+ }
+ else {
+ tmr_cancel(&tc->tmr);
+ }
+}
+
+#else
+
+static void check_timer(struct tls_conn *tc)
+{
+ (void)tc;
+}
+
+#endif
+
+
+static int tls_connect(struct tls_conn *tc)
+{
+ int r;
+
+ ERR_clear_error();
+
+ r = SSL_connect(tc->ssl);
+ if (r <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, r);
+
+ tls_flush_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ default:
+ DEBUG_WARNING("connect error: %i\n", ssl_err);
+ return EPROTO;
+ }
+ }
+
+ check_timer(tc);
+
+ return 0;
+}
+
+
+static int tls_accept(struct tls_conn *tc)
+{
+ int r;
+
+ ERR_clear_error();
+
+ r = SSL_accept(tc->ssl);
+ if (r <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, r);
+
+ tls_flush_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ default:
+ DEBUG_WARNING("accept error: %i\n", ssl_err);
+ return EPROTO;
+ }
+ }
+
+ check_timer(tc);
+
+ return 0;
+}
+
+
+static void conn_recv(struct tls_conn *tc, struct mbuf *mb)
+{
+ int err, r;
+
+ if (!tc->ssl)
+ return;
+
+ /* feed SSL data to the BIO */
+ r = BIO_write(tc->sbio_in, mbuf_buf(mb), (int)mbuf_get_left(mb));
+ if (r <= 0) {
+ DEBUG_WARNING("receive bio write error: %i\n", r);
+ ERR_clear_error();
+ conn_close(tc, ENOMEM);
+ return;
+ }
+
+ if (SSL_state(tc->ssl) != SSL_ST_OK) {
+
+ if (tc->up) {
+ conn_close(tc, EPROTO);
+ return;
+ }
+
+ if (tc->active) {
+ err = tls_connect(tc);
+ }
+ else {
+ err = tls_accept(tc);
+ }
+
+ if (err) {
+ conn_close(tc, err);
+ return;
+ }
+
+ DEBUG_INFO("%s: state=0x%04x\n",
+ tc->active ? "client" : "server",
+ SSL_state(tc->ssl));
+
+ /* TLS connection is established */
+ if (SSL_state(tc->ssl) != SSL_ST_OK)
+ return;
+
+ tc->up = true;
+
+ if (tc->estabh) {
+ uint32_t nrefs;
+
+ mem_ref(tc);
+
+ tc->estabh(tc->arg);
+
+ nrefs = mem_nrefs(tc);
+ mem_deref(tc);
+
+ /* check if connection was deref'd from handler */
+ if (nrefs == 1)
+ return;
+ }
+ }
+
+ mbuf_set_pos(mb, 0);
+
+ for (;;) {
+ int n;
+
+ if (mbuf_get_space(mb) < 4096) {
+ err = mbuf_resize(mb, mb->size + 8192);
+ if (err) {
+ conn_close(tc, err);
+ return;
+ }
+ }
+
+ ERR_clear_error();
+
+ n = SSL_read(tc->ssl, mbuf_buf(mb), (int)mbuf_get_space(mb));
+ if (n <= 0) {
+ const int ssl_err = SSL_get_error(tc->ssl, n);
+
+ ERR_clear_error();
+
+ switch (ssl_err) {
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ conn_close(tc, ECONNRESET);
+ return;
+
+ default:
+ DEBUG_WARNING("read error: %i\n", ssl_err);
+ conn_close(tc, EPROTO);
+ return;
+ }
+
+ break;
+ }
+
+ mb->pos += n;
+ }
+
+ mbuf_set_end(mb, mb->pos);
+ mbuf_set_pos(mb, 0);
+
+ if (tc->recvh && mbuf_get_left(mb) > 0)
+ tc->recvh(mb, tc->arg);
+
+ return;
+}
+
+
+static int conn_alloc(struct tls_conn **ptc, struct tls *tls,
+ struct dtls_sock *sock, const struct sa *peer,
+ dtls_estab_h *estabh, dtls_recv_h *recvh,
+ dtls_close_h *closeh, void *arg)
+{
+ struct tls_conn *tc;
+ int err = 0;
+
+ tc = mem_zalloc(sizeof(*tc), conn_destructor);
+ if (!tc)
+ return ENOMEM;
+
+ hash_append(sock->ht, sa_hash(peer, SA_ALL), &tc->he, tc);
+
+ tc->sock = mem_ref(sock);
+ tc->peer = *peer;
+ tc->estabh = estabh;
+ tc->recvh = recvh;
+ tc->closeh = closeh;
+ tc->arg = arg;
+
+#ifdef TLS_BIO_OPAQUE
+ tc->biomet = bio_method_udp();
+ if (!tc->biomet) {
+ err = ENOMEM;
+ goto out;
+ }
+#endif
+
+ /* Connect the SSL socket */
+ tc->ssl = SSL_new(tls->ctx);
+ if (!tc->ssl) {
+ DEBUG_WARNING("ssl new failed: %i\n",
+ ERR_GET_REASON(ERR_get_error()));
+ ERR_clear_error();
+ err = ENOMEM;
+ goto out;
+ }
+
+ tc->sbio_in = BIO_new(BIO_s_mem());
+ if (!tc->sbio_in) {
+ ERR_clear_error();
+ err = ENOMEM;
+ goto out;
+ }
+
+#ifdef TLS_BIO_OPAQUE
+ tc->sbio_out = BIO_new(tc->biomet);
+#else
+ tc->sbio_out = BIO_new(&bio_udp_send);
+#endif
+ if (!tc->sbio_out) {
+ ERR_clear_error();
+ BIO_free(tc->sbio_in);
+ err = ENOMEM;
+ goto out;
+ }
+
+#ifdef TLS_BIO_OPAQUE
+ BIO_set_data(tc->sbio_out, tc);
+#else
+ tc->sbio_out->ptr = tc;
+#endif
+
+ SSL_set_bio(tc->ssl, tc->sbio_in, tc->sbio_out);
+
+ SSL_set_read_ahead(tc->ssl, 1);
+
+ out:
+ if (err)
+ mem_deref(tc);
+ else
+ *ptc = tc;
+
+ return err;
+}
+
+
+/**
+ * DTLS Connect
+ *
+ * @param ptc Pointer to allocated DTLS connection
+ * @param tls TLS Context
+ * @param sock DTLS Socket
+ * @param peer Peer address
+ * @param estabh Establish handler
+ * @param recvh Receive handler
+ * @param closeh Close handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dtls_connect(struct tls_conn **ptc, struct tls *tls,
+ struct dtls_sock *sock, const struct sa *peer,
+ dtls_estab_h *estabh, dtls_recv_h *recvh,
+ dtls_close_h *closeh, void *arg)
+{
+ struct tls_conn *tc;
+ int err;
+
+ if (!ptc || !tls || !sock || !peer)
+ return EINVAL;
+
+ err = conn_alloc(&tc, tls, sock, peer, estabh, recvh, closeh, arg);
+ if (err)
+ return err;
+
+ tc->active = true;
+
+ err = tls_connect(tc);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(tc);
+ else
+ *ptc = tc;
+
+ return err;
+}
+
+
+/**
+ * DTLS Accept
+ *
+ * @param ptc Pointer to allocated DTLS connection
+ * @param tls TLS Context
+ * @param sock DTLS Socket
+ * @param estabh Establish handler
+ * @param recvh Receive handler
+ * @param closeh Close handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dtls_accept(struct tls_conn **ptc, struct tls *tls,
+ struct dtls_sock *sock,
+ dtls_estab_h *estabh, dtls_recv_h *recvh,
+ dtls_close_h *closeh, void *arg)
+{
+ struct tls_conn *tc;
+ int err, r;
+
+ if (!ptc || !tls || !sock || !sock->mb)
+ return EINVAL;
+
+ err = conn_alloc(&tc, tls, sock, &sock->peer, estabh, recvh, closeh,
+ arg);
+ if (err)
+ return err;
+
+ tc->active = false;
+
+ r = BIO_write(tc->sbio_in, mbuf_buf(sock->mb),
+ (int)mbuf_get_left(sock->mb));
+ if (r <= 0) {
+ DEBUG_WARNING("accept bio write error: %i\n", r);
+ ERR_clear_error();
+ err = ENOMEM;
+ goto out;
+ }
+
+ err = tls_accept(tc);
+ if (err)
+ goto out;
+
+ sock->mb = mem_deref(sock->mb);
+
+ out:
+ if (err)
+ mem_deref(tc);
+ else
+ *ptc = tc;
+
+ return err;
+}
+
+
+/**
+ * Send data on a DTLS connection
+ *
+ * @param tc DTLS connection
+ * @param mb Buffer to send
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dtls_send(struct tls_conn *tc, struct mbuf *mb)
+{
+ int r;
+
+ if (!tc || !mb)
+ return EINVAL;
+
+ if (!tc->up || !tc->ssl)
+ return ENOTCONN;
+
+ ERR_clear_error();
+
+ r = SSL_write(tc->ssl, mbuf_buf(mb), (int)mbuf_get_left(mb));
+ if (r <= 0) {
+ DEBUG_WARNING("write error: %i\n", SSL_get_error(tc->ssl, r));
+ ERR_clear_error();
+ return EPROTO;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Set handlers on a DTLS Connection
+ *
+ * @param tc DTLS Connection
+ * @param estabh DTLS Connection Established handler
+ * @param recvh DTLS Connection Receive data handler
+ * @param closeh DTLS Connection Close handler
+ * @param arg Handler argument
+ */
+void dtls_set_handlers(struct tls_conn *tc, dtls_estab_h *estabh,
+ dtls_recv_h *recvh, dtls_close_h *closeh, void *arg)
+{
+ if (!tc)
+ return;
+
+ tc->estabh = estabh;
+ tc->recvh = recvh;
+ tc->closeh = closeh;
+ tc->arg = arg;
+}
+
+
+/**
+ * Get the remote peer of a DTLS Connection
+ *
+ * @param tc DTLS Connection
+ *
+ * @return Remote peer
+ */
+const struct sa *dtls_peer(const struct tls_conn *tc)
+{
+ return tc ? &tc->peer : NULL;
+}
+
+
+/**
+ * Set the remote peer of a DTLS Connection
+ *
+ * @param tc DTLS Connection
+ * @param peer Peer address
+ */
+void dtls_set_peer(struct tls_conn *tc, const struct sa *peer)
+{
+ if (!tc || !peer)
+ return;
+
+ hash_unlink(&tc->he);
+ hash_append(tc->sock->ht, sa_hash(peer, SA_ALL), &tc->he, tc);
+
+ tc->peer = *peer;
+}
+
+
+static void sock_destructor(void *arg)
+{
+ struct dtls_sock *sock = arg;
+
+ hash_clear(sock->ht);
+ mem_deref(sock->uh);
+ mem_deref(sock->us);
+ mem_deref(sock->ht);
+ mem_deref(sock->mb);
+}
+
+
+static bool cmp_handler(struct le *le, void *arg)
+{
+ struct tls_conn *tc = le->data;
+
+ return sa_cmp(&tc->peer, arg, SA_ALL);
+}
+
+
+static struct tls_conn *conn_lookup(struct dtls_sock *sock,
+ const struct sa *peer)
+{
+ return list_ledata(hash_lookup(sock->ht, sa_hash(peer, SA_ALL),
+ cmp_handler, (void *)peer));
+}
+
+
+static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct dtls_sock *sock = arg;
+ struct tls_conn *tc;
+ uint8_t b;
+
+ if (mbuf_get_left(mb) < 1)
+ return false;
+
+ b = mb->buf[mb->pos];
+ if (b < 20 || b > 63)
+ return false;
+
+ tc = conn_lookup(sock, src);
+ if (tc) {
+ conn_recv(tc, mb);
+ return true;
+ }
+
+ if (sock->connh) {
+
+ mem_deref(sock->mb);
+ sock->mb = mem_ref(mb);
+ sock->peer = *src;
+
+ sock->connh(src, sock->arg);
+ }
+
+ return true;
+}
+
+
+/**
+ * Create DTLS Socket
+ *
+ * @param sockp Pointer to returned DTLS Socket
+ * @param laddr Local listen address (optional)
+ * @param us External UDP socket (optional)
+ * @param htsize Connection hash table size
+ * @param layer UDP protocol layer
+ * @param connh Connect handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dtls_listen(struct dtls_sock **sockp, const struct sa *laddr,
+ struct udp_sock *us, uint32_t htsize, int layer,
+ dtls_conn_h *connh, void *arg)
+{
+ struct dtls_sock *sock;
+ int err;
+
+ if (!sockp)
+ return EINVAL;
+
+ sock = mem_zalloc(sizeof(*sock), sock_destructor);
+ if (!sock)
+ return ENOMEM;
+
+ if (us) {
+ sock->us = mem_ref(us);
+ }
+ else {
+ err = udp_listen(&sock->us, laddr, NULL, NULL);
+ if (err)
+ goto out;
+ }
+
+ err = udp_register_helper(&sock->uh, sock->us, layer,
+ NULL, recv_handler, sock);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&sock->ht, hash_valid_size(htsize));
+ if (err)
+ goto out;
+
+ sock->mtu = MTU_DEFAULT;
+ sock->connh = connh;
+ sock->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(sock);
+ else
+ *sockp = sock;
+
+ return err;
+}
+
+
+/**
+ * Get the underlying UDP socket of a DTLS Socket
+ *
+ * @param sock DTLS Socket
+ *
+ * @return UDP Socket
+ */
+struct udp_sock *dtls_udp_sock(struct dtls_sock *sock)
+{
+ return sock ? sock->us : NULL;
+}
+
+
+/**
+ * Set MTU on a DTLS Socket
+ *
+ * @param sock DTLS Socket
+ * @param mtu MTU value
+ */
+void dtls_set_mtu(struct dtls_sock *sock, size_t mtu)
+{
+ if (!sock)
+ return;
+
+ sock->mtu = mtu;
+}
+
+
+void dtls_recv_packet(struct dtls_sock *sock, const struct sa *src,
+ struct mbuf *mb)
+{
+ struct sa addr;
+
+ if (!sock || !src || !mb)
+ return;
+
+ addr = *src;
+
+ recv_handler(&addr, mb, sock);
+}
diff --git a/src/tmr/mod.mk b/src/tmr/mod.mk
new file mode 100644
index 0000000..324c183
--- /dev/null
+++ b/src/tmr/mod.mk
@@ -0,0 +1,7 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += tmr/tmr.c
diff --git a/src/tmr/tmr.c b/src/tmr/tmr.c
new file mode 100644
index 0000000..73083f1
--- /dev/null
+++ b/src/tmr/tmr.c
@@ -0,0 +1,305 @@
+/**
+ * @file tmr.c Timer implementation
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <time.h>
+#endif
+#ifdef HAVE_PTHREAD
+#include <stdlib.h>
+#include <pthread.h>
+#endif
+#include <re_types.h>
+#include <re_list.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_tmr.h>
+
+
+#define DEBUG_MODULE "tmr"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+#if !defined (RELEASE) && !defined (TMR_DEBUG)
+#define TMR_DEBUG 1 /**< Timer debugging (0 or 1) */
+#endif
+
+/** Timer values */
+enum {
+ MAX_BLOCKING = 100 /**< Maximum time spent in handler [ms] */
+};
+
+extern struct list *tmrl_get(void);
+
+
+static bool inspos_handler(struct le *le, void *arg)
+{
+ struct tmr *tmr = le->data;
+ const uint64_t now = *(uint64_t *)arg;
+
+ return tmr->jfs <= now;
+}
+
+
+static bool inspos_handler_0(struct le *le, void *arg)
+{
+ struct tmr *tmr = le->data;
+ const uint64_t now = *(uint64_t *)arg;
+
+ return tmr->jfs > now;
+}
+
+
+#if TMR_DEBUG
+static void call_handler(tmr_h *th, void *arg)
+{
+ const uint64_t tick = tmr_jiffies();
+ uint32_t diff;
+
+ /* Call handler */
+ th(arg);
+
+ diff = (uint32_t)(tmr_jiffies() - tick);
+
+ if (diff > MAX_BLOCKING) {
+ DEBUG_WARNING("long async blocking: %u>%u ms (h=%p arg=%p)\n",
+ diff, MAX_BLOCKING, th, arg);
+ }
+}
+#endif
+
+
+/**
+ * Poll all timers in the current thread
+ *
+ * @param tmrl Timer list
+ */
+void tmr_poll(struct list *tmrl)
+{
+ const uint64_t jfs = tmr_jiffies();
+
+ for (;;) {
+ struct tmr *tmr;
+ tmr_h *th;
+ void *th_arg;
+
+ tmr = list_ledata(tmrl->head);
+
+ if (!tmr || (tmr->jfs > jfs)) {
+ break;
+ }
+
+ th = tmr->th;
+ th_arg = tmr->arg;
+
+ tmr->th = NULL;
+
+ list_unlink(&tmr->le);
+
+ if (!th)
+ continue;
+
+#if TMR_DEBUG
+ call_handler(th, th_arg);
+#else
+ th(th_arg);
+#endif
+ }
+}
+
+
+/**
+ * Get the timer jiffies in milliseconds
+ *
+ * @return Jiffies in [ms]
+ */
+uint64_t tmr_jiffies(void)
+{
+ uint64_t jfs;
+
+#if defined(WIN32)
+ FILETIME ft;
+ ULARGE_INTEGER li;
+ GetSystemTimeAsFileTime(&ft);
+ li.LowPart = ft.dwLowDateTime;
+ li.HighPart = ft.dwHighDateTime;
+ jfs = li.QuadPart/10/1000;
+#else
+ struct timeval now;
+
+ if (0 != gettimeofday(&now, NULL)) {
+ DEBUG_WARNING("jiffies: gettimeofday() failed (%m)\n", errno);
+ return 0;
+ }
+
+ jfs = (long)now.tv_sec * (uint64_t)1000;
+ jfs += now.tv_usec / 1000;
+#endif
+
+ return jfs;
+}
+
+
+/**
+ * Get number of milliseconds until the next timer expires
+ *
+ * @param tmrl Timer-list
+ *
+ * @return Number of [ms], or 0 if no active timers
+ */
+uint64_t tmr_next_timeout(struct list *tmrl)
+{
+ const uint64_t jif = tmr_jiffies();
+ const struct tmr *tmr;
+
+ tmr = list_ledata(tmrl->head);
+ if (!tmr)
+ return 0;
+
+ if (tmr->jfs <= jif)
+ return 1;
+ else
+ return tmr->jfs - jif;
+}
+
+
+int tmr_status(struct re_printf *pf, void *unused)
+{
+ struct list *tmrl = tmrl_get();
+ struct le *le;
+ uint32_t n;
+ int err;
+
+ (void)unused;
+
+ n = list_count(tmrl);
+ if (!n)
+ return 0;
+
+ err = re_hprintf(pf, "Timers (%u):\n", n);
+
+ for (le = tmrl->head; le; le = le->next) {
+ const struct tmr *tmr = le->data;
+
+ err |= re_hprintf(pf, " %p: th=%p expire=%llums\n",
+ tmr, tmr->th,
+ (unsigned long long)tmr_get_expire(tmr));
+ }
+
+ if (n > 100)
+ err |= re_hprintf(pf, " (Dumped Timers: %u)\n", n);
+
+ return err;
+}
+
+
+/**
+ * Print timer debug info to stderr
+ */
+void tmr_debug(void)
+{
+ if (!list_isempty(tmrl_get()))
+ (void)re_fprintf(stderr, "%H", tmr_status, NULL);
+}
+
+
+/**
+ * Initialise a timer object
+ *
+ * @param tmr Timer to initialise
+ */
+void tmr_init(struct tmr *tmr)
+{
+ if (!tmr)
+ return;
+
+ memset(tmr, 0, sizeof(*tmr));
+}
+
+
+/**
+ * Start a timer
+ *
+ * @param tmr Timer to start
+ * @param delay Timer delay in [ms]
+ * @param th Timeout handler
+ * @param arg Handler argument
+ */
+void tmr_start(struct tmr *tmr, uint64_t delay, tmr_h *th, void *arg)
+{
+ struct list *tmrl = tmrl_get();
+ struct le *le;
+
+ if (!tmr)
+ return;
+
+ if (tmr->th) {
+ list_unlink(&tmr->le);
+ }
+
+ tmr->th = th;
+ tmr->arg = arg;
+
+ if (!th)
+ return;
+
+ tmr->jfs = delay + tmr_jiffies();
+
+ if (delay == 0) {
+ le = list_apply(tmrl, true, inspos_handler_0, &tmr->jfs);
+ if (le) {
+ list_insert_before(tmrl, le, &tmr->le, tmr);
+ }
+ else {
+ list_append(tmrl, &tmr->le, tmr);
+ }
+ }
+ else {
+ le = list_apply(tmrl, false, inspos_handler, &tmr->jfs);
+ if (le) {
+ list_insert_after(tmrl, le, &tmr->le, tmr);
+ }
+ else {
+ list_prepend(tmrl, &tmr->le, tmr);
+ }
+ }
+}
+
+
+/**
+ * Cancel an active timer
+ *
+ * @param tmr Timer to cancel
+ */
+void tmr_cancel(struct tmr *tmr)
+{
+ tmr_start(tmr, 0, NULL, NULL);
+}
+
+
+/**
+ * Get the time left until timer expires
+ *
+ * @param tmr Timer object
+ *
+ * @return Time in [ms] until expiration
+ */
+uint64_t tmr_get_expire(const struct tmr *tmr)
+{
+ uint64_t jfs;
+
+ if (!tmr || !tmr->th)
+ return 0;
+
+ jfs = tmr_jiffies();
+
+ return (tmr->jfs > jfs) ? (tmr->jfs - jfs) : 0;
+}
diff --git a/src/turn/chan.c b/src/turn/chan.c
new file mode 100644
index 0000000..2a6cf70
--- /dev/null
+++ b/src/turn/chan.c
@@ -0,0 +1,305 @@
+/**
+ * @file chan.c TURN Channels handling
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_md5.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_turn.h>
+#include "turnc.h"
+
+
+enum {
+ CHAN_LIFETIME = 600,
+ CHAN_REFRESH = 250,
+ CHAN_NUMB_MIN = 0x4000,
+ CHAN_NUMB_MAX = 0x7fff
+};
+
+
+struct channels {
+ struct hash *ht_numb;
+ struct hash *ht_peer;
+ uint16_t nr;
+};
+
+
+struct chan {
+ struct le he_numb;
+ struct le he_peer;
+ struct loop_state ls;
+ uint16_t nr;
+ struct sa peer;
+ struct tmr tmr;
+ struct turnc *turnc;
+ struct stun_ctrans *ct;
+ turnc_chan_h *ch;
+ void *arg;
+};
+
+
+static int chanbind_request(struct chan *chan, bool reset_ls);
+
+
+static void channels_destructor(void *data)
+{
+ struct channels *c = data;
+
+ /* flush from primary hash */
+ hash_flush(c->ht_numb);
+
+ mem_deref(c->ht_numb);
+ mem_deref(c->ht_peer);
+}
+
+
+static void chan_destructor(void *data)
+{
+ struct chan *chan = data;
+
+ tmr_cancel(&chan->tmr);
+ mem_deref(chan->ct);
+ hash_unlink(&chan->he_numb);
+ hash_unlink(&chan->he_peer);
+}
+
+
+static bool numb_hash_cmp_handler(struct le *le, void *arg)
+{
+ const struct chan *chan = le->data;
+ const uint16_t *nr = arg;
+
+ return chan->nr == *nr;
+}
+
+
+static bool peer_hash_cmp_handler(struct le *le, void *arg)
+{
+ const struct chan *chan = le->data;
+
+ return sa_cmp(&chan->peer, arg, SA_ALL);
+}
+
+
+static void timeout(void *arg)
+{
+ struct chan *chan = arg;
+ int err;
+
+ err = chanbind_request(chan, true);
+ if (err)
+ chan->turnc->th(err, 0, NULL, NULL, NULL, NULL,
+ chan->turnc->arg);
+}
+
+
+static void chanbind_resp_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct chan *chan = arg;
+
+ if (err || turnc_request_loops(&chan->ls, scode))
+ goto out;
+
+ switch (scode) {
+
+ case 0:
+ tmr_start(&chan->tmr, CHAN_REFRESH * 1000, timeout, chan);
+ if (chan->ch) {
+ chan->ch(chan->arg);
+ chan->ch = NULL;
+ chan->arg = NULL;
+ }
+ return;
+
+ case 401:
+ case 438:
+ err = turnc_keygen(chan->turnc, msg);
+ if (err)
+ break;
+
+ err = chanbind_request(chan, false);
+ if (err)
+ break;
+
+ return;
+
+ default:
+ break;
+ }
+
+ out:
+ chan->turnc->th(err, scode, reason, NULL, NULL, msg, chan->turnc->arg);
+}
+
+
+static int chanbind_request(struct chan *chan, bool reset_ls)
+{
+ struct turnc *t = chan->turnc;
+
+ if (reset_ls)
+ turnc_loopstate_reset(&chan->ls);
+
+ return stun_request(&chan->ct, t->stun, t->proto, t->sock, &t->srv, 0,
+ STUN_METHOD_CHANBIND,
+ t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash),
+ false, chanbind_resp_handler, chan, 6,
+ STUN_ATTR_CHANNEL_NUMBER, &chan->nr,
+ STUN_ATTR_XOR_PEER_ADDR, &chan->peer,
+ STUN_ATTR_USERNAME, t->realm ? t->username : NULL,
+ STUN_ATTR_REALM, t->realm,
+ STUN_ATTR_NONCE, t->nonce,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+/**
+ * Add a TURN Channel for a peer
+ *
+ * @param turnc TURN Client
+ * @param peer Peer IP-address
+ * @param ch Channel handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int turnc_add_chan(struct turnc *turnc, const struct sa *peer,
+ turnc_chan_h *ch, void *arg)
+{
+ struct chan *chan;
+ int err;
+
+ if (!turnc || !peer)
+ return EINVAL;
+
+ if (turnc->chans->nr >= CHAN_NUMB_MAX)
+ return ERANGE;
+
+ if (turnc_chan_find_peer(turnc, peer))
+ return 0;
+
+ chan = mem_zalloc(sizeof(*chan), chan_destructor);
+ if (!chan)
+ return ENOMEM;
+
+ chan->nr = turnc->chans->nr++;
+ chan->peer = *peer;
+
+ hash_append(turnc->chans->ht_numb, chan->nr, &chan->he_numb, chan);
+ hash_append(turnc->chans->ht_peer, sa_hash(peer, SA_ALL),
+ &chan->he_peer, chan);
+
+ tmr_init(&chan->tmr);
+ chan->turnc = turnc;
+ chan->ch = ch;
+ chan->arg = arg;
+
+ err = chanbind_request(chan, true);
+ if (err)
+ mem_deref(chan);
+
+ return err;
+}
+
+
+int turnc_chan_hash_alloc(struct channels **cp, uint32_t bsize)
+{
+ struct channels *c;
+ int err;
+
+ if (!cp)
+ return EINVAL;
+
+ c = mem_zalloc(sizeof(*c), channels_destructor);
+ if (!c)
+ return ENOMEM;
+
+ err = hash_alloc(&c->ht_numb, bsize);
+ if (err)
+ goto out;
+
+ err = hash_alloc(&c->ht_peer, bsize);
+ if (err)
+ goto out;
+
+ c->nr = CHAN_NUMB_MIN;
+
+ out:
+ if (err)
+ mem_deref(c);
+ else
+ *cp = c;
+
+ return err;
+}
+
+
+struct chan *turnc_chan_find_numb(const struct turnc *turnc, uint16_t nr)
+{
+ if (!turnc)
+ return NULL;
+
+ return list_ledata(hash_lookup(turnc->chans->ht_numb, nr,
+ numb_hash_cmp_handler, &nr));
+}
+
+
+struct chan *turnc_chan_find_peer(const struct turnc *turnc,
+ const struct sa *peer)
+{
+ if (!turnc)
+ return NULL;
+
+ return list_ledata(hash_lookup(turnc->chans->ht_peer,
+ sa_hash(peer, SA_ALL),
+ peer_hash_cmp_handler, (void *)peer));
+}
+
+
+uint16_t turnc_chan_numb(const struct chan *chan)
+{
+ return chan ? chan->nr : 0;
+}
+
+
+const struct sa *turnc_chan_peer(const struct chan *chan)
+{
+ return chan ? &chan->peer : NULL;
+}
+
+
+int turnc_chan_hdr_encode(const struct chan_hdr *hdr, struct mbuf *mb)
+{
+ int err;
+
+ if (!hdr || !mb)
+ return EINVAL;
+
+ err = mbuf_write_u16(mb, htons(hdr->nr));
+ err |= mbuf_write_u16(mb, htons(hdr->len));
+
+ return err;
+}
+
+
+int turnc_chan_hdr_decode(struct chan_hdr *hdr, struct mbuf *mb)
+{
+ if (!hdr || !mb)
+ return EINVAL;
+
+ if (mbuf_get_left(mb) < sizeof(*hdr))
+ return ENOENT;
+
+ hdr->nr = ntohs(mbuf_read_u16(mb));
+ hdr->len = ntohs(mbuf_read_u16(mb));
+
+ return 0;
+}
diff --git a/src/turn/mod.mk b/src/turn/mod.mk
new file mode 100644
index 0000000..eb20f18
--- /dev/null
+++ b/src/turn/mod.mk
@@ -0,0 +1,9 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += turn/chan.c
+SRCS += turn/perm.c
+SRCS += turn/turnc.c
diff --git a/src/turn/perm.c b/src/turn/perm.c
new file mode 100644
index 0000000..c80650a
--- /dev/null
+++ b/src/turn/perm.c
@@ -0,0 +1,182 @@
+/**
+ * @file perm.c TURN permission handling
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_md5.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_turn.h>
+#include "turnc.h"
+
+
+enum {
+ PERM_LIFETIME = 300,
+ PERM_REFRESH = 250,
+};
+
+
+struct perm {
+ struct le he;
+ struct loop_state ls;
+ struct sa peer;
+ struct tmr tmr;
+ struct turnc *turnc;
+ struct stun_ctrans *ct;
+ turnc_perm_h *ph;
+ void *arg;
+};
+
+
+static int createperm_request(struct perm *perm, bool reset_ls);
+
+
+static void destructor(void *arg)
+{
+ struct perm *perm = arg;
+
+ tmr_cancel(&perm->tmr);
+ mem_deref(perm->ct);
+ hash_unlink(&perm->he);
+}
+
+
+static bool hash_cmp_handler(struct le *le, void *arg)
+{
+ const struct perm *perm = le->data;
+
+ return sa_cmp(&perm->peer, arg, SA_ADDR);
+}
+
+
+static struct perm *perm_find(const struct turnc *turnc, const struct sa *peer)
+{
+ return list_ledata(hash_lookup(turnc->perms, sa_hash(peer, SA_ADDR),
+ hash_cmp_handler, (void *)peer));
+}
+
+
+static void timeout(void *arg)
+{
+ struct perm *perm = arg;
+ int err;
+
+ err = createperm_request(perm, true);
+ if (err)
+ perm->turnc->th(err, 0, NULL, NULL, NULL, NULL,
+ perm->turnc->arg);
+}
+
+
+static void createperm_resp_handler(int err, uint16_t scode,
+ const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct perm *perm = arg;
+
+ if (err || turnc_request_loops(&perm->ls, scode))
+ goto out;
+
+ switch (scode) {
+
+ case 0:
+ tmr_start(&perm->tmr, PERM_REFRESH * 1000, timeout, perm);
+ if (perm->ph) {
+ perm->ph(perm->arg);
+ perm->ph = NULL;
+ perm->arg = NULL;
+ }
+ return;
+
+ case 401:
+ case 438:
+ err = turnc_keygen(perm->turnc, msg);
+ if (err)
+ break;
+
+ err = createperm_request(perm, false);
+ if (err)
+ break;
+
+ return;
+
+ default:
+ break;
+ }
+
+ out:
+ perm->turnc->th(err, scode, reason, NULL, NULL, msg, perm->turnc->arg);
+}
+
+
+static int createperm_request(struct perm *perm, bool reset_ls)
+{
+ struct turnc *t = perm->turnc;
+
+ if (reset_ls)
+ turnc_loopstate_reset(&perm->ls);
+
+ return stun_request(&perm->ct, t->stun, t->proto, t->sock, &t->srv, 0,
+ STUN_METHOD_CREATEPERM,
+ t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash),
+ false, createperm_resp_handler, perm, 5,
+ STUN_ATTR_XOR_PEER_ADDR, &perm->peer,
+ STUN_ATTR_USERNAME, t->realm ? t->username : NULL,
+ STUN_ATTR_REALM, t->realm,
+ STUN_ATTR_NONCE, t->nonce,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+/**
+ * Add TURN Permission for a peer
+ *
+ * @param turnc TURN Client
+ * @param peer Peer IP-address
+ * @param ph Permission handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int turnc_add_perm(struct turnc *turnc, const struct sa *peer,
+ turnc_perm_h *ph, void *arg)
+{
+ struct perm *perm;
+ int err;
+
+ if (!turnc || !peer)
+ return EINVAL;
+
+ if (perm_find(turnc, peer))
+ return 0;
+
+ perm = mem_zalloc(sizeof(*perm), destructor);
+ if (!perm)
+ return ENOMEM;
+
+ hash_append(turnc->perms, sa_hash(peer, SA_ADDR), &perm->he, perm);
+ tmr_init(&perm->tmr);
+ perm->peer = *peer;
+ perm->turnc = turnc;
+ perm->ph = ph;
+ perm->arg = arg;
+
+ err = createperm_request(perm, true);
+ if (err)
+ mem_deref(perm);
+
+ return err;
+}
+
+
+int turnc_perm_hash_alloc(struct hash **ht, uint32_t bsize)
+{
+ return hash_alloc(ht, bsize);
+}
diff --git a/src/turn/turnc.c b/src/turn/turnc.c
new file mode 100644
index 0000000..b103dba
--- /dev/null
+++ b/src/turn/turnc.c
@@ -0,0 +1,682 @@
+/**
+ * @file turnc.c TURN Client implementation
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_md5.h>
+#include <re_list.h>
+#include <re_hash.h>
+#include <re_tmr.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_srtp.h>
+#include <re_tls.h>
+#include <re_stun.h>
+#include <re_turn.h>
+#include "turnc.h"
+
+
+#define DEBUG_MODULE "turnc"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** TURN Client protocol values */
+enum {
+ PERM_HASH_SIZE = 16,
+ CHAN_HASH_SIZE = 16,
+ FAILC_MAX = 16, /**< Maximum number of request errors for loopcheck. */
+ STUN_ATTR_ADDR4_SIZE = 8,
+ STUN_ATTR_ADDR6_SIZE = 20,
+};
+
+
+static const uint8_t sendind_tid[STUN_TID_SIZE];
+
+static int allocate_request(struct turnc *t);
+static int refresh_request(struct turnc *t, uint32_t lifetime, bool reset_ls,
+ stun_resp_h *resph, void *arg);
+static void refresh_resp_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg);
+
+
+static void destructor(void *arg)
+{
+ struct turnc *turnc = arg;
+
+ if (turnc->allocated)
+ (void)refresh_request(turnc, 0, true, NULL, NULL);
+
+ tmr_cancel(&turnc->tmr);
+ mem_deref(turnc->ct);
+
+ hash_flush(turnc->perms);
+ mem_deref(turnc->perms);
+ mem_deref(turnc->chans);
+ mem_deref(turnc->username);
+ mem_deref(turnc->password);
+ mem_deref(turnc->nonce);
+ mem_deref(turnc->realm);
+ mem_deref(turnc->stun);
+ mem_deref(turnc->uh);
+ mem_deref(turnc->sock);
+}
+
+
+static void timeout(void *arg)
+{
+ struct turnc *turnc = arg;
+ int err;
+
+ err = refresh_request(turnc, turnc->lifetime, true,
+ refresh_resp_handler, turnc);
+ if (err)
+ turnc->th(err, 0, NULL, NULL, NULL, NULL, turnc->arg);
+}
+
+
+static void refresh_timer(struct turnc *turnc)
+{
+ const uint32_t t = turnc->lifetime*1000*3/4;
+
+ DEBUG_INFO("Start refresh timer.. %u seconds\n", t/1000);
+
+ tmr_start(&turnc->tmr, t, timeout, turnc);
+}
+
+
+static void allocate_resp_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct stun_attr *map = NULL, *rel = NULL, *ltm, *alt;
+ struct turnc *turnc = arg;
+
+ if (err || turnc_request_loops(&turnc->ls, scode))
+ goto out;
+
+ switch (scode) {
+
+ case 0:
+ map = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ rel = stun_msg_attr(msg, STUN_ATTR_XOR_RELAY_ADDR);
+ ltm = stun_msg_attr(msg, STUN_ATTR_LIFETIME);
+ if (!rel || !map) {
+ DEBUG_WARNING("xor_mapped/relay addr attr missing\n");
+ err = EPROTO;
+ break;
+ }
+
+ if (ltm)
+ turnc->lifetime = ltm->v.lifetime;
+
+ turnc->allocated = true;
+ refresh_timer(turnc);
+ break;
+
+ case 300:
+ if (turnc->proto == IPPROTO_TCP ||
+ turnc->proto == STUN_TRANSP_DTLS)
+ break;
+
+ alt = stun_msg_attr(msg, STUN_ATTR_ALT_SERVER);
+ if (!alt)
+ break;
+
+ turnc->psrv = turnc->srv;
+ turnc->srv = alt->v.alt_server;
+
+ err = allocate_request(turnc);
+ if (err)
+ break;
+
+ return;
+
+ case 401:
+ case 438:
+ err = turnc_keygen(turnc, msg);
+ if (err)
+ break;
+
+ err = allocate_request(turnc);
+ if (err)
+ break;
+
+ return;
+
+ default:
+ break;
+ }
+
+ out:
+ turnc->th(err, scode, reason,
+ rel ? &rel->v.xor_relay_addr : NULL,
+ map ? &map->v.xor_mapped_addr : NULL,
+ msg,
+ turnc->arg);
+}
+
+
+static int allocate_request(struct turnc *t)
+{
+ const uint8_t proto = IPPROTO_UDP;
+
+ return stun_request(&t->ct, t->stun, t->proto, t->sock, &t->srv, 0,
+ STUN_METHOD_ALLOCATE,
+ t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash),
+ false, allocate_resp_handler, t, 6,
+ STUN_ATTR_LIFETIME, &t->lifetime,
+ STUN_ATTR_REQ_TRANSPORT, &proto,
+ STUN_ATTR_USERNAME, t->realm ? t->username : NULL,
+ STUN_ATTR_REALM, t->realm,
+ STUN_ATTR_NONCE, t->nonce,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+static void refresh_resp_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct turnc *turnc = arg;
+ struct stun_attr *ltm;
+
+ if (err || turnc_request_loops(&turnc->ls, scode))
+ goto out;
+
+ switch (scode) {
+
+ case 0:
+ ltm = stun_msg_attr(msg, STUN_ATTR_LIFETIME);
+ if (ltm)
+ turnc->lifetime = ltm->v.lifetime;
+ refresh_timer(turnc);
+ return;
+
+ case 401:
+ case 438:
+ err = turnc_keygen(turnc, msg);
+ if (err)
+ break;
+
+ err = refresh_request(turnc, turnc->lifetime, false,
+ refresh_resp_handler, turnc);
+ if (err)
+ break;
+
+ return;
+
+ default:
+ break;
+ }
+
+ out:
+ turnc->th(err, scode, reason, NULL, NULL, msg, turnc->arg);
+}
+
+
+static int refresh_request(struct turnc *t, uint32_t lifetime, bool reset_ls,
+ stun_resp_h *resph, void *arg)
+{
+ if (!t)
+ return EINVAL;
+
+ if (reset_ls)
+ turnc_loopstate_reset(&t->ls);
+
+ if (t->ct)
+ t->ct = mem_deref(t->ct);
+
+ return stun_request(&t->ct, t->stun, t->proto, t->sock, &t->srv, 0,
+ STUN_METHOD_REFRESH,
+ t->realm ? t->md5_hash : NULL, sizeof(t->md5_hash),
+ false, resph, arg, 5,
+ STUN_ATTR_LIFETIME, &lifetime,
+ STUN_ATTR_USERNAME, t->realm ? t->username : NULL,
+ STUN_ATTR_REALM, t->realm,
+ STUN_ATTR_NONCE, t->nonce,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+static inline size_t stun_indlen(const struct sa *sa)
+{
+ size_t len = STUN_HEADER_SIZE + STUN_ATTR_HEADER_SIZE * 2;
+
+ switch (sa_af(sa)) {
+
+ case AF_INET:
+ len += STUN_ATTR_ADDR4_SIZE;
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ len += STUN_ATTR_ADDR6_SIZE;
+ break;
+#endif
+ }
+
+ return len;
+}
+
+
+static bool udp_send_handler(int *err, struct sa *dst, struct mbuf *mb,
+ void *arg)
+{
+ struct turnc *turnc = arg;
+ size_t pos, indlen;
+ struct chan *chan;
+
+ if (mb->pos < CHAN_HDR_SIZE)
+ return false;
+
+ chan = turnc_chan_find_peer(turnc, dst);
+ if (chan) {
+ struct chan_hdr hdr;
+
+ hdr.nr = turnc_chan_numb(chan);
+ hdr.len = mbuf_get_left(mb);
+
+ mb->pos -= CHAN_HDR_SIZE;
+ *err = turnc_chan_hdr_encode(&hdr, mb);
+ mb->pos -= CHAN_HDR_SIZE;
+
+ *dst = turnc->srv;
+
+ return false;
+ }
+
+ indlen = stun_indlen(dst);
+
+ if (mb->pos < indlen)
+ return false;
+
+ mb->pos -= indlen;
+ pos = mb->pos;
+ *err = stun_msg_encode(mb, STUN_METHOD_SEND, STUN_CLASS_INDICATION,
+ sendind_tid, NULL, NULL, 0, false, 0x00, 2,
+ STUN_ATTR_XOR_PEER_ADDR, dst,
+ STUN_ATTR_DATA, mb);
+ mb->pos = pos;
+
+ *dst = turnc->srv;
+
+ return false;
+}
+
+
+static bool udp_recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct stun_attr *peer, *data;
+ struct stun_unknown_attr ua;
+ struct turnc *turnc = arg;
+ struct stun_msg *msg;
+ bool hdld = true;
+
+ if (!sa_cmp(&turnc->srv, src, SA_ALL) &&
+ !sa_cmp(&turnc->psrv, src, SA_ALL))
+ return false;
+
+ if (stun_msg_decode(&msg, mb, &ua)) {
+
+ struct chan_hdr hdr;
+ struct chan *chan;
+
+ if (turnc_chan_hdr_decode(&hdr, mb))
+ return true;
+
+ if (mbuf_get_left(mb) < hdr.len)
+ return true;
+
+ chan = turnc_chan_find_numb(turnc, hdr.nr);
+ if (!chan)
+ return true;
+
+ *src = *turnc_chan_peer(chan);
+
+ return false;
+ }
+
+ switch (stun_msg_class(msg)) {
+
+ case STUN_CLASS_INDICATION:
+ if (ua.typec > 0)
+ break;
+
+ if (stun_msg_method(msg) != STUN_METHOD_DATA)
+ break;
+
+ peer = stun_msg_attr(msg, STUN_ATTR_XOR_PEER_ADDR);
+ data = stun_msg_attr(msg, STUN_ATTR_DATA);
+ if (!peer || !data)
+ break;
+
+ *src = peer->v.xor_peer_addr;
+
+ mb->pos = data->v.data.pos;
+ mb->end = data->v.data.end;
+
+ hdld = false;
+ break;
+
+ case STUN_CLASS_ERROR_RESP:
+ case STUN_CLASS_SUCCESS_RESP:
+ (void)stun_ctrans_recv(turnc->stun, msg, &ua);
+ break;
+
+ default:
+ break;
+ }
+
+ mem_deref(msg);
+
+ return hdld;
+}
+
+
+/**
+ * Allocate a TURN Client
+ *
+ * @param turncp Pointer to allocated TURN Client
+ * @param conf Optional STUN Configuration
+ * @param proto Transport Protocol
+ * @param sock Transport socket
+ * @param layer Transport layer
+ * @param srv TURN Server IP-address
+ * @param username Authentication username
+ * @param password Authentication password
+ * @param lifetime Allocate lifetime in [seconds]
+ * @param th TURN handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int turnc_alloc(struct turnc **turncp, const struct stun_conf *conf, int proto,
+ void *sock, int layer, const struct sa *srv,
+ const char *username, const char *password,
+ uint32_t lifetime, turnc_h *th, void *arg)
+{
+ struct turnc *turnc;
+ int err;
+
+ if (!turncp || !sock || !srv || !username || !password || !th)
+ return EINVAL;
+
+ turnc = mem_zalloc(sizeof(*turnc), destructor);
+ if (!turnc)
+ return ENOMEM;
+
+ err = stun_alloc(&turnc->stun, conf, NULL, NULL);
+ if (err)
+ goto out;
+
+ err = str_dup(&turnc->username, username);
+ if (err)
+ goto out;
+
+ err = str_dup(&turnc->password, password);
+ if (err)
+ goto out;
+
+ err = turnc_perm_hash_alloc(&turnc->perms, PERM_HASH_SIZE);
+ if (err)
+ goto out;
+
+ err = turnc_chan_hash_alloc(&turnc->chans, CHAN_HASH_SIZE);
+ if (err)
+ goto out;
+
+ tmr_init(&turnc->tmr);
+ turnc->proto = proto;
+ turnc->sock = mem_ref(sock);
+ turnc->psrv = *srv;
+ turnc->srv = *srv;
+ turnc->lifetime = lifetime;
+ turnc->th = th;
+ turnc->arg = arg;
+
+ switch (proto) {
+
+ case IPPROTO_UDP:
+ err = udp_register_helper(&turnc->uh, sock, layer,
+ udp_send_handler, udp_recv_handler,
+ turnc);
+ break;
+
+ default:
+ err = 0;
+ break;
+ }
+
+ if (err)
+ goto out;
+
+ err = allocate_request(turnc);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(turnc);
+ else
+ *turncp = turnc;
+
+ return err;
+}
+
+
+int turnc_send(struct turnc *turnc, const struct sa *dst, struct mbuf *mb)
+{
+ size_t pos, indlen;
+ struct chan *chan;
+ int err;
+
+ if (!turnc || !dst || !mb)
+ return EINVAL;
+
+ chan = turnc_chan_find_peer(turnc, dst);
+ if (chan) {
+ struct chan_hdr hdr;
+
+ if (mb->pos < CHAN_HDR_SIZE)
+ return EINVAL;
+
+ hdr.nr = turnc_chan_numb(chan);
+ hdr.len = mbuf_get_left(mb);
+
+ mb->pos -= CHAN_HDR_SIZE;
+ pos = mb->pos;
+
+ err = turnc_chan_hdr_encode(&hdr, mb);
+ if (err)
+ return err;
+
+ if (turnc->proto == IPPROTO_TCP) {
+
+ mb->pos = mb->end;
+
+ /* padding */
+ while (hdr.len++ & 0x03) {
+ err = mbuf_write_u8(mb, 0x00);
+ if (err)
+ return err;
+ }
+ }
+
+ mb->pos = pos;
+ }
+ else {
+ indlen = stun_indlen(dst);
+
+ if (mb->pos < indlen)
+ return EINVAL;
+
+ mb->pos -= indlen;
+ pos = mb->pos;
+
+ err = stun_msg_encode(mb, STUN_METHOD_SEND,
+ STUN_CLASS_INDICATION, sendind_tid,
+ NULL, NULL, 0, false, 0x00, 2,
+ STUN_ATTR_XOR_PEER_ADDR, dst,
+ STUN_ATTR_DATA, mb);
+ if (err)
+ return err;
+
+ mb->pos = pos;
+ }
+
+ switch (turnc->proto) {
+
+ case IPPROTO_UDP:
+ err = udp_send(turnc->sock, &turnc->srv, mb);
+ break;
+
+ case IPPROTO_TCP:
+ err = tcp_send(turnc->sock, mb);
+ break;
+
+#ifdef USE_DTLS
+ case STUN_TRANSP_DTLS:
+ err = dtls_send(turnc->sock, mb);
+ break;
+#endif
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ return err;
+}
+
+
+int turnc_recv(struct turnc *turnc, struct sa *src, struct mbuf *mb)
+{
+ struct stun_attr *peer, *data;
+ struct stun_unknown_attr ua;
+ struct stun_msg *msg;
+ int err = 0;
+
+ if (!turnc || !src || !mb)
+ return EINVAL;
+
+ if (stun_msg_decode(&msg, mb, &ua)) {
+
+ struct chan_hdr hdr;
+ struct chan *chan;
+
+ if (turnc_chan_hdr_decode(&hdr, mb))
+ return EBADMSG;
+
+ if (mbuf_get_left(mb) < hdr.len)
+ return EBADMSG;
+
+ chan = turnc_chan_find_numb(turnc, hdr.nr);
+ if (!chan)
+ return EBADMSG;
+
+ *src = *turnc_chan_peer(chan);
+
+ return 0;
+ }
+
+ switch (stun_msg_class(msg)) {
+
+ case STUN_CLASS_INDICATION:
+ if (ua.typec > 0) {
+ err = ENOSYS;
+ break;
+ }
+
+ if (stun_msg_method(msg) != STUN_METHOD_DATA) {
+ err = ENOSYS;
+ break;
+ }
+
+ peer = stun_msg_attr(msg, STUN_ATTR_XOR_PEER_ADDR);
+ data = stun_msg_attr(msg, STUN_ATTR_DATA);
+ if (!peer || !data) {
+ err = EPROTO;
+ break;
+ }
+
+ *src = peer->v.xor_peer_addr;
+
+ mb->pos = data->v.data.pos;
+ mb->end = data->v.data.end;
+ break;
+
+ case STUN_CLASS_ERROR_RESP:
+ case STUN_CLASS_SUCCESS_RESP:
+ (void)stun_ctrans_recv(turnc->stun, msg, &ua);
+ mb->pos = mb->end;
+ break;
+
+ default:
+ err = ENOSYS;
+ break;
+ }
+
+ mem_deref(msg);
+
+ return err;
+}
+
+
+bool turnc_request_loops(struct loop_state *ls, uint16_t scode)
+{
+ bool loop = false;
+
+ switch (scode) {
+
+ case 0:
+ ls->failc = 0;
+ break;
+
+ default:
+ if (ls->last_scode == scode)
+ loop = true;
+ /*@fallthrough@*/
+ case 300:
+ if (++ls->failc >= FAILC_MAX)
+ loop = true;
+
+ break;
+ }
+
+ ls->last_scode = scode;
+
+ return loop;
+}
+
+
+void turnc_loopstate_reset(struct loop_state *ls)
+{
+ if (!ls)
+ return;
+
+ ls->last_scode = 0;
+ ls->failc = 0;
+}
+
+
+int turnc_keygen(struct turnc *turnc, const struct stun_msg *msg)
+{
+ struct stun_attr *realm, *nonce;
+
+ realm = stun_msg_attr(msg, STUN_ATTR_REALM);
+ nonce = stun_msg_attr(msg, STUN_ATTR_NONCE);
+ if (!realm || !nonce)
+ return EPROTO;
+
+ mem_deref(turnc->realm);
+ mem_deref(turnc->nonce);
+ turnc->realm = mem_ref(realm->v.realm);
+ turnc->nonce = mem_ref(nonce->v.nonce);
+
+ return md5_printf(turnc->md5_hash, "%s:%s:%s",
+ turnc->username, turnc->realm, turnc->password);
+}
diff --git a/src/turn/turnc.h b/src/turn/turnc.h
new file mode 100644
index 0000000..5f672ac
--- /dev/null
+++ b/src/turn/turnc.h
@@ -0,0 +1,70 @@
+/**
+ * @file turnc.h Internal TURN interface
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <time.h>
+
+
+struct loop_state {
+ uint32_t failc;
+ uint16_t last_scode;
+};
+
+struct channels;
+
+/** Defines a TURN Client */
+struct turnc {
+ struct loop_state ls; /**< Loop state */
+ struct udp_helper *uh; /**< UDP Helper for the TURN Socket */
+ struct stun_ctrans *ct; /**< Pending STUN Client Transaction */
+ char *username; /**< Authentication username */
+ char *password; /**< Authentication password */
+ struct sa psrv; /**< Previous TURN Server address */
+ struct sa srv; /**< TURN Server address */
+ void *sock; /**< Transport socket */
+ int proto; /**< Transport protocol */
+ struct stun *stun; /**< STUN Instance */
+ uint32_t lifetime; /**< Allocation lifetime in [seconds]*/
+ struct tmr tmr; /**< Allocation refresh timer */
+ turnc_h *th; /**< Turn client handler */
+ void *arg; /**< Handler argument */
+ uint8_t md5_hash[MD5_SIZE]; /**< Cached MD5-sum of credentials */
+ char *nonce; /**< Saved NONCE value from server */
+ char *realm; /**< Saved REALM value from server */
+ struct hash *perms; /**< Hash-table of permissions */
+ struct channels *chans; /**< TURN Channels */
+ bool allocated; /**< Allocation was done flag */
+};
+
+
+/* Util */
+bool turnc_request_loops(struct loop_state *ls, uint16_t scode);
+void turnc_loopstate_reset(struct loop_state *ls);
+int turnc_keygen(struct turnc *turnc, const struct stun_msg *msg);
+
+
+/* Permission */
+int turnc_perm_hash_alloc(struct hash **ht, uint32_t bsize);
+
+
+/* Channels */
+enum {
+ CHAN_HDR_SIZE = 4,
+};
+
+struct chan_hdr {
+ uint16_t nr;
+ uint16_t len;
+};
+
+struct chan;
+
+int turnc_chan_hash_alloc(struct channels **cp, uint32_t bsize);
+struct chan *turnc_chan_find_numb(const struct turnc *turnc, uint16_t nr);
+struct chan *turnc_chan_find_peer(const struct turnc *turnc,
+ const struct sa *peer);
+uint16_t turnc_chan_numb(const struct chan *chan);
+const struct sa *turnc_chan_peer(const struct chan *chan);
+int turnc_chan_hdr_encode(const struct chan_hdr *hdr, struct mbuf *mb);
+int turnc_chan_hdr_decode(struct chan_hdr *hdr, struct mbuf *mb);
diff --git a/src/udp/mcast.c b/src/udp/mcast.c
new file mode 100644
index 0000000..327257a
--- /dev/null
+++ b/src/udp/mcast.c
@@ -0,0 +1,70 @@
+/**
+ * @file mcast.c UDP Multicast
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#define _BSD_SOURCE 1
+#define _DEFAULT_SOURCE 1
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_sa.h>
+#include <re_udp.h>
+
+
+static int multicast_update(struct udp_sock *us, const struct sa *group,
+ bool join)
+{
+ struct ip_mreq mreq;
+#ifdef HAVE_INET6
+ struct ipv6_mreq mreq6;
+#endif
+ int err;
+
+ if (!us || !group)
+ return EINVAL;
+
+ switch (sa_af(group)) {
+
+ case AF_INET:
+ mreq.imr_multiaddr = group->u.in.sin_addr;
+ mreq.imr_interface.s_addr = 0;
+
+ err = udp_setsockopt(us, IPPROTO_IP,
+ join
+ ? IP_ADD_MEMBERSHIP
+ : IP_DROP_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ break;
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ mreq6.ipv6mr_multiaddr = group->u.in6.sin6_addr;
+ mreq6.ipv6mr_interface = 0;
+
+ err = udp_setsockopt(us, IPPROTO_IPV6,
+ join
+ ? IPV6_JOIN_GROUP
+ : IPV6_LEAVE_GROUP,
+ &mreq6, sizeof(mreq6));
+ break;
+#endif
+
+ default:
+ return EAFNOSUPPORT;
+ }
+
+ return err;
+}
+
+
+int udp_multicast_join(struct udp_sock *us, const struct sa *group)
+{
+ return multicast_update(us, group, true);
+}
+
+
+int udp_multicast_leave(struct udp_sock *us, const struct sa *group)
+{
+ return multicast_update(us, group, false);
+}
diff --git a/src/udp/mod.mk b/src/udp/mod.mk
new file mode 100644
index 0000000..10cff50
--- /dev/null
+++ b/src/udp/mod.mk
@@ -0,0 +1,8 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += udp/udp.c
+SRCS += udp/mcast.c
diff --git a/src/udp/udp.c b/src/udp/udp.c
new file mode 100644
index 0000000..f6312ab
--- /dev/null
+++ b/src/udp/udp.c
@@ -0,0 +1,837 @@
+/**
+ * @file udp.c User Datagram Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <stdlib.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_IO_H
+#include <io.h>
+#endif
+#if !defined(WIN32)
+#define __USE_POSIX 1 /**< Use POSIX flag */
+#define __USE_XOPEN2K 1/**< Use POSIX.1:2001 code */
+#include <netdb.h>
+#endif
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+#include <strings.h>
+#endif
+#ifdef __APPLE__
+#include "TargetConditionals.h"
+#endif
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_main.h>
+#include <re_sa.h>
+#include <re_net.h>
+#include <re_udp.h>
+
+
+#define DEBUG_MODULE "udp"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Platform independent buffer type cast */
+#ifdef WIN32
+#define BUF_CAST (char *)
+#define SOK_CAST (int)
+#define SIZ_CAST (int)
+#define close closesocket
+#else
+#define BUF_CAST
+#define SOK_CAST
+#define SIZ_CAST
+#endif
+
+
+enum {
+ UDP_RXSZ_DEFAULT = 8192
+};
+
+
+/** Defines a UDP socket */
+struct udp_sock {
+ struct list helpers; /**< List of UDP Helpers */
+ udp_recv_h *rh; /**< Receive handler */
+ udp_error_h *eh; /**< Error handler */
+ void *arg; /**< Handler argument */
+ int fd; /**< Socket file descriptor */
+ int fd6; /**< IPv6 socket file descriptor */
+ bool conn; /**< Connected socket flag */
+ size_t rxsz; /**< Maximum receive chunk size */
+ size_t rx_presz; /**< Preallocated rx buffer size */
+};
+
+/** Defines a UDP helper */
+struct udp_helper {
+ struct le le;
+ int layer;
+ udp_helper_send_h *sendh;
+ udp_helper_recv_h *recvh;
+ void *arg;
+};
+
+
+static void dummy_udp_recv_handler(const struct sa *src,
+ struct mbuf *mb, void *arg)
+{
+ (void)src;
+ (void)mb;
+ (void)arg;
+}
+
+
+static bool helper_send_handler(int *err, struct sa *dst,
+ struct mbuf *mb, void *arg)
+{
+ (void)err;
+ (void)dst;
+ (void)mb;
+ (void)arg;
+ return false;
+}
+
+
+static bool helper_recv_handler(struct sa *src,
+ struct mbuf *mb, void *arg)
+{
+ (void)src;
+ (void)mb;
+ (void)arg;
+ return false;
+}
+
+
+static void udp_destructor(void *data)
+{
+ struct udp_sock *us = data;
+
+ list_flush(&us->helpers);
+
+ if (-1 != us->fd) {
+ fd_close(us->fd);
+ (void)close(us->fd);
+ }
+
+ if (-1 != us->fd6) {
+ fd_close(us->fd6);
+ (void)close(us->fd6);
+ }
+}
+
+
+static void udp_read(struct udp_sock *us, int fd)
+{
+ struct mbuf *mb = mbuf_alloc(us->rxsz);
+ struct sa src;
+ struct le *le;
+ int err = 0;
+ ssize_t n;
+
+ if (!mb)
+ return;
+
+ src.len = sizeof(src.u);
+ n = recvfrom(fd, BUF_CAST mb->buf + us->rx_presz,
+ mb->size - us->rx_presz, 0,
+ &src.u.sa, &src.len);
+ if (n < 0) {
+ err = errno;
+
+ if (EAGAIN == err)
+ goto out;
+
+#ifdef EWOULDBLOCK
+ if (EWOULDBLOCK == err)
+ goto out;
+#endif
+
+#if TARGET_OS_IPHONE
+ if (ENOTCONN == err) {
+
+ struct udp_sock *us_new;
+ struct sa laddr;
+
+ err = udp_local_get(us, &laddr);
+ if (err)
+ goto out;
+
+ if (-1 != us->fd) {
+ fd_close(us->fd);
+ (void)close(us->fd);
+ us->fd = -1;
+ }
+
+ if (-1 != us->fd6) {
+ fd_close(us->fd6);
+ (void)close(us->fd6);
+ us->fd6 = -1;
+ }
+
+ err = udp_listen(&us_new, &laddr, NULL, NULL);
+ if (err)
+ goto out;
+
+ us->fd = us_new->fd;
+ us->fd6 = us_new->fd6;
+
+ us_new->fd = -1;
+ us_new->fd6 = -1;
+
+ mem_deref(us_new);
+
+ udp_thread_attach(us);
+
+ goto out;
+ }
+#endif
+ if (us->eh)
+ us->eh(err, us->arg);
+
+ goto out;
+ }
+
+ mb->pos = us->rx_presz;
+ mb->end = n + us->rx_presz;
+
+ (void)mbuf_resize(mb, mb->end);
+
+ /* call helpers */
+ le = us->helpers.head;
+ while (le) {
+ struct udp_helper *uh = le->data;
+ bool hdld;
+
+ le = le->next;
+
+ hdld = uh->recvh(&src, mb, uh->arg);
+ if (hdld)
+ goto out;
+ }
+
+ us->rh(&src, mb, us->arg);
+
+ out:
+ mem_deref(mb);
+}
+
+
+static void udp_read_handler(int flags, void *arg)
+{
+ struct udp_sock *us = arg;
+
+ (void)flags;
+
+ udp_read(us, us->fd);
+}
+
+
+static void udp_read_handler6(int flags, void *arg)
+{
+ struct udp_sock *us = arg;
+
+ (void)flags;
+
+ udp_read(us, us->fd6);
+}
+
+
+/**
+ * Create and listen on a UDP Socket
+ *
+ * @param usp Pointer to returned UDP Socket
+ * @param local Local network address
+ * @param rh Receive handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int udp_listen(struct udp_sock **usp, const struct sa *local,
+ udp_recv_h *rh, void *arg)
+{
+ struct addrinfo hints, *res = NULL, *r;
+ struct udp_sock *us = NULL;
+ char addr[64];
+ char serv[6] = "0";
+ int af, error, err = 0;
+
+ if (!usp)
+ return EINVAL;
+
+ us = mem_zalloc(sizeof(*us), udp_destructor);
+ if (!us)
+ return ENOMEM;
+
+ list_init(&us->helpers);
+
+ us->fd = -1;
+ us->fd6 = -1;
+
+ if (local) {
+ af = sa_af(local);
+ (void)re_snprintf(addr, sizeof(addr), "%H",
+ sa_print_addr, local);
+ (void)re_snprintf(serv, sizeof(serv), "%u", sa_port(local));
+ }
+ else {
+#ifdef HAVE_INET6
+ af = AF_UNSPEC;
+#else
+ af = AF_INET;
+#endif
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ /* set-up hints structure */
+ hints.ai_family = af;
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+
+ error = getaddrinfo(local ? addr : NULL, serv, &hints, &res);
+ if (error) {
+#ifdef WIN32
+ DEBUG_WARNING("listen: getaddrinfo: wsaerr=%d\n",
+ WSAGetLastError());
+#endif
+ DEBUG_WARNING("listen: getaddrinfo: %s:%s (%s)\n",
+ addr, serv, gai_strerror(error));
+ err = EADDRNOTAVAIL;
+ goto out;
+ }
+
+ for (r = res; r; r = r->ai_next) {
+ int fd = -1;
+
+ if (us->fd > 0)
+ continue;
+
+ DEBUG_INFO("listen: for: af=%d addr=%j\n",
+ r->ai_family, r->ai_addr);
+
+ fd = SOK_CAST socket(r->ai_family, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd < 0) {
+ err = errno;
+ continue;
+ }
+
+ err = net_sockopt_blocking_set(fd, false);
+ if (err) {
+ DEBUG_WARNING("udp listen: nonblock set: %m\n", err);
+ (void)close(fd);
+ continue;
+ }
+
+ if (bind(fd, r->ai_addr, SIZ_CAST r->ai_addrlen) < 0) {
+ err = errno;
+ DEBUG_INFO("listen: bind(): %m (%J)\n", err, local);
+ (void)close(fd);
+ continue;
+ }
+
+ /* Can we do both IPv4 and IPv6 on same socket? */
+ if (AF_INET6 == r->ai_family) {
+ struct sa sa;
+ int on = 1; /* assume v6only */
+
+#if defined (IPPROTO_IPV6) && defined (IPV6_V6ONLY)
+ socklen_t on_len = sizeof(on);
+ if (0 != getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *)&on, &on_len)) {
+ on = 1;
+ }
+#endif
+ /* Extra check for unspec addr - MAC OS X/Solaris */
+ if (0==sa_set_sa(&sa, r->ai_addr) && sa_is_any(&sa)) {
+ on = 1;
+ }
+ DEBUG_INFO("socket %d: IPV6_V6ONLY is %d\n", fd, on);
+ if (on) {
+ us->fd6 = fd;
+ continue;
+ }
+ }
+
+ /* OK */
+ us->fd = fd;
+ break;
+ }
+
+ freeaddrinfo(res);
+
+ /* We must have at least one socket */
+ if (-1 == us->fd && -1 == us->fd6) {
+ if (0 == err)
+ err = EADDRNOTAVAIL;
+ goto out;
+ }
+
+ err = udp_thread_attach(us);
+ if (err)
+ goto out;
+
+ us->rh = rh ? rh : dummy_udp_recv_handler;
+ us->arg = arg;
+ us->rxsz = UDP_RXSZ_DEFAULT;
+
+ out:
+ if (err)
+ mem_deref(us);
+ else
+ *usp = us;
+
+ return err;
+}
+
+
+/**
+ * Connect a UDP Socket to a specific peer.
+ * When connected, this UDP Socket will only receive data from that peer.
+ *
+ * @param us UDP Socket
+ * @param peer Peer network address
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int udp_connect(struct udp_sock *us, const struct sa *peer)
+{
+ int fd;
+
+ if (!us || !peer)
+ return EINVAL;
+
+ /* choose a socket */
+ if (AF_INET6 == sa_af(peer) && -1 != us->fd6)
+ fd = us->fd6;
+ else
+ fd = us->fd;
+
+ if (0 != connect(fd, &peer->u.sa, peer->len))
+ return errno;
+
+ us->conn = true;
+
+ return 0;
+}
+
+
+static int udp_send_internal(struct udp_sock *us, const struct sa *dst,
+ struct mbuf *mb, struct le *le)
+{
+ struct sa hdst;
+ int err = 0, fd;
+
+ /* choose a socket */
+ if (AF_INET6 == sa_af(dst) && -1 != us->fd6)
+ fd = us->fd6;
+ else
+ fd = us->fd;
+
+ /* call helpers in reverse order */
+ while (le) {
+ struct udp_helper *uh = le->data;
+
+ le = le->prev;
+
+ if (dst != &hdst) {
+ sa_cpy(&hdst, dst);
+ dst = &hdst;
+ }
+
+ if (uh->sendh(&err, &hdst, mb, uh->arg) || err)
+ return err;
+ }
+
+ /* Connected socket? */
+ if (us->conn) {
+ if (send(fd, BUF_CAST mb->buf + mb->pos, mb->end - mb->pos,
+ 0) < 0)
+ return errno;
+ }
+ else {
+ if (sendto(fd, BUF_CAST mb->buf + mb->pos, mb->end - mb->pos,
+ 0, &dst->u.sa, dst->len) < 0)
+ return errno;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Send a UDP Datagram to a peer
+ *
+ * @param us UDP Socket
+ * @param dst Destination network address
+ * @param mb Buffer to send
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int udp_send(struct udp_sock *us, const struct sa *dst, struct mbuf *mb)
+{
+ if (!us || !dst || !mb)
+ return EINVAL;
+
+ return udp_send_internal(us, dst, mb, us->helpers.tail);
+}
+
+
+/**
+ * Send an anonymous UDP Datagram to a peer
+ *
+ * @param dst Destination network address
+ * @param mb Buffer to send
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int udp_send_anon(const struct sa *dst, struct mbuf *mb)
+{
+ struct udp_sock *us;
+ int err;
+
+ if (!dst || !mb)
+ return EINVAL;
+
+ err = udp_listen(&us, NULL, NULL, NULL);
+ if (err)
+ return err;
+
+ err = udp_send_internal(us, dst, mb, NULL);
+ mem_deref(us);
+
+ return err;
+}
+
+
+/**
+ * Get the local network address on the UDP Socket
+ *
+ * @param us UDP Socket
+ * @param local The returned local network address
+ *
+ * @return 0 if success, otherwise errorcode
+ *
+ * @todo bug no way to specify AF
+ */
+int udp_local_get(const struct udp_sock *us, struct sa *local)
+{
+ if (!us || !local)
+ return EINVAL;
+
+ local->len = sizeof(local->u);
+
+ if (0 == getsockname(us->fd, &local->u.sa, &local->len))
+ return 0;
+
+ if (0 == getsockname(us->fd6, &local->u.sa, &local->len))
+ return 0;
+
+ return errno;
+}
+
+
+/**
+ * Set socket options on the UDP Socket
+ *
+ * @param us UDP Socket
+ * @param level Socket level
+ * @param optname Option name
+ * @param optval Option value
+ * @param optlen Option length
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int udp_setsockopt(struct udp_sock *us, int level, int optname,
+ const void *optval, uint32_t optlen)
+{
+ int err = 0;
+
+ if (!us)
+ return EINVAL;
+
+ if (-1 != us->fd) {
+ if (0 != setsockopt(us->fd, level, optname,
+ BUF_CAST optval, optlen))
+ err |= errno;
+ }
+
+ if (-1 != us->fd6) {
+ if (0 != setsockopt(us->fd6, level, optname,
+ BUF_CAST optval, optlen))
+ err |= errno;
+ }
+
+ return err;
+}
+
+
+/**
+ * Set the send/receive buffer size on a UDP Socket
+ *
+ * @param us UDP Socket
+ * @param size Buffer size in bytes
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int udp_sockbuf_set(struct udp_sock *us, int size)
+{
+ int err = 0;
+
+ if (!us)
+ return EINVAL;
+
+ err |= udp_setsockopt(us, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
+ err |= udp_setsockopt(us, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
+
+ return err;
+}
+
+
+/**
+ * Set the maximum receive chunk size on a UDP Socket
+ *
+ * @param us UDP Socket
+ * @param rxsz Maximum receive chunk size
+ */
+void udp_rxsz_set(struct udp_sock *us, size_t rxsz)
+{
+ if (!us)
+ return;
+
+ us->rxsz = rxsz;
+}
+
+
+/**
+ * Set preallocated space on receive buffer.
+ *
+ * @param us UDP Socket
+ * @param rx_presz Size of preallocate space.
+ */
+void udp_rxbuf_presz_set(struct udp_sock *us, size_t rx_presz)
+{
+ if (!us)
+ return;
+
+ us->rx_presz = rx_presz;
+}
+
+
+/**
+ * Set receive handler on a UDP Socket
+ *
+ * @param us UDP Socket
+ * @param rh Receive handler
+ * @param arg Handler argument
+ */
+void udp_handler_set(struct udp_sock *us, udp_recv_h *rh, void *arg)
+{
+ if (!us)
+ return;
+
+ us->rh = rh ? rh : dummy_udp_recv_handler;
+ us->arg = arg;
+}
+
+
+/**
+ * Set error handler on a UDP Socket
+ *
+ * @param us UDP Socket
+ * @param eh Error handler
+ */
+void udp_error_handler_set(struct udp_sock *us, udp_error_h *eh)
+{
+ if (!us)
+ return;
+
+ us->eh = eh;
+}
+
+
+/**
+ * Get the File Descriptor from a UDP Socket
+ *
+ * @param us UDP Socket
+ * @param af Address Family
+ *
+ * @return File Descriptor, or -1 for errors
+ */
+int udp_sock_fd(const struct udp_sock *us, int af)
+{
+ if (!us)
+ return -1;
+
+ switch (af) {
+
+ default:
+ case AF_INET: return us->fd;
+ case AF_INET6: return (us->fd6 != -1) ? us->fd6 : us->fd;
+ }
+}
+
+
+/**
+ * Attach the current thread to the UDP Socket
+ *
+ * @param us UDP Socket
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int udp_thread_attach(struct udp_sock *us)
+{
+ int err = 0;
+
+ if (!us)
+ return EINVAL;
+
+ if (-1 != us->fd) {
+ err = fd_listen(us->fd, FD_READ, udp_read_handler, us);
+ if (err)
+ goto out;
+ }
+
+ if (-1 != us->fd6) {
+ err = fd_listen(us->fd6, FD_READ, udp_read_handler6, us);
+ if (err)
+ goto out;
+ }
+
+ out:
+ if (err)
+ udp_thread_detach(us);
+
+ return err;
+}
+
+
+/**
+ * Detach the current thread from the UDP Socket
+ *
+ * @param us UDP Socket
+ */
+void udp_thread_detach(struct udp_sock *us)
+{
+ if (!us)
+ return;
+
+ if (-1 != us->fd)
+ fd_close(us->fd);
+
+ if (-1 != us->fd6)
+ fd_close(us->fd6);
+}
+
+
+static void helper_destructor(void *data)
+{
+ struct udp_helper *uh = data;
+
+ list_unlink(&uh->le);
+}
+
+
+static bool sort_handler(struct le *le1, struct le *le2, void *arg)
+{
+ struct udp_helper *uh1 = le1->data, *uh2 = le2->data;
+ (void)arg;
+
+ return uh1->layer <= uh2->layer;
+}
+
+
+/**
+ * Register a UDP protocol stack helper
+ *
+ * @param uhp Pointer to allocated UDP helper object
+ * @param us UDP socket
+ * @param layer Layer number; higher number means higher up in stack
+ * @param sh Send handler
+ * @param rh Receive handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int udp_register_helper(struct udp_helper **uhp, struct udp_sock *us,
+ int layer,
+ udp_helper_send_h *sh, udp_helper_recv_h *rh,
+ void *arg)
+{
+ struct udp_helper *uh;
+
+ if (!us)
+ return EINVAL;
+
+ uh = mem_zalloc(sizeof(*uh), helper_destructor);
+ if (!uh)
+ return ENOMEM;
+
+ list_append(&us->helpers, &uh->le, uh);
+
+ uh->layer = layer;
+ uh->sendh = sh ? sh : helper_send_handler;
+ uh->recvh = rh ? rh : helper_recv_handler;
+ uh->arg = arg;
+
+ list_sort(&us->helpers, sort_handler, NULL);
+
+ if (uhp)
+ *uhp = uh;
+
+ return 0;
+}
+
+
+/**
+ * Send a UDP Datagram to a remote peer bypassing this helper and
+ * the helpers above it.
+ *
+ * @param us UDP Socket
+ * @param dst Destination network address
+ * @param mb Buffer to send
+ * @param uh UDP Helper
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int udp_send_helper(struct udp_sock *us, const struct sa *dst,
+ struct mbuf *mb, struct udp_helper *uh)
+{
+ if (!us || !dst || !mb || !uh)
+ return EINVAL;
+
+ return udp_send_internal(us, dst, mb, uh->le.prev);
+}
+
+
+/**
+ * Find a UDP-helper on a UDP socket
+ *
+ * @param us UDP socket
+ * @param layer Layer number
+ *
+ * @return UDP-helper if found, NULL if not found
+ */
+struct udp_helper *udp_helper_find(const struct udp_sock *us, int layer)
+{
+ struct le *le;
+
+ if (!us)
+ return NULL;
+
+ for (le = us->helpers.head; le; le = le->next) {
+
+ struct udp_helper *uh = le->data;
+
+ if (layer == uh->layer)
+ return uh;
+ }
+
+ return NULL;
+}
diff --git a/src/uri/mod.mk b/src/uri/mod.mk
new file mode 100644
index 0000000..4acb507
--- /dev/null
+++ b/src/uri/mod.mk
@@ -0,0 +1,9 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += uri/uri.c
+SRCS += uri/ucmp.c
+SRCS += uri/uric.c
diff --git a/src/uri/ucmp.c b/src/uri/ucmp.c
new file mode 100644
index 0000000..7112212
--- /dev/null
+++ b/src/uri/ucmp.c
@@ -0,0 +1,110 @@
+/**
+ * @file ucmp.c URI comparison
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+
+
+static int param_handler(const struct pl *pname, const struct pl *pvalue,
+ void *arg)
+{
+ struct pl *other_params = arg;
+ struct pl other_pvalue = PL_INIT;
+ bool both;
+
+ if (0 == pl_strcmp(pname, "user"))
+ both = true;
+ else if (0 == pl_strcmp(pname, "ttl"))
+ both = true;
+ else if (0 == pl_strcmp(pname, "method"))
+ both = true;
+ else if (0 == pl_strcmp(pname, "maddr"))
+ both = true;
+ else if (0 == pl_strcmp(pname, "transport"))
+ both = true;
+ else
+ both = false;
+
+ if (uri_param_get(other_params, pname, &other_pvalue))
+ return both ? ENOENT : 0;
+
+ return pl_casecmp(pvalue, &other_pvalue);
+}
+
+
+static int header_handler(const struct pl *hname, const struct pl *hvalue,
+ void *arg)
+{
+ struct pl *other_headers = arg;
+ struct pl other_hvalue;
+ int err;
+
+ err = uri_header_get(other_headers, hname, &other_hvalue);
+ if (err)
+ return err;
+
+ return pl_casecmp(hvalue, &other_hvalue);
+}
+
+
+/**
+ * Compare two URIs - see RFC 3261 Section 19.1.4
+ *
+ * @param l Left-hand URI object
+ * @param r Right-hand URI object
+ *
+ * @return true if match, otherwise false
+ */
+bool uri_cmp(const struct uri *l, const struct uri *r)
+{
+ int err;
+
+ if (!l || !r)
+ return false;
+
+ if (l == r)
+ return true;
+
+ /* A SIP and SIPS URI are never equivalent. */
+ if (pl_casecmp(&l->scheme, &r->scheme))
+ return false;
+
+ /* Comparison of the userinfo of SIP and SIPS URIs is case-sensitive */
+ if (pl_cmp(&l->user, &r->user))
+ return false;
+
+ if (pl_cmp(&l->password, &r->password))
+ return false;
+
+ if (pl_casecmp(&l->host, &r->host))
+ return false;
+ if (l->af != r->af)
+ return false;
+
+ if (l->port != r->port)
+ return false;
+
+ /* URI parameters */
+ err = uri_params_apply(&l->params, param_handler, (void *)&r->params);
+ if (err)
+ return false;
+ err = uri_params_apply(&r->params, param_handler, (void *)&l->params);
+ if (err)
+ return false;
+
+ /* URI headers */
+ err = uri_headers_apply(&l->headers, header_handler,
+ (void *)&r->headers);
+ if (err)
+ return false;
+ err = uri_headers_apply(&r->headers, header_handler,
+ (void *)&l->headers);
+ if (err)
+ return false;
+
+ /* Match */
+ return true;
+}
diff --git a/src/uri/uri.c b/src/uri/uri.c
new file mode 100644
index 0000000..5409463
--- /dev/null
+++ b/src/uri/uri.c
@@ -0,0 +1,270 @@
+/**
+ * @file uri.c Uniform Resource Identifier (URI) module
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_uri.h>
+
+
+/**
+ * Encode a URI object
+ *
+ * @param pf Print function to encode into
+ * @param uri URI object
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uri_encode(struct re_printf *pf, const struct uri *uri)
+{
+ int err;
+
+ if (!uri)
+ return 0;
+
+ if (!pl_isset(&uri->scheme) || !pl_isset(&uri->host))
+ return EINVAL;
+
+ err = re_hprintf(pf, "%r:", &uri->scheme);
+ if (err)
+ return err;
+
+ if (pl_isset(&uri->user)) {
+ err = re_hprintf(pf, "%r", &uri->user);
+
+ if (pl_isset(&uri->password))
+ err |= re_hprintf(pf, ":%r", &uri->password);
+
+ err |= pf->vph("@", 1, pf->arg);
+
+ if (err)
+ return err;
+ }
+
+ /* The IPv6 address is delimited by '[' and ']' */
+ switch (uri->af) {
+
+#ifdef HAVE_INET6
+ case AF_INET6:
+ err = re_hprintf(pf, "[%r]", &uri->host);
+ break;
+#endif
+
+ default:
+ err = re_hprintf(pf, "%r", &uri->host);
+ break;
+ }
+ if (err)
+ return err;
+
+ if (uri->port)
+ err = re_hprintf(pf, ":%u", uri->port);
+
+ err |= re_hprintf(pf, "%r%r", &uri->params, &uri->headers);
+
+ return err;
+}
+
+
+/**
+ * Decode host-port portion of a URI (if present)
+ *
+ * @param hostport Host and port input string
+ * @param host Decoded host portion
+ * @param port Decoded port portion
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uri_decode_hostport(const struct pl *hostport, struct pl *host,
+ struct pl *port)
+{
+ if (!hostport || !host || !port)
+ return EINVAL;
+
+ /* Try IPv6 first */
+ if (!re_regex(hostport->p, hostport->l, "\\[[0-9a-f:]+\\][:]*[0-9]*",
+ host, NULL, port))
+ return 0;
+
+ /* Then non-IPv6 host */
+ return re_regex(hostport->p, hostport->l, "[^:]+[:]*[0-9]*",
+ host, NULL, port);
+}
+
+
+/**
+ * Decode a pointer-length object into a URI object
+ *
+ * @param uri URI object
+ * @param pl Pointer-length object to decode from
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uri_decode(struct uri *uri, const struct pl *pl)
+{
+ struct sa addr;
+ struct pl port = PL_INIT;
+ struct pl hostport;
+ int err;
+
+ if (!uri || !pl)
+ return EINVAL;
+
+ memset(uri, 0, sizeof(*uri));
+ if (0 == re_regex(pl->p, pl->l,
+ "[^:]+:[^@:]*[:]*[^@]*@[^;? ]+[^?]*[^]*",
+ &uri->scheme, &uri->user, NULL, &uri->password,
+ &hostport, &uri->params, &uri->headers)) {
+
+ if (0 == uri_decode_hostport(&hostport, &uri->host, &port))
+ goto out;
+ }
+
+ memset(uri, 0, sizeof(*uri));
+ err = re_regex(pl->p, pl->l, "[^:]+:[^;? ]+[^?]*[^]*",
+ &uri->scheme, &hostport, &uri->params, &uri->headers);
+ if (0 == err) {
+ err = uri_decode_hostport(&hostport, &uri->host, &port);
+ if (0 == err)
+ goto out;
+ }
+
+ return err;
+
+ out:
+ /* Cache host address family */
+ if (0 == sa_set(&addr, &uri->host, 0))
+ uri->af = sa_af(&addr);
+ else
+ uri->af = AF_UNSPEC;
+
+ if (pl_isset(&port))
+ uri->port = (uint16_t)pl_u32(&port);
+
+ return 0;
+}
+
+
+/**
+ * Get a URI parameter and possibly the value of it
+ *
+ * @param pl Pointer-length string containing parameters
+ * @param pname URI Parameter name
+ * @param pvalue Returned URI Parameter value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uri_param_get(const struct pl *pl, const struct pl *pname,
+ struct pl *pvalue)
+{
+ char expr[128];
+
+ if (!pl || !pname || !pvalue)
+ return EINVAL;
+
+ (void)re_snprintf(expr, sizeof(expr), ";%r[=]*[^;]*", pname);
+
+ return re_regex(pl->p, pl->l, expr, NULL, pvalue);
+}
+
+
+/**
+ * Call the apply handler for each URI Parameter
+ *
+ * @param pl Pointer-length string containing parameters
+ * @param ah Apply handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode (returned from handler)
+ */
+int uri_params_apply(const struct pl *pl, uri_apply_h *ah, void *arg)
+{
+ struct pl plr, pname, eq, pvalue;
+ int err = 0;
+
+ if (!pl || !ah)
+ return EINVAL;
+
+ plr = *pl;
+
+ while (plr.l > 0) {
+
+ err = re_regex(plr.p, plr.l, ";[^;=]+[=]*[^;]*",
+ &pname, &eq, &pvalue);
+ if (err)
+ break;
+
+ pl_advance(&plr, 1 + pname.l + eq.l + pvalue.l);
+
+ err = ah(&pname, &pvalue, arg);
+ if (err)
+ break;
+ }
+
+ return err;
+}
+
+
+/**
+ * Get a URI header and possibly the value of it
+ *
+ * @param pl Pointer-length string containing URI Headers
+ * @param hname URI Header name
+ * @param hvalue Returned URI Header value
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uri_header_get(const struct pl *pl, const struct pl *hname,
+ struct pl *hvalue)
+{
+ char expr[128];
+
+ if (!pl || !hname || !hvalue)
+ return EINVAL;
+
+ (void)re_snprintf(expr, sizeof(expr), "[?&]1%r=[^&]+", hname);
+
+ return re_regex(pl->p, pl->l, expr, NULL, hvalue);
+}
+
+
+/**
+ * Call the apply handler for each URI Header
+ *
+ * @param pl Pointer-length string containing URI Headers
+ * @param ah Apply handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, otherwise errorcode (returned from handler)
+ */
+int uri_headers_apply(const struct pl *pl, uri_apply_h *ah, void *arg)
+{
+ struct pl plr, sep, hname, hvalue;
+ int err = 0;
+
+ if (!pl || !ah)
+ return EINVAL;
+
+ plr = *pl;
+
+ while (plr.l > 0) {
+
+ err = re_regex(plr.p, plr.l, "[?&]1[^=]+=[^&]+",
+ &sep, &hname, &hvalue);
+ if (err)
+ break;
+
+ pl_advance(&plr, sep.l + hname.l + 1 + hvalue.l);
+
+ err = ah(&hname, &hvalue, arg);
+ if (err)
+ break;
+ }
+
+ return err;
+}
diff --git a/src/uri/uric.c b/src/uri/uric.c
new file mode 100644
index 0000000..1d97dfe
--- /dev/null
+++ b/src/uri/uric.c
@@ -0,0 +1,306 @@
+/**
+ * @file uric.c URI component escaping/unescaping
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <ctype.h>
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_uri.h>
+
+
+#define DEBUG_MODULE "uric"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/** Defines the URI escape handler */
+typedef bool (esc_h)(char c);
+
+
+static bool is_mark(int c)
+{
+ switch (c) {
+
+ case '-':
+ case '_':
+ case '.':
+ case '!':
+ case '~':
+ case '*':
+ case '\'':
+ case '(':
+ case ')':
+ return true;
+ default:
+ return false;
+ }
+}
+
+
+static bool is_unreserved(char c)
+{
+ return isalnum(c) || is_mark(c);
+}
+
+
+static bool is_user_unreserved(int c)
+{
+ switch (c) {
+
+ case '&':
+ case '=':
+ case '+':
+ case '$':
+ case ',':
+ case ';':
+ case '?':
+ case '/':
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool is_hnv_unreserved(char c)
+{
+ switch (c) {
+
+ case '[':
+ case ']':
+ case '/':
+ case '?':
+ case ':':
+ case '+':
+ case '$':
+ return true;
+ default:
+ return false;
+ }
+}
+
+
+static bool is_user(char c)
+{
+ return is_unreserved(c) || is_user_unreserved(c);
+}
+
+
+static bool is_password(char c)
+{
+ switch (c) {
+
+ case '&':
+ case '=':
+ case '+':
+ case '$':
+ case ',':
+ return true;
+ default:
+ return is_unreserved(c);
+ }
+}
+
+
+static bool is_param_unreserved(char c)
+{
+ switch (c) {
+
+ case '[':
+ case ']':
+ case '/':
+ case ':':
+ case '&':
+ case '+':
+ case '$':
+ return true;
+ default:
+ return false;
+ }
+}
+
+
+static bool is_paramchar(char c)
+{
+ return is_param_unreserved(c) || is_unreserved(c);
+}
+
+
+static bool is_hvalue(char c)
+{
+ return is_hnv_unreserved(c) || is_unreserved(c);
+}
+
+
+static int comp_escape(struct re_printf *pf, const struct pl *pl, esc_h *eh)
+{
+ size_t i;
+ int err = 0;
+
+ if (!pf || !pl || !eh)
+ return EINVAL;
+
+ for (i=0; i<pl->l && !err; i++) {
+ const char c = pl->p[i];
+
+ if (eh(c)) {
+ err = pf->vph(&c, 1, pf->arg);
+ }
+ else {
+ err = re_hprintf(pf, "%%%02X", c);
+ }
+ }
+
+ return err;
+}
+
+
+static int comp_unescape(struct re_printf *pf, const struct pl *pl, esc_h *eh)
+{
+ size_t i;
+ int err = 0;
+
+ if (!pf || !pl || !eh)
+ return EINVAL;
+
+ for (i=0; i<pl->l && !err; i++) {
+ const char c = pl->p[i];
+
+ if (eh(c)) {
+ err = pf->vph(&c, 1, pf->arg);
+ continue;
+ }
+
+ if ('%' == c) {
+ if (i+2 < pl->l) {
+ const uint8_t hi = ch_hex(pl->p[++i]);
+ const uint8_t lo = ch_hex(pl->p[++i]);
+ const char b = hi<<4 | lo;
+ err = pf->vph(&b, 1, pf->arg);
+ }
+ else {
+ DEBUG_WARNING("unescape: short uri (%u)\n", i);
+ return EBADMSG;
+ }
+ }
+ else {
+ DEBUG_WARNING("unescape: illegal '%c' in %r\n",
+ c, pl);
+ return EINVAL;
+ }
+ }
+
+ return err;
+}
+
+
+/**
+ * Escape a URI user component
+ *
+ * @param pf Print function
+ * @param pl String to escape
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uri_user_escape(struct re_printf *pf, const struct pl *pl)
+{
+ return comp_escape(pf, pl, is_user);
+}
+
+
+/**
+ * Unescape a URI user component
+ *
+ * @param pf Print function
+ * @param pl String to unescape
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uri_user_unescape(struct re_printf *pf, const struct pl *pl)
+{
+ return comp_unescape(pf, pl, is_user);
+}
+
+
+/**
+ * Escape a URI password component
+ *
+ * @param pf Print function
+ * @param pl String to escape
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uri_password_escape(struct re_printf *pf, const struct pl *pl)
+{
+ return comp_escape(pf, pl, is_password);
+}
+
+
+/**
+ * Unescape a URI password component
+ *
+ * @param pf Print function
+ * @param pl String to unescape
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uri_password_unescape(struct re_printf *pf, const struct pl *pl)
+{
+ return comp_unescape(pf, pl, is_password);
+}
+
+
+/**
+ * Escape one URI Parameter value
+ *
+ * @param pf Print function
+ * @param pl String to escape
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uri_param_escape(struct re_printf *pf, const struct pl *pl)
+{
+ return comp_escape(pf, pl, is_paramchar);
+}
+
+
+/**
+ * Unescape one URI Parameter value
+ *
+ * @param pf Print function
+ * @param pl String to unescape
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uri_param_unescape(struct re_printf *pf, const struct pl *pl)
+{
+ return comp_unescape(pf, pl, is_paramchar);
+}
+
+
+/**
+ * Escape one URI Header name/value
+ *
+ * @param pf Print function
+ * @param pl String to escape
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uri_header_escape(struct re_printf *pf, const struct pl *pl)
+{
+ return comp_escape(pf, pl, is_hvalue);
+}
+
+
+/**
+ * Unescape one URI Header name/value
+ *
+ * @param pf Print function
+ * @param pl String to unescape
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int uri_header_unescape(struct re_printf *pf, const struct pl *pl)
+{
+ return comp_unescape(pf, pl, is_hvalue);
+}
diff --git a/src/websock/mod.mk b/src/websock/mod.mk
new file mode 100644
index 0000000..d9667a7
--- /dev/null
+++ b/src/websock/mod.mk
@@ -0,0 +1,7 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += websock/websock.c
diff --git a/src/websock/websock.c b/src/websock/websock.c
new file mode 100644
index 0000000..e78147757
--- /dev/null
+++ b/src/websock/websock.c
@@ -0,0 +1,729 @@
+/**
+ * @file websock.c Implementation of The WebSocket Protocol
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_tmr.h>
+#include <re_srtp.h>
+#include <re_tcp.h>
+#include <re_tls.h>
+#include <re_msg.h>
+#include <re_http.h>
+#include <re_base64.h>
+#include <re_sha.h>
+#include <re_sys.h>
+#include <re_websock.h>
+
+
+enum {
+ TIMEOUT_CLOSE = 10000,
+ BUFSIZE_MAX = 131072,
+};
+
+enum websock_state {
+ ACCEPTING = 0,
+ CONNECTING,
+ OPEN,
+ CLOSING,
+ CLOSED,
+};
+
+struct websock {
+ websock_shutdown_h *shuth;
+ void *arg;
+ bool shutdown;
+};
+
+struct websock_conn {
+ struct tmr tmr;
+ struct sa peer;
+ char nonce[24];
+ struct websock *sock;
+ struct tcp_conn *tc;
+ struct tls_conn *sc;
+ struct mbuf *mb;
+ struct http_req *req;
+ websock_estab_h *estabh;
+ websock_recv_h *recvh;
+ websock_close_h *closeh;
+ void *arg;
+ enum websock_state state;
+ unsigned kaint;
+ bool active;
+};
+
+
+static const char magic[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
+
+static void timeout_handler(void *arg);
+
+
+static void dummy_recv_handler(const struct websock_hdr *hdr, struct mbuf *mb,
+ void *arg)
+{
+ (void)hdr;
+ (void)mb;
+ (void)arg;
+}
+
+
+static void internal_close_handler(int err, void *arg)
+{
+ struct websock_conn *conn = arg;
+ (void)err;
+
+ mem_deref(conn);
+}
+
+
+static void sock_destructor(void *arg)
+{
+ struct websock *sock = arg;
+
+ if (sock->shutdown) {
+ sock->shutdown = false;
+ mem_ref(sock);
+ if (sock->shuth)
+ sock->shuth(sock->arg);
+ return;
+ }
+}
+
+
+static void conn_destructor(void *arg)
+{
+ struct websock_conn *conn = arg;
+
+ if (conn->state == OPEN)
+ (void)websock_close(conn, WEBSOCK_GOING_AWAY, "Going Away");
+
+ if (conn->state == CLOSING) {
+
+ conn->recvh = dummy_recv_handler;
+ conn->closeh = internal_close_handler;
+ conn->arg = conn;
+
+ tmr_start(&conn->tmr, TIMEOUT_CLOSE, timeout_handler, conn);
+
+ /* important: the hack below depends on this */
+ mem_ref(conn);
+ return;
+ }
+
+ tmr_cancel(&conn->tmr);
+ mem_deref(conn->sc);
+ mem_deref(conn->tc);
+ mem_deref(conn->mb);
+ mem_deref(conn->req);
+ mem_deref(conn->sock);
+}
+
+
+static void conn_close(struct websock_conn *conn, int err)
+{
+ tmr_cancel(&conn->tmr);
+ conn->sc = mem_deref(conn->sc);
+ conn->tc = mem_deref(conn->tc);
+ conn->state = CLOSED;
+
+ conn->closeh(err, conn->arg);
+}
+
+
+static void timeout_handler(void *arg)
+{
+ struct websock_conn *conn = arg;
+
+ conn_close(conn, ETIMEDOUT);
+}
+
+
+static void keepalive_handler(void *arg)
+{
+ struct websock_conn *conn = arg;
+
+ tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn);
+
+ (void)websock_send(conn, WEBSOCK_PING, NULL);
+}
+
+
+static enum websock_scode websock_err2scode(int err)
+{
+ switch (err) {
+
+ case EOVERFLOW: return WEBSOCK_MESSAGE_TOO_BIG;
+ case EPROTO: return WEBSOCK_PROTOCOL_ERROR;
+ case EBADMSG: return WEBSOCK_PROTOCOL_ERROR;
+ default: return WEBSOCK_INTERNAL_ERROR;
+ }
+}
+
+
+static int websock_decode(struct websock_hdr *hdr, struct mbuf *mb)
+{
+ uint8_t v, *p;
+ size_t i;
+
+ if (mbuf_get_left(mb) < 2)
+ return ENODATA;
+
+ v = mbuf_read_u8(mb);
+ hdr->fin = v>>7 & 0x1;
+ hdr->rsv1 = v>>6 & 0x1;
+ hdr->rsv2 = v>>5 & 0x1;
+ hdr->rsv3 = v>>4 & 0x1;
+ hdr->opcode = v & 0x0f;
+
+ v = mbuf_read_u8(mb);
+ hdr->mask = v>>7 & 0x1;
+ hdr->len = v & 0x7f;
+
+ if (hdr->len == 126) {
+
+ if (mbuf_get_left(mb) < 2)
+ return ENODATA;
+
+ hdr->len = ntohs(mbuf_read_u16(mb));
+ }
+ else if (hdr->len == 127) {
+
+ if (mbuf_get_left(mb) < 8)
+ return ENODATA;
+
+ hdr->len = sys_ntohll(mbuf_read_u64(mb));
+ }
+
+ if (hdr->mask) {
+
+ if (mbuf_get_left(mb) < (4 + hdr->len))
+ return ENODATA;
+
+ hdr->mkey[0] = mbuf_read_u8(mb);
+ hdr->mkey[1] = mbuf_read_u8(mb);
+ hdr->mkey[2] = mbuf_read_u8(mb);
+ hdr->mkey[3] = mbuf_read_u8(mb);
+
+ for (i=0, p=mbuf_buf(mb); i<hdr->len; i++)
+ p[i] = p[i] ^ hdr->mkey[i%4];
+ }
+ else {
+ if (mbuf_get_left(mb) < hdr->len)
+ return ENODATA;
+ }
+
+ return 0;
+}
+
+
+static void recv_handler(struct mbuf *mb, void *arg)
+{
+ struct websock_conn *conn = arg;
+ int err = 0;
+
+ if (conn->mb) {
+
+ const size_t len = mbuf_get_left(mb), pos = conn->mb->pos;
+
+ if ((mbuf_get_left(conn->mb) + len) > BUFSIZE_MAX) {
+ err = EOVERFLOW;
+ goto out;
+ }
+
+ conn->mb->pos = conn->mb->end;
+
+ err = mbuf_write_mem(conn->mb, mbuf_buf(mb), len);
+ if (err)
+ goto out;
+
+ conn->mb->pos = pos;
+ }
+ else {
+ conn->mb = mem_ref(mb);
+ }
+
+ while (conn->mb) {
+
+ struct websock_hdr hdr;
+ size_t pos, end;
+
+ pos = conn->mb->pos;
+
+ err = websock_decode(&hdr, conn->mb);
+ if (err) {
+ if (err == ENODATA) {
+ conn->mb->pos = pos;
+ err = 0;
+ break;
+ }
+
+ goto out;
+ }
+
+ if (conn->active == hdr.mask) {
+ err = EPROTO;
+ goto out;
+ }
+
+ if (hdr.rsv1 || hdr.rsv2 || hdr.rsv3) {
+ err = EPROTO;
+ goto out;
+ }
+
+ mb = conn->mb;
+
+ end = mb->end;
+ mb->end = mb->pos + (size_t)hdr.len;
+
+ if (end > mb->end) {
+ struct mbuf *mbn = mbuf_alloc(end - mb->end);
+ if (!mbn) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ (void)mbuf_write_mem(mbn, mb->buf + mb->end,
+ end - mb->end);
+ mbn->pos = 0;
+
+ conn->mb = mbn;
+ }
+ else {
+ conn->mb = NULL;
+ }
+
+ switch (hdr.opcode) {
+
+ case WEBSOCK_CONT:
+ case WEBSOCK_TEXT:
+ case WEBSOCK_BIN:
+ mem_ref(conn);
+ conn->recvh(&hdr, mb, conn->arg);
+
+ if (mem_nrefs(conn) == 1) {
+
+ if (conn->state == OPEN)
+ (void)websock_close(conn,
+ WEBSOCK_GOING_AWAY,
+ "Going Away");
+
+ /*
+ * This is a hack. We enforce CLOSING
+ * state so we know the connection will
+ * continue to live.
+ */
+ conn->state = CLOSING;
+ }
+ mem_deref(conn);
+ break;
+
+ case WEBSOCK_CLOSE:
+ if (conn->state == OPEN)
+ (void)websock_send(conn, WEBSOCK_CLOSE, "%b",
+ mbuf_buf(mb), mbuf_get_left(mb));
+ conn_close(conn, 0);
+ mem_deref(mb);
+ return;
+
+ case WEBSOCK_PING:
+ (void)websock_send(conn, WEBSOCK_PONG, "%b",
+ mbuf_buf(mb), mbuf_get_left(mb));
+ break;
+
+ case WEBSOCK_PONG:
+ break;
+
+ default:
+ mem_deref(mb);
+ err = EPROTO;
+ goto out;
+ }
+
+ mem_deref(mb);
+ }
+
+ out:
+ if (err) {
+ (void)websock_close(conn, websock_err2scode(err), NULL);
+ conn_close(conn, err);
+ }
+}
+
+
+static void close_handler(int err, void *arg)
+{
+ struct websock_conn *conn = arg;
+
+ conn_close(conn, err);
+}
+
+
+static int accept_print(struct re_printf *pf, const struct pl *key)
+{
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA_CTX ctx;
+
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, key->p, key->l);
+ SHA1_Update(&ctx, magic, sizeof(magic)-1);
+ SHA1_Final(digest, &ctx);
+
+ return base64_print(pf, digest, sizeof(digest));
+}
+
+
+static void http_resp_handler(int err, const struct http_msg *msg, void *arg)
+{
+ struct websock_conn *conn = arg;
+ const struct http_hdr *hdr;
+ struct pl key;
+ char buf[32];
+
+ if (err || msg->scode != 101)
+ goto fail;
+
+ if (!http_msg_hdr_has_value(msg, HTTP_HDR_UPGRADE, "websocket"))
+ goto fail;
+
+ if (!http_msg_hdr_has_value(msg, HTTP_HDR_CONNECTION, "Upgrade"))
+ goto fail;
+
+ hdr = http_msg_hdr(msg, HTTP_HDR_SEC_WEBSOCKET_ACCEPT);
+ if (!hdr)
+ goto fail;
+
+ key.p = conn->nonce;
+ key.l = sizeof(conn->nonce);
+
+ if (re_snprintf(buf, sizeof(buf), "%H", accept_print, &key) < 0)
+ goto fail;
+
+ if (pl_strcmp(&hdr->val, buf))
+ goto fail;
+
+ /* here we are ok */
+
+ conn->state = OPEN;
+ (void)tcp_conn_peer_get(conn->tc, &conn->peer);
+
+ if (conn->kaint)
+ tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn);
+
+ conn->estabh(conn->arg);
+ return;
+
+ fail:
+ conn_close(conn, err ? err : EPROTO);
+}
+
+
+static void http_conn_handler(struct tcp_conn *tc, struct tls_conn *sc,
+ void *arg)
+{
+ struct websock_conn *conn = arg;
+
+ conn->tc = mem_ref(tc);
+ conn->sc = mem_ref(sc);
+
+ tcp_set_handlers(conn->tc, NULL, recv_handler, close_handler, conn);
+}
+
+
+int websock_connect(struct websock_conn **connp, struct websock *sock,
+ struct http_cli *cli, const char *uri, unsigned kaint,
+ websock_estab_h *estabh, websock_recv_h *recvh,
+ websock_close_h *closeh, void *arg,
+ const char *fmt, ...)
+{
+ struct websock_conn *conn;
+ uint8_t nonce[16];
+ va_list ap;
+ size_t len;
+ int err;
+
+ if (!connp || !sock || !cli || !uri || !estabh || !recvh || !closeh)
+ return EINVAL;
+
+ conn = mem_zalloc(sizeof(*conn), conn_destructor);
+ if (!conn)
+ return ENOMEM;
+
+ /* The nonce MUST be selected randomly for each connection */
+ rand_bytes(nonce, sizeof(nonce));
+
+ len = sizeof(conn->nonce);
+
+ err = base64_encode(nonce, sizeof(nonce), conn->nonce, &len);
+ if (err)
+ goto out;
+
+ conn->sock = mem_ref(sock);
+ conn->kaint = kaint;
+ conn->estabh = estabh;
+ conn->recvh = recvh;
+ conn->closeh = closeh;
+ conn->arg = arg;
+ conn->state = CONNECTING;
+ conn->active = true;
+
+ /* Protocol Handshake */
+ va_start(ap, fmt);
+ err = http_request(&conn->req, cli, "GET", uri,
+ http_resp_handler, NULL, conn,
+ "Upgrade: websocket\r\n"
+ "Connection: upgrade\r\n"
+ "Sec-WebSocket-Key: %b\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "%v"
+ "\r\n",
+ conn->nonce, sizeof(conn->nonce),
+ fmt, &ap);
+ va_end(ap);
+ if (err)
+ goto out;
+
+ http_req_set_conn_handler(conn->req, http_conn_handler);
+
+ out:
+ if (err)
+ mem_deref(conn);
+ else
+ *connp = conn;
+
+ return err;
+}
+
+
+int websock_accept(struct websock_conn **connp, struct websock *sock,
+ struct http_conn *htconn, const struct http_msg *msg,
+ unsigned kaint, websock_recv_h *recvh,
+ websock_close_h *closeh, void *arg)
+{
+ const struct http_hdr *key;
+ struct websock_conn *conn;
+ int err;
+
+ if (!connp || !sock || !htconn || !msg || !recvh || !closeh)
+ return EINVAL;
+
+ if (!http_msg_hdr_has_value(msg, HTTP_HDR_UPGRADE, "websocket"))
+ return EBADMSG;
+
+ if (!http_msg_hdr_has_value(msg, HTTP_HDR_CONNECTION, "Upgrade"))
+ return EBADMSG;
+
+ if (!http_msg_hdr_has_value(msg, HTTP_HDR_SEC_WEBSOCKET_VERSION, "13"))
+ return EBADMSG;
+
+ key = http_msg_hdr(msg, HTTP_HDR_SEC_WEBSOCKET_KEY);
+ if (!key)
+ return EBADMSG;
+
+ conn = mem_zalloc(sizeof(*conn), conn_destructor);
+ if (!conn)
+ return ENOMEM;
+
+ err = http_reply(htconn, 101, "Switching Protocols",
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Accept: %H\r\n"
+ "\r\n",
+ accept_print, &key->val);
+ if (err)
+ goto out;
+
+ sa_cpy(&conn->peer, http_conn_peer(htconn));
+ conn->sock = mem_ref(sock);
+ conn->tc = mem_ref(http_conn_tcp(htconn));
+ conn->sc = mem_ref(http_conn_tls(htconn));
+ conn->kaint = kaint;
+ conn->recvh = recvh;
+ conn->closeh = closeh;
+ conn->arg = arg;
+ conn->state = OPEN;
+ conn->active = false;
+
+ tcp_set_handlers(conn->tc, NULL, recv_handler, close_handler, conn);
+ http_conn_close(htconn);
+
+ if (conn->kaint)
+ tmr_start(&conn->tmr, conn->kaint, keepalive_handler, conn);
+
+ out:
+ if (err)
+ mem_deref(conn);
+ else
+ *connp = conn;
+
+ return err;
+}
+
+
+static int websock_encode(struct mbuf *mb, bool fin,
+ enum websock_opcode opcode, bool mask, size_t len)
+{
+ int err;
+
+ err = mbuf_write_u8(mb, (fin<<7) | (opcode & 0x0f));
+
+ if (len > 0xffff) {
+ err |= mbuf_write_u8(mb, (mask<<7) | 127);
+ err |= mbuf_write_u64(mb, sys_htonll(len));
+ }
+ else if (len > 125) {
+ err |= mbuf_write_u8(mb, (mask<<7) | 126);
+ err |= mbuf_write_u16(mb, htons(len));
+ }
+ else {
+ err |= mbuf_write_u8(mb, (mask<<7) | len);
+ }
+
+ if (mask) {
+ uint8_t mkey[4];
+ uint8_t *p;
+ size_t i;
+
+ rand_bytes(mkey, sizeof(mkey));
+
+ err |= mbuf_write_mem(mb, mkey, sizeof(mkey));
+
+ for (i=0, p=mbuf_buf(mb); i<len; i++)
+ p[i] = p[i] ^ mkey[i%4];
+ }
+
+ return err;
+}
+
+
+static int websock_vsend(struct websock_conn *conn, enum websock_opcode opcode,
+ enum websock_scode scode, const char *fmt, va_list ap)
+{
+ const size_t hsz = conn->active ? 14 : 10;
+ size_t len, start;
+ struct mbuf *mb;
+ int err = 0;
+
+ if (conn->state != OPEN)
+ return ENOTCONN;
+
+ mb = mbuf_alloc(2048);
+ if (!mb)
+ return ENOMEM;
+
+ mb->pos = hsz;
+
+ if (scode)
+ err |= mbuf_write_u16(mb, htons(scode));
+ if (fmt)
+ err |= mbuf_vprintf(mb, fmt, ap);
+ if (err)
+ goto out;
+
+ len = mb->pos - hsz;
+
+ if (len > 0xffff)
+ start = mb->pos = 0;
+ else if (len > 125)
+ start = mb->pos = 6;
+ else
+ start = mb->pos = 8;
+
+ err = websock_encode(mb, true, opcode, conn->active, len);
+ if (err)
+ goto out;
+
+ mb->pos = start;
+
+ err = tcp_send(conn->tc, mb);
+ if (err)
+ goto out;
+
+ out:
+ mem_deref(mb);
+
+ return err;
+}
+
+
+int websock_send(struct websock_conn *conn, enum websock_opcode opcode,
+ const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ if (!conn)
+ return EINVAL;
+
+ va_start(ap, fmt);
+ err = websock_vsend(conn, opcode, 0, fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+int websock_close(struct websock_conn *conn, enum websock_scode scode,
+ const char *fmt, ...)
+{
+ va_list ap;
+ int err;
+
+ if (!conn)
+ return EINVAL;
+
+ if (!scode)
+ fmt = NULL;
+
+ va_start(ap, fmt);
+ err = websock_vsend(conn, WEBSOCK_CLOSE, scode, fmt, ap);
+ va_end(ap);
+
+ if (!err)
+ conn->state = CLOSING;
+
+ return err;
+}
+
+
+const struct sa *websock_peer(const struct websock_conn *conn)
+{
+ return conn ? &conn->peer : NULL;
+}
+
+
+int websock_alloc(struct websock **sockp, websock_shutdown_h *shuth, void *arg)
+{
+ struct websock *sock;
+
+ if (!sockp)
+ return EINVAL;
+
+ sock = mem_zalloc(sizeof(*sock), sock_destructor);
+ if (!sock)
+ return ENOMEM;
+
+ sock->shuth = shuth;
+ sock->arg = arg;
+
+ *sockp = sock;
+
+ return 0;
+}
+
+
+void websock_shutdown(struct websock *sock)
+{
+ if (!sock || sock->shutdown)
+ return;
+
+ sock->shutdown = true;
+ mem_deref(sock);
+}