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
+
+
+[![Build Status](https://travis-ci.org/creytiv/re.svg?branch=master)](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, &param_realm))
+		chall->realm = *val;
+	else if (!pl_casecmp(name, &param_nonce))
+		chall->nonce = *val;
+	else if (!pl_casecmp(name, &param_opaque))
+		chall->opaque= *val;
+	else if (!pl_casecmp(name, &param_stale))
+		chall->stale = *val;
+	else if (!pl_casecmp(name, &param_algorithm))
+		chall->algorithm = *val;
+	else if (!pl_casecmp(name, &param_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, &param_realm))
+		resp->realm = *val;
+	else if (!pl_casecmp(name, &param_nonce))
+		resp->nonce = *val;
+	else if (!pl_casecmp(name, &param_response))
+		resp->response = *val;
+	else if (!pl_casecmp(name, &param_username))
+		resp->username = *val;
+	else if (!pl_casecmp(name, &param_uri))
+		resp->uri = *val;
+	else if (!pl_casecmp(name, &param_nc))
+		resp->nc = *val;
+	else if (!pl_casecmp(name, &param_cnonce))
+		resp->cnonce = *val;
+	else if (!pl_casecmp(name, &param_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] = &af;
+	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, &params))
+		return EBADMSG;
+
+	fmt = sdp_format_find(&m->rfmtl, &id);
+	if (!fmt)
+		return 0;
+
+	fmt->params = mem_deref(fmt->params);
+
+	return pl_strdup(&fmt->params, &params);
+}
+
+
+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", &param))
+		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", &param)) {
+
+		if (!pl_strcasecmp(&param, "deactivated"))
+			ss->reason = SIPEVENT_DEACTIVATED;
+		else if (!pl_strcasecmp(&param, "probation"))
+			ss->reason = SIPEVENT_PROBATION;
+		else if (!pl_strcasecmp(&param, "rejected"))
+			ss->reason = SIPEVENT_REJECTED;
+		else if (!pl_strcasecmp(&param, "timeout"))
+			ss->reason = SIPEVENT_TIMEOUT;
+		else if (!pl_strcasecmp(&param, "giveup"))
+			ss->reason = SIPEVENT_GIVEUP;
+		else if (!pl_strcasecmp(&param, "noresource"))
+			ss->reason = SIPEVENT_NORESOURCE;
+		else
+			ss->reason = -1;
+	}
+	else {
+		ss->reason = -1;
+	}
+
+	if (!msg_param_decode(&ss->params, "expires", &param))
+		ss->expires = param;
+	else
+		ss->expires = pl_null;
+
+	if (!msg_param_decode(&ss->params, "retry-after", &param))
+		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(&not->tmr);
+
+	if (!not->terminated) {
+
+		if (terminate(not, SIPEVENT_DEACTIVATED))
+			return;
+	}
+
+	hash_unlink(&not->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(&not->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(&not->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(&not->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(&not->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(&not->ls);
+
+	if (not->terminated)
+		not->termsent = true;
+
+	not->notify_pending = false;
+
+	return sip_drequestf(&not->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(&not->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(&not->event, "refer");
+		if (err)
+			goto out;
+
+		err = re_sdprintf(&not->id, "%u", msg->cseq.num);
+		if (err)
+			goto out;
+	}
+	else {
+		if (!event) {
+			err = EINVAL;
+			goto out;
+		}
+
+		err = pl_strdup(&not->event, &event->event);
+		if (err)
+			goto out;
+
+		if (pl_isset(&event->id)) {
+
+			err = pl_strdup(&not->id, &event->id);
+			if (err)
+				goto out;
+		}
+	}
+
+	if (dlg) {
+		not->dlg = mem_ref(dlg);
+	}
+	else {
+		err = sip_dialog_accept(&not->dlg, msg);
+		if (err)
+			goto out;
+	}
+
+	hash_append(sock->ht_not,
+		    hash_joaat_str(sip_dialog_callid(not->dlg)),
+		    &not->he, not);
+
+	err = sip_auth_alloc(&not->auth, authh, aarg, aref);
+	if (err)
+		goto out;
+
+	err = str_dup(&not->cuser, cuser);
+	if (err)
+		goto out;
+
+	err = str_dup(&not->ctype, ctype);
+	if (err)
+		goto out;
+
+	if (fmt) {
+		va_list ap;
+
+		va_start(ap, fmt);
+		err = re_vsdprintf(&not->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(&not->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(&reg->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(&reg->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(&reg->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(&reg->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,
+			&reg->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(&reg->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(&reg->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, &reg->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(&reg->ls);
+
+	return sip_drequestf(&reg->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(&reg->dlg, reg_uri, to_uri, from_name, from_uri,
+			       routev, routec);
+	if (err)
+		goto out;
+
+	err = sip_auth_alloc(&reg->auth, authh, aarg, aref);
+	if (err)
+		goto out;
+
+	err = str_dup(&reg->cuser, cuser);
+	if (params)
+		err |= str_dup(&reg->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 ? &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);
+}