Squashed 'third_party/rawrtc/rew/' content from commit 24c91fd83

Change-Id: Ica2fcc790472ecd5b195d20da982c4e84139cbdd
git-subtree-dir: third_party/rawrtc/rew
git-subtree-split: 24c91fd839b40b11f727c902fa46d20874da33fb
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..23f48de
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+
+build*
+librew.pc
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..4f4a140
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,19 @@
+language: c
+
+os:
+    - linux
+    - osx
+
+before_install:
+    - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq ; fi
+    - if [[ "$TRAVIS_OS_NAME" == "osx" ]];   then brew update             ; fi
+
+install:
+    - git clone https://github.com/creytiv/re.git
+    - cd re && make && sudo make install && cd ..
+    - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo ldconfig ; fi
+    - wget "https://github.com/alfredh/pytools/raw/master/ccheck.py"
+
+script: 
+    - make EXTRA_CFLAGS=-Werror
+    - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then python2 ccheck.py ; fi
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..63880e9
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,147 @@
+#
+# Makefile
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+# Master version number
+VER_MAJOR := 0
+VER_MINOR := 5
+VER_PATCH := 0
+
+PROJECT   := rew
+VERSION   := 0.5.0
+OPT_SPEED := 1
+
+LIBRE_MK  := $(shell [ -f ../re/mk/re.mk ] && \
+	echo "../re/mk/re.mk")
+ifeq ($(LIBRE_MK),)
+LIBRE_MK  := $(shell [ -f /usr/share/re/re.mk ] && \
+	echo "/usr/share/re/re.mk")
+endif
+ifeq ($(LIBRE_MK),)
+LIBRE_MK  := $(shell [ -f /usr/local/share/re/re.mk ] && \
+	echo "/usr/local/share/re/re.mk")
+endif
+
+include $(LIBRE_MK)
+
+# List of modules
+MODULES += shim
+MODULES += trice
+MODULES += pcp
+
+LIBS    += -lm
+
+INSTALL := install
+ifndef PREFIX
+ifeq ($(DESTDIR),)
+PREFIX  := /usr/local
+else
+PREFIX  := /usr
+endif
+endif
+ifeq ($(LIBDIR),)
+LIBDIR  := $(PREFIX)/lib
+endif
+INCDIR  := $(PREFIX)/include/rew
+CFLAGS	+= -I$(LIBRE_INC) -Iinclude
+
+
+MODMKS	:= $(patsubst %,src/%/mod.mk,$(MODULES))
+SHARED  := librew$(LIB_SUFFIX)
+STATIC	:= librew.a
+
+
+include $(MODMKS)
+
+
+OBJS	?= $(patsubst %.c,$(BUILD)/%.o,$(filter %.c,$(SRCS)))
+OBJS	+= $(patsubst %.S,$(BUILD)/%.o,$(filter %.S,$(SRCS)))
+
+
+all: $(SHARED) $(STATIC)
+
+
+-include $(OBJS:.o=.d)
+
+
+$(SHARED): $(OBJS)
+	@echo "  LD      $@"
+	@$(LD) $(LFLAGS) $(SH_LFLAGS) $^ -L$(LIBRE_SO) -lre $(LIBS) -o $@
+
+
+$(STATIC): $(OBJS)
+	@echo "  AR      $@"
+	@$(AR) $(AFLAGS) $@ $^
+ifneq ($(RANLIB),)
+	@$(RANLIB) $@
+endif
+
+librew.pc:
+	@echo 'prefix='$(PREFIX) > librew.pc
+	@echo 'exec_prefix=$${prefix}' >> librew.pc
+	@echo 'libdir=$${prefix}/lib' >> librew.pc
+	@echo 'includedir=$${prefix}/include/rew' >> librew.pc
+	@echo '' >> librew.pc
+	@echo 'Name: librew' >> librew.pc
+	@echo 'Description: ' >> librew.pc
+	@echo 'Version: '$(VERSION) >> librew.pc
+	@echo 'URL: https://github.com/alfredh/rew' >> librew.pc
+	@echo 'Libs: -L$${libdir} -lrew' >> librew.pc
+	@echo 'Libs.private: -L$${libdir} -lrew ${LIBS}' >> librew.pc
+	@echo 'Cflags: -I$${includedir}' >> librew.pc
+
+$(BUILD)/%.o: src/%.c $(BUILD) Makefile $(MK) $(MODMKS)
+	@echo "  CC      $@"
+	@$(CC) $(CFLAGS) -c $< -o $@ $(DFLAGS)
+
+
+$(BUILD)/%.o: src/%.S $(BUILD) Makefile $(MK) $(MODMKS)
+	@echo "  AS      $@"
+	@$(CC) $(CFLAGS) -c $< -o $@ $(DFLAGS)
+
+
+$(BUILD): Makefile $(MK) $(MODMKS)
+	@mkdir -p $(patsubst %,$(BUILD)/%,$(sort $(dir $(SRCS))))
+	@touch $@
+
+
+.PHONY: clean
+clean:
+	@rm -rf $(SHARED) $(STATIC) librew.pc test.d test.o test $(BUILD)
+
+
+install: $(SHARED) $(STATIC) librew.pc
+	@mkdir -p $(DESTDIR)$(LIBDIR) $(DESTDIR)$(LIBDIR)/pkgconfig \
+		$(DESTDIR)$(INCDIR)
+	$(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 librew.pc $(DESTDIR)$(LIBDIR)/pkgconfig
+
+install-static: $(STATIC) librew.pc
+	@mkdir -p $(DESTDIR)$(LIBDIR) $(DESTDIR)$(LIBDIR)/pkgconfig \
+		$(DESTDIR)$(INCDIR)
+	$(INSTALL) -m 0644 $(shell find include -name "*.h") \
+		$(DESTDIR)$(INCDIR)
+	$(INSTALL) -m 0755 $(STATIC) $(DESTDIR)$(LIBDIR)
+	$(INSTALL) -m 0644 librew.pc $(DESTDIR)$(LIBDIR)/pkgconfig
+
+.PHONY: uninstall
+uninstall:
+	@rm -rf $(DESTDIR)$(INCDIR)
+	@rm -f $(DESTDIR)$(LIBDIR)/$(SHARED)
+	@rm -f $(DESTDIR)$(LIBDIR)/$(STATIC)
+	@rm -f $(DESTDIR)$(LIBDIR)/pkgconfig/librew.pc
+
+-include test.d
+
+test.o:	test.c
+	@echo "  CC      $@"
+	@$(CC) $(CFLAGS) -c $< -o $@ $(DFLAGS)
+
+test$(BIN_SUFFIX): test.o $(SHARED) $(STATIC)
+	@echo "  LD      $@"
+	@$(LD) $(LFLAGS) $< -L. -lrew -lre $(LIBS) -o $@
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..888be89
--- /dev/null
+++ b/README.md
@@ -0,0 +1,38 @@
+# librew
+
+[![Build Status](https://travis-ci.org/alfredh/rew.svg?branch=master)](https://travis-ci.org/alfredh/rew)
+
+A staging library to libre with experimental code
+
+
+# Building
+
+You need to install libre first (http://www.creytiv.com/re.html)
+Then:
+
+```
+$ make
+$ sudo make install
+```
+
+
+## Modules:
+
+The following modules are implemented:
+
+### trice -- Trickle ICE
+
+- https://tools.ietf.org/html/draft-ietf-ice-trickle-02
+- https://tools.ietf.org/html/draft-ietf-ice-rfc5245bis-00
+
+### pcp -- Port Control Protocol
+
+- https://tools.ietf.org/html/rfc6887
+- https://tools.ietf.org/html/rfc7220
+
+
+# Testing
+
+The retest program has a branch for testing librew:
+
+https://github.com/creytiv/retest/tree/rew
diff --git a/docs/COPYING b/docs/COPYING
new file mode 100644
index 0000000..6c68ed7
--- /dev/null
+++ b/docs/COPYING
@@ -0,0 +1,32 @@
+Copyright (c) 2010 - 2015, Alfred E. Heggestad
+Copyright (c) 2010 - 2015, Richard Aas
+Copyright (c) 2010 - 2015, 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/include/re_pcp.h b/include/re_pcp.h
new file mode 100644
index 0000000..a98d5d4
--- /dev/null
+++ b/include/re_pcp.h
@@ -0,0 +1,200 @@
+/**
+ * @file re_pcp.h  PCP - Port Control Protocol (RFC 6887)
+ *
+ * Copyright (C) 2010 - 2014 Creytiv.com
+ */
+
+
+/*
+ * The following specifications are implemented:
+ *
+ *   RFC 6887
+ *   draft-ietf-pcp-description-option-02
+ *   draft-cheshire-pcp-unsupp-family
+ *
+ */
+
+
+/** PCP Version numbers */
+enum {
+	PCP_VERSION = 2,
+};
+
+/* PCP port numbers */
+enum {
+	PCP_PORT_CLI   = 5350,  /* for ANNOUNCE notifications */
+	PCP_PORT_SRV   = 5351,
+};
+
+/** PCP Protocol sizes */
+enum {
+	PCP_HDR_SZ   = 24,
+	PCP_NONCE_SZ = 12,
+	PCP_MAP_SZ   = 36,
+	PCP_PEER_SZ  = 56,
+
+	PCP_MIN_PACKET =   24,
+	PCP_MAX_PACKET = 1100
+};
+
+enum pcp_opcode {
+	PCP_ANNOUNCE = 0,
+	PCP_MAP      = 1,
+	PCP_PEER     = 2,
+};
+
+enum pcp_result {
+	PCP_SUCCESS                 =  0,
+	PCP_UNSUPP_VERSION          =  1,
+	PCP_NOT_AUTHORIZED          =  2,
+	PCP_MALFORMED_REQUEST       =  3,
+	PCP_UNSUPP_OPCODE           =  4,
+	PCP_UNSUPP_OPTION           =  5,
+	PCP_MALFORMED_OPTION        =  6,
+	PCP_NETWORK_FAILURE         =  7,
+	PCP_NO_RESOURCES            =  8,
+	PCP_UNSUPP_PROTOCOL         =  9,
+	PCP_USER_EX_QUOTA           = 10,
+	PCP_CANNOT_PROVIDE_EXTERNAL = 11,
+	PCP_ADDRESS_MISMATCH        = 12,
+	PCP_EXCESSIVE_REMOTE_PEERS  = 13,
+	PCP_UNSUPP_FAMILY           = 14  /*draft-cheshire-pcp-unsupp-family*/
+};
+
+enum pcp_option_code {
+	PCP_OPTION_THIRD_PARTY    =   1,
+	PCP_OPTION_PREFER_FAILURE =   2,
+	PCP_OPTION_FILTER         =   3,
+	PCP_OPTION_DESCRIPTION    = 128,  /* RFC 7220 */
+};
+
+/* forward declarations */
+struct udp_sock;
+
+/** Defines a PCP option */
+struct pcp_option {
+	struct le le;
+	enum pcp_option_code code;
+	union {
+		struct sa third_party;          /* Internal IP-address */
+		struct pcp_option_filter {
+			uint8_t prefix_length;
+			struct sa remote_peer;
+		} filter;
+		char *description;
+	} u;
+};
+
+/**
+ * Defines a complete and decoded PCP request/response.
+ *
+ * A PCP message consist of a header, and optional payload and options:
+ *
+ *     [      Header    ]
+ *     ( Opcode Payload )
+ *     (   PCP Options  )
+ *
+ */
+struct pcp_msg {
+
+	/** PCP Common Header */
+	struct pcp_hdr {
+		uint8_t version;        /**< PCP Protocol version 2        */
+		unsigned resp:1;        /**< R-bit; 0=Request, 1=Response  */
+		uint8_t opcode;         /**< A 7-bit opcode                */
+		uint32_t lifetime;      /**< Lifetime in [seconds]         */
+
+		/* request: */
+		struct sa cli_addr;     /**< Client's IP Address (SA_ADDR) */
+
+		/* response: */
+		enum pcp_result result; /**< Result code for this response */
+		uint32_t epoch;         /**< Server's Epoch Time [seconds] */
+	} hdr;
+
+	/** PCP Opcode-specific payload */
+	union pcp_payload {
+		struct pcp_map {
+			uint8_t nonce[PCP_NONCE_SZ]; /**< Mapping Nonce    */
+			uint8_t proto;               /**< IANA protocol    */
+			uint16_t int_port;           /**< Internal Port    */
+			struct sa ext_addr;          /**< External Address */
+		} map;
+		struct pcp_peer {
+			struct pcp_map map;          /**< Common with MAP  */
+			struct sa remote_addr;       /**< Remote address   */
+		} peer;
+	} pld;
+
+	/** List of PCP Options (struct pcp_option) */
+	struct list optionl;
+};
+
+/** PCP request configuration */
+struct pcp_conf {
+	uint32_t irt;  /**< Initial retransmission time [seconds]     */
+	uint32_t mrc;  /**< Maximum retransmission count              */
+	uint32_t mrt;  /**< Maximum retransmission time [seconds]     */
+	uint32_t mrd;  /**< Maximum retransmission duration [seconds] */
+};
+
+
+/* request */
+
+struct pcp_request;
+
+typedef void (pcp_resp_h)(int err, struct pcp_msg *msg, void *arg);
+
+int pcp_request(struct pcp_request **reqp, const struct pcp_conf *conf,
+		const struct sa *pcp_server, enum pcp_opcode opcode,
+		uint32_t lifetime, const void *payload,
+		pcp_resp_h *resph, void *arg, uint32_t optionc, ...);
+void pcp_force_refresh(struct pcp_request *req);
+
+
+/* reply */
+
+int pcp_reply(struct udp_sock *us, const struct sa *dst, struct mbuf *req,
+	      enum pcp_opcode opcode, enum pcp_result result,
+	      uint32_t lifetime, uint32_t epoch_time, const void *payload);
+
+
+/* msg */
+
+typedef bool (pcp_option_h)(const struct pcp_option *opt, void *arg);
+
+int pcp_msg_decode(struct pcp_msg **msgp, struct mbuf *mb);
+int pcp_msg_printhdr(struct re_printf *pf, const struct pcp_msg *msg);
+int pcp_msg_print(struct re_printf *pf, const struct pcp_msg *msg);
+struct pcp_option *pcp_msg_option(const struct pcp_msg *msg,
+				  enum pcp_option_code code);
+struct pcp_option *pcp_msg_option_apply(const struct pcp_msg *msg,
+					pcp_option_h *h, void *arg);
+const void *pcp_msg_payload(const struct pcp_msg *msg);
+
+
+/* option */
+
+int pcp_option_encode(struct mbuf *mb, enum pcp_option_code code,
+		      const void *v);
+int pcp_option_decode(struct pcp_option **optp, struct mbuf *mb);
+int pcp_option_print(struct re_printf *pf, const struct pcp_option *opt);
+
+
+/* encode */
+
+int pcp_msg_req_vencode(struct mbuf *mb, enum pcp_opcode opcode,
+			uint32_t lifetime, const struct sa *cli_addr,
+			const void *payload, uint32_t optionc, va_list ap);
+int pcp_msg_req_encode(struct mbuf *mb, enum pcp_opcode opcode,
+		       uint32_t lifetime, const struct sa *cli_addr,
+		       const void *payload, uint32_t optionc, ...);
+
+
+/* pcp */
+
+int pcp_ipaddr_encode(struct mbuf *mb, const struct sa *sa);
+int pcp_ipaddr_decode(struct mbuf *mb, struct sa *sa);
+const char *pcp_result_name(enum pcp_result result);
+const char *pcp_opcode_name(enum pcp_opcode opcode);
+const char *pcp_proto_name(int proto);
diff --git a/include/re_shim.h b/include/re_shim.h
new file mode 100644
index 0000000..f5966a6
--- /dev/null
+++ b/include/re_shim.h
@@ -0,0 +1,15 @@
+
+
+/* RFC 4571 */
+
+
+enum { SHIM_HDR_SIZE = 2 };
+
+struct shim;
+
+typedef bool (shim_frame_h)(struct mbuf *mb, void *arg);
+
+
+int shim_insert(struct shim **shimp, struct tcp_conn *tc, int layer,
+		shim_frame_h *frameh, void *arg);
+int shim_debug(struct re_printf *pf, const struct shim *shim);
diff --git a/include/re_trice.h b/include/re_trice.h
new file mode 100644
index 0000000..9eba6c7
--- /dev/null
+++ b/include/re_trice.h
@@ -0,0 +1,167 @@
+/**
+ * @file re_ice.h  Interface to Interactive Connectivity Establishment (ICE)
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+/** ICE Configuration */
+struct trice_conf {
+	enum ice_nomination nom;       /**< Nomination algorithm             */
+	bool debug;                    /**< Enable ICE debugging             */
+	bool trace;                    /**< Enable tracing of Connectivity
+					    checks                           */
+	bool ansi;                     /**< Enable ANSI colors for debug
+					   output                            */
+	bool enable_prflx;             /**< Enable Peer-Reflexive candidates */
+	bool optimize_loopback_pairing;/**< Reduce candidate pairs when
+					    using loopback addresses         */
+};
+
+struct trice;
+struct ice_lcand;
+struct ice_candpair;
+struct stun_conf;
+
+
+typedef bool (ice_cand_recv_h)(struct ice_lcand *lcand,
+			       int proto, void *sock, const struct sa *src,
+			       struct mbuf *mb, void *arg);
+
+
+/** Local candidate */
+struct ice_lcand {
+	struct ice_cand_attr attr;   /**< Base class (inheritance)           */
+	struct le le;                /**< List element                       */
+
+	/* Base-address only set for SRFLX, PRFLX, RELAY */
+	struct sa base_addr;    /* IP-address of "base" candidate (optional) */
+
+	struct udp_sock *us;
+	struct udp_helper *uh;
+	struct tcp_sock *ts;    /* TCP for simultaneous-open or passive. */
+	char ifname[32];        /**< Network interface, for diagnostics */
+	int layer;
+	ice_cand_recv_h *recvh;
+	void *arg;
+
+	// todo: remove
+	struct trice *icem;           /* parent */
+
+	struct {
+		size_t n_tx;
+		size_t n_rx;
+	} stats;
+};
+
+/** Remote candidate */
+struct ice_rcand {
+	struct ice_cand_attr attr;   /**< Base class (inheritance)           */
+	struct le le;                /**< List element                       */
+};
+
+
+/** Defines a candidate pair */
+struct ice_candpair {
+	struct le le;                /**< List element                       */
+	struct ice_lcand *lcand;     /**< Local candidate                    */
+	struct ice_rcand *rcand;     /**< Remote candidate                   */
+	enum ice_candpair_state state;/**< Candidate pair state              */
+	uint64_t pprio;              /**< Pair priority                      */
+      //bool def;                    /**< Default flag                       */
+	bool valid;                  /**< Valid flag                         */
+	bool nominated;              /**< Nominated flag                     */
+	bool estab;
+	bool trigged;
+	int err;                     /**< Saved error code, if failed        */
+	uint16_t scode;              /**< Saved STUN code, if failed         */
+
+	struct tcp_conn *tc;
+
+	struct ice_tcpconn *conn;    /* the TCP-connection used */
+};
+
+
+typedef void (trice_estab_h)(struct ice_candpair *pair,
+			     const struct stun_msg *msg, void *arg);
+
+
+typedef void (trice_failed_h)(int err, uint16_t scode,
+			    struct ice_candpair *pair, void *arg);
+
+
+int  trice_alloc(struct trice **icemp, const struct trice_conf *conf,
+		 enum ice_role role, const char *lufrag, const char *lpwd);
+int  trice_set_remote_ufrag(struct trice *icem, const char *rufrag);
+int  trice_set_remote_pwd(struct trice *icem, const char *rpwd);
+int  trice_set_software(struct trice *icem, const char *sw);
+int  trice_set_role(struct trice *trice, enum ice_role role);
+enum ice_role trice_local_role(const struct trice *icem);
+int  trice_debug(struct re_printf *pf, const struct trice *icem);
+struct trice_conf *trice_conf(struct trice *icem);
+
+
+/* Candidates (common) */
+int  trice_cand_print(struct re_printf *pf, const struct ice_cand_attr *cand);
+enum ice_tcptype   ice_tcptype_reverse(enum ice_tcptype type);
+const char        *ice_tcptype_name(enum ice_tcptype tcptype);
+enum ice_cand_type ice_cand_type_base(enum ice_cand_type type);
+
+
+/* Local candidates */
+int trice_lcand_add(struct ice_lcand **lcandp, struct trice *icem,
+		    unsigned compid, int proto, uint32_t prio,
+		    const struct sa *addr, const struct sa *base_addr,
+		    enum ice_cand_type type, const struct sa *rel_addr,
+		    enum ice_tcptype tcptype,
+		    void *sock, int layer);
+struct list      *trice_lcandl(const struct trice *icem);
+struct ice_lcand *trice_lcand_find(struct trice *icem,
+				   enum ice_cand_type type,
+				   unsigned compid, int proto,
+				   const struct sa *addr);
+struct ice_lcand *trice_lcand_find2(const struct trice *icem,
+				    enum ice_cand_type type, int af);
+void *trice_lcand_sock(struct trice *icem, const struct ice_lcand *lcand);
+void trice_lcand_recv_packet(struct ice_lcand *lcand,
+			     const struct sa *src, struct mbuf *mb);
+
+
+/* Remote candidate */
+struct list *trice_rcandl(const struct trice *icem);
+int trice_rcand_add(struct ice_rcand **rcandp, struct trice *icem,
+		    unsigned compid, const char *foundation, int proto,
+		    uint32_t prio, const struct sa *addr,
+		    enum ice_cand_type type, enum ice_tcptype tcptype);
+struct ice_rcand *trice_rcand_find(struct trice *icem, unsigned compid,
+				   int proto, const struct sa *addr);
+
+
+/* ICE Candidate pairs */
+struct list *trice_checkl(const struct trice *icem);
+struct list *trice_validl(const struct trice *icem);
+struct ice_candpair *trice_candpair_find_state(const struct list *lst,
+					   enum ice_candpair_state state);
+int  trice_candpair_debug(struct re_printf *pf, const struct ice_candpair *cp);
+int  trice_candpairs_debug(struct re_printf *pf, bool ansi_output,
+			   const struct list *list);
+
+
+/* ICE checklist */
+void trice_checklist_set_waiting(struct trice *icem);
+int  trice_checklist_start(struct trice *icem, struct stun *stun,
+			   uint32_t interval,
+			   trice_estab_h *estabh, trice_failed_h *failh,
+			   void *arg);
+void trice_checklist_stop(struct trice *icem);
+bool trice_checklist_isrunning(const struct trice *icem);
+bool trice_checklist_iscompleted(const struct trice *icem);
+
+
+/* ICE Conncheck */
+int trice_conncheck_send(struct trice *icem, struct ice_candpair *pair,
+			bool use_cand);
+
+/* Port range */
+int trice_set_port_range(struct trice *trice,
+			 uint16_t min_port, uint16_t max_port);
diff --git a/include/rew.h b/include/rew.h
new file mode 100644
index 0000000..afeeb68
--- /dev/null
+++ b/include/rew.h
@@ -0,0 +1,24 @@
+/**
+ * @file rew.h  Wrapper for librew headers
+ *
+ * Copyright (C) 2010 - 2015 Creytiv.com
+ */
+
+#ifndef REW_H__
+#define REW_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#include "re_pcp.h"
+#include "re_shim.h"
+#include "re_trice.h"
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/pcp/README b/src/pcp/README
new file mode 100644
index 0000000..620a614
--- /dev/null
+++ b/src/pcp/README
@@ -0,0 +1,13 @@
+PCP README:
+----------
+
+Port Control Protocol (PCP) as of RFC 6887
+
+
+
+
+other PCP implementations:
+
+  https://github.com/libpcp/pcp
+
+
diff --git a/src/pcp/mod.mk b/src/pcp/mod.mk
new file mode 100644
index 0000000..2a1dc95
--- /dev/null
+++ b/src/pcp/mod.mk
@@ -0,0 +1,12 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 - 2016 Creytiv.com
+#
+
+SRCS	+= pcp/msg.c
+SRCS	+= pcp/option.c
+SRCS	+= pcp/payload.c
+SRCS	+= pcp/pcp.c
+SRCS	+= pcp/reply.c
+SRCS	+= pcp/request.c
diff --git a/src/pcp/msg.c b/src/pcp/msg.c
new file mode 100644
index 0000000..327f254
--- /dev/null
+++ b/src/pcp/msg.c
@@ -0,0 +1,378 @@
+/**
+ * @file pcp/msg.c  PCP messages
+ *
+ * Copyright (C) 2010 - 2016 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_pcp.h>
+#include "pcp.h"
+
+
+static int pcp_map_decode(struct pcp_map *map, struct mbuf *mb)
+{
+	uint16_t port;
+	int err;
+
+	if (!map || !mb)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < PCP_MAP_SZ)
+		return EBADMSG;
+
+	(void)mbuf_read_mem(mb, map->nonce, sizeof(map->nonce));
+	map->proto = mbuf_read_u8(mb); mbuf_advance(mb, 3);
+	map->int_port = ntohs(mbuf_read_u16(mb));
+
+	port = ntohs(mbuf_read_u16(mb));
+	err = pcp_ipaddr_decode(mb, &map->ext_addr);
+	sa_set_port(&map->ext_addr, port);
+
+	return err;
+}
+
+
+static int pcp_peer_decode(struct pcp_peer *peer, struct mbuf *mb)
+{
+	uint16_t port;
+	int err = 0;
+
+	if (!peer || !mb)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < PCP_PEER_SZ)
+		return EBADMSG;
+
+	/* note: the MAP and PEER opcodes are quite similar */
+	err = pcp_map_decode(&peer->map, mb);
+	if (err)
+		return err;
+
+	port = ntohs(mbuf_read_u16(mb)); mbuf_advance(mb, 2);
+	err |= pcp_ipaddr_decode(mb, &peer->remote_addr);
+	sa_set_port(&peer->remote_addr, port);
+
+	return err;
+}
+
+
+static void destructor(void *arg)
+{
+	struct pcp_msg *msg = arg;
+
+	list_flush(&msg->optionl);
+}
+
+
+static int pcp_header_encode_request(struct mbuf *mb, enum pcp_opcode opcode,
+			      uint32_t req_lifetime, const struct sa *int_addr)
+{
+	int err = 0;
+
+	if (!mb || !int_addr)
+		return EINVAL;
+
+	err |= mbuf_write_u8(mb, PCP_VERSION);
+	err |= mbuf_write_u8(mb, opcode);
+	err |= mbuf_write_u16(mb, 0x0000);
+	err |= mbuf_write_u32(mb, htonl(req_lifetime));
+	err |= pcp_ipaddr_encode(mb, int_addr);
+
+	return err;
+}
+
+
+static int pcp_header_decode(struct pcp_hdr *hdr, struct mbuf *mb)
+{
+	uint8_t b;
+
+	if (!hdr || !mb)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < PCP_HDR_SZ)
+		return EBADMSG;
+
+	hdr->version = mbuf_read_u8(mb);
+
+	if (hdr->version != PCP_VERSION) {
+		(void)re_fprintf(stderr, "pcp: unknown version %u\n",
+				 hdr->version);
+		return EPROTO;
+	}
+
+	b            = mbuf_read_u8(mb);
+	hdr->resp    = b>>7;
+	hdr->opcode  = b & 0x7f;
+
+	(void)mbuf_read_u8(mb);
+	b = mbuf_read_u8(mb);
+
+	if (hdr->resp)
+		hdr->result = b;
+
+	hdr->lifetime = ntohl(mbuf_read_u32(mb));
+
+	if (hdr->resp) {
+		hdr->epoch = ntohl(mbuf_read_u32(mb));
+		mbuf_advance(mb, 12);
+	}
+	else { /* Request */
+		(void)pcp_ipaddr_decode(mb, &hdr->cli_addr);
+	}
+
+	return 0;
+}
+
+
+int pcp_msg_req_vencode(struct mbuf *mb, enum pcp_opcode opcode,
+			uint32_t lifetime, const struct sa *cli_addr,
+			const void *payload, uint32_t optionc, va_list ap)
+{
+	uint32_t i;
+	int err;
+
+	if (!mb || !cli_addr)
+		return EINVAL;
+
+	err = pcp_header_encode_request(mb, opcode, lifetime, cli_addr);
+	if (err)
+		return err;
+
+	if (payload) {
+		err = pcp_payload_encode(mb, opcode, payload);
+		if (err)
+			return err;
+	}
+
+	/* encode options */
+	for (i=0; i<optionc; i++) {
+
+		enum pcp_option_code code = va_arg(ap, int);
+		const void *v = va_arg(ap, const void *);
+
+		if (!v)
+			continue;
+
+		err |= pcp_option_encode(mb, code, v);
+	}
+
+	return err;
+}
+
+
+int pcp_msg_req_encode(struct mbuf *mb, enum pcp_opcode opcode,
+		       uint32_t lifetime, const struct sa *cli_addr,
+		       const void *payload, uint32_t optionc, ...)
+{
+	va_list ap;
+	int err;
+
+	va_start(ap, optionc);
+	err = pcp_msg_req_vencode(mb, opcode, lifetime, cli_addr,
+				  payload, optionc, ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+int pcp_msg_decode(struct pcp_msg **msgp, struct mbuf *mb)
+{
+	struct pcp_msg *msg;
+	size_t len, pos;
+	int err;
+
+	if (!msgp || !mb)
+		return EINVAL;
+
+	len = mbuf_get_left(mb);
+	if (len < PCP_MIN_PACKET || len > PCP_MAX_PACKET || len&3)
+		return EBADMSG;
+
+	msg = mem_zalloc(sizeof(*msg), destructor);
+	if (!msg)
+		return ENOMEM;
+
+	pos = mb->pos;
+	err = pcp_header_decode(&msg->hdr, mb);
+	if (err)
+		goto out;
+
+	switch (msg->hdr.opcode) {
+
+	case PCP_MAP:
+		err = pcp_map_decode(&msg->pld.map, mb);
+		break;
+
+	case PCP_PEER:
+		err = pcp_peer_decode(&msg->pld.peer, mb);
+		break;
+
+	default:
+		break;
+	}
+	if (err)
+		goto out;
+
+	/* Decode PCP Options */
+	while (mbuf_get_left(mb) >= 4) {
+
+		struct pcp_option *opt;
+
+		err = pcp_option_decode(&opt, mb);
+		if (err)
+			goto out;
+
+		list_append(&msg->optionl, &opt->le, opt);
+	}
+
+ out:
+	if (err) {
+		mb->pos = pos;
+		mem_deref(msg);
+	}
+	else
+		*msgp = msg;
+
+	return err;
+}
+
+
+struct pcp_option *pcp_msg_option(const struct pcp_msg *msg,
+				  enum pcp_option_code code)
+{
+	struct le *le = msg ? list_head(&msg->optionl) : NULL;
+
+	while (le) {
+		struct pcp_option *opt = le->data;
+
+		le = le->next;
+
+		if (opt->code == code)
+			return opt;
+	}
+
+	return NULL;
+}
+
+
+struct pcp_option *pcp_msg_option_apply(const struct pcp_msg *msg,
+					pcp_option_h *h, void *arg)
+{
+	struct le *le = msg ? list_head(&msg->optionl) : NULL;
+
+	while (le) {
+		struct pcp_option *opt = le->data;
+
+		le = le->next;
+
+		if (h && h(opt, arg))
+			return opt;
+	}
+
+	return NULL;
+}
+
+
+static bool option_print(const struct pcp_option *opt, void *arg)
+{
+	return 0 != pcp_option_print(arg, opt);
+}
+
+
+int pcp_msg_printhdr(struct re_printf *pf, const struct pcp_msg *msg)
+{
+	int err;
+
+	if (!msg)
+		return 0;
+
+	err = re_hprintf(pf, "%s %s %usec",
+			 msg->hdr.resp ? "Response" : "Request",
+			 pcp_opcode_name(msg->hdr.opcode),
+			 msg->hdr.lifetime);
+
+	if (msg->hdr.resp) {
+		err |= re_hprintf(pf, " result=%s, epoch_time=%u sec",
+				  pcp_result_name(msg->hdr.result),
+				  msg->hdr.epoch);
+	}
+	else {
+		err |= re_hprintf(pf, " client_addr=%j", &msg->hdr.cli_addr);
+	}
+
+	return err;
+}
+
+
+static int pcp_map_print(struct re_printf *pf, const struct pcp_map *map)
+{
+	if (!map)
+		return 0;
+
+	return re_hprintf(pf,
+			  " nonce    = %w\n protocol = %s\n"
+			  " int_port = %u\n ext_addr = %J\n",
+			  map->nonce, sizeof(map->nonce),
+			  pcp_proto_name(map->proto),
+			  map->int_port,
+			  &map->ext_addr);
+}
+
+
+int pcp_msg_print(struct re_printf *pf, const struct pcp_msg *msg)
+{
+	int err;
+
+	if (!msg)
+		return 0;
+
+	err  = pcp_msg_printhdr(pf, msg);
+	err |= re_hprintf(pf, "\n");
+
+	switch (msg->hdr.opcode) {
+
+	case PCP_MAP:
+		err |= pcp_map_print(pf, &msg->pld.map);
+		break;
+
+	case PCP_PEER:
+		err |= pcp_map_print(pf, &msg->pld.peer.map);
+		err |= re_hprintf(pf, " remote_peer = %J\n",
+				  &msg->pld.peer.remote_addr);
+		break;
+	}
+
+	if (err)
+		return err;
+
+	if (pcp_msg_option_apply(msg, option_print, pf))
+		return ENOMEM;
+
+	return 0;
+}
+
+
+/**
+ * Get the payload from a PCP message
+ *
+ * @param msg PCP message
+ *
+ * @return either "struct pcp_map" or "struct pcp_peer"
+ */
+const void *pcp_msg_payload(const struct pcp_msg *msg)
+{
+	if (!msg)
+		return NULL;
+
+	switch (msg->hdr.opcode) {
+
+	case PCP_MAP:  return &msg->pld.map;
+	case PCP_PEER: return &msg->pld.peer;
+	default:       return NULL;
+	}
+}
diff --git a/src/pcp/option.c b/src/pcp/option.c
new file mode 100644
index 0000000..5ca9e0b
--- /dev/null
+++ b/src/pcp/option.c
@@ -0,0 +1,228 @@
+/**
+ * @file option.c  PCP options
+ *
+ * Copyright (C) 2010 - 2016 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_pcp.h>
+#include "pcp.h"
+
+
+static void destructor(void *arg)
+{
+	struct pcp_option *opt = arg;
+
+	list_unlink(&opt->le);
+
+	switch (opt->code) {
+
+	case PCP_OPTION_DESCRIPTION:
+		mem_deref(opt->u.description);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+int pcp_option_encode(struct mbuf *mb, enum pcp_option_code code,
+		      const void *v)
+{
+	const struct sa *sa = v;
+	const struct pcp_option_filter *filt = v;
+	size_t start, len;
+	int err = 0;
+
+	if (!mb)
+		return EINVAL;
+
+	mb->pos += 4;
+	start = mb->pos;
+
+	switch (code) {
+
+	case PCP_OPTION_THIRD_PARTY:
+		if (!sa)
+			return EINVAL;
+		err |= pcp_ipaddr_encode(mb, sa);
+		break;
+
+	case PCP_OPTION_PREFER_FAILURE:
+		/* no payload */
+		break;
+
+	case PCP_OPTION_FILTER:
+		if (!filt)
+			return EINVAL;
+		err |= mbuf_write_u8(mb, 0x00);
+		err |= mbuf_write_u8(mb, filt->prefix_length);
+		err |= mbuf_write_u16(mb, htons(sa_port(&filt->remote_peer)));
+		err |= pcp_ipaddr_encode(mb, &filt->remote_peer);
+		break;
+
+	case PCP_OPTION_DESCRIPTION:
+		if (!v)
+			return EINVAL;
+		err |= mbuf_write_str(mb, v);
+		break;
+
+	default:
+		(void)re_fprintf(stderr,
+				 "pcp: unsupported option %d\n", code);
+		return EINVAL;
+	}
+
+	/* header */
+	len = mb->pos - start;
+
+	mb->pos = start - 4;
+	err |= mbuf_write_u8(mb, code);
+	err |= mbuf_write_u8(mb, 0x00);
+	err |= mbuf_write_u16(mb, htons(len));
+	mb->pos += len;
+
+	/* padding */
+	while ((mb->pos - start) & 0x03)
+		err |= mbuf_write_u8(mb, 0x00);
+
+	return err;
+}
+
+
+int pcp_option_decode(struct pcp_option **optp, struct mbuf *mb)
+{
+	struct pcp_option *opt;
+	size_t start, len;
+	uint16_t port;
+	int err = 0;
+
+	if (!optp || !mb)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < 4)
+		return EBADMSG;
+
+	opt = mem_zalloc(sizeof(*opt), destructor);
+	if (!opt)
+		return ENOMEM;
+
+	opt->code = mbuf_read_u8(mb);
+	(void)mbuf_read_u8(mb);
+	len = ntohs(mbuf_read_u16(mb));
+
+	if (mbuf_get_left(mb) < len)
+		goto badmsg;
+
+	start = mb->pos;
+
+	switch (opt->code) {
+
+	case PCP_OPTION_THIRD_PARTY:
+		if (len < 16)
+			goto badmsg;
+		err = pcp_ipaddr_decode(mb, &opt->u.third_party);
+		break;
+
+	case PCP_OPTION_PREFER_FAILURE:
+		/* no payload */
+		break;
+
+	case PCP_OPTION_FILTER:
+		if (len < 20)
+			goto badmsg;
+		(void)mbuf_read_u8(mb);
+		opt->u.filter.prefix_length = mbuf_read_u8(mb);
+		port = ntohs(mbuf_read_u16(mb));
+		err = pcp_ipaddr_decode(mb, &opt->u.filter.remote_peer);
+		sa_set_port(&opt->u.filter.remote_peer, port);
+		break;
+
+	case PCP_OPTION_DESCRIPTION:
+		err = mbuf_strdup(mb, &opt->u.description, len);
+		break;
+
+	default:
+		mb->pos += len;
+
+		(void)re_printf("pcp: ignore option code %d (len=%zu)\n",
+				opt->code, len);
+		break;
+	}
+
+	if (err)
+		goto error;
+
+	/* padding */
+	while (((mb->pos - start) & 0x03) && mbuf_get_left(mb))
+		++mb->pos;
+
+	*optp = opt;
+
+	return 0;
+
+ badmsg:
+	err = EBADMSG;
+ error:
+	mem_deref(opt);
+
+	return err;
+}
+
+
+static const char *pcp_option_name(enum pcp_option_code code)
+{
+	switch (code) {
+
+	case PCP_OPTION_THIRD_PARTY:    return "THIRD_PARTY";
+	case PCP_OPTION_PREFER_FAILURE: return "PREFER_FAILURE";
+	case PCP_OPTION_FILTER:         return "FILTER";
+	case PCP_OPTION_DESCRIPTION:    return "DESCRIPTION";
+	default: return "?";
+	}
+}
+
+
+int pcp_option_print(struct re_printf *pf, const struct pcp_option *opt)
+{
+	int err;
+
+	if (!opt)
+		return 0;
+
+	err = re_hprintf(pf, " %-25s", pcp_option_name(opt->code));
+
+	switch (opt->code) {
+
+	case PCP_OPTION_THIRD_PARTY:
+		err |= re_hprintf(pf, "address=%j",
+				  &opt->u.third_party);
+		break;
+
+	case PCP_OPTION_PREFER_FAILURE:
+		break;
+
+	case PCP_OPTION_FILTER:
+		err |= re_hprintf(pf, "prefix_length=%u, remote_peer=%J",
+				  opt->u.filter.prefix_length,
+				  &opt->u.filter.remote_peer);
+		break;
+
+	case PCP_OPTION_DESCRIPTION:
+		err |= re_hprintf(pf, "'%s'", opt->u.description);
+		break;
+
+	default:
+		err |= re_hprintf(pf, "???");
+		break;
+	}
+
+	err |= re_hprintf(pf, "\n");
+
+	return err;
+}
diff --git a/src/pcp/payload.c b/src/pcp/payload.c
new file mode 100644
index 0000000..c9d6033
--- /dev/null
+++ b/src/pcp/payload.c
@@ -0,0 +1,112 @@
+/**
+ * @file payload.c  PCP payload encoding and decoding
+ *
+ * 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_pcp.h>
+#include "pcp.h"
+
+
+static int pcp_write_port(struct mbuf *mb, const struct sa *sa)
+{
+	uint16_t port_be;
+
+	if (!mb || !sa)
+		return EINVAL;
+
+	switch (sa->u.sa.sa_family) {
+
+	case AF_INET:
+		port_be = sa->u.in.sin_port;
+		break;
+
+#ifdef HAVE_INET6
+	case AF_INET6:
+		port_be = sa->u.in6.sin6_port;
+		break;
+#endif
+
+	default:
+		return EAFNOSUPPORT;
+	}
+
+	return mbuf_write_u16(mb, port_be);
+}
+
+
+static int pcp_map_encode(struct mbuf *mb, const struct pcp_map *map)
+{
+	int err = 0;
+
+	if (!mb || !map)
+		return EINVAL;
+
+	err |= mbuf_write_mem(mb, map->nonce, sizeof(map->nonce));
+	err |= mbuf_write_u8(mb, map->proto);
+	err |= mbuf_fill(mb, 0x00, 3);
+	err |= mbuf_write_u16(mb, htons(map->int_port));
+	err |= pcp_write_port(mb, &map->ext_addr);
+	err |= pcp_ipaddr_encode(mb, &map->ext_addr);
+
+	return err;
+}
+
+
+static int pcp_peer_encode(struct mbuf *mb, const struct pcp_peer *peer)
+{
+	int err;
+
+	if (!mb || !peer)
+		return EINVAL;
+
+	/* Protocol MUST NOT be zero.
+	 * Internal port MUST NOT be zero.
+	 */
+	if (!peer->map.proto || !peer->map.int_port)
+		return EPROTO;
+
+	/* note: the MAP and PEER opcodes are quite similar */
+	err = pcp_map_encode(mb, &peer->map);
+	if (err)
+		return err;
+
+	err  = pcp_write_port(mb, &peer->remote_addr);
+	err |= mbuf_write_u16(mb, 0x0000);
+	err |= pcp_ipaddr_encode(mb, &peer->remote_addr);
+
+	return err;
+}
+
+
+int pcp_payload_encode(struct mbuf *mb, enum pcp_opcode opcode,
+		       const union pcp_payload *pld)
+{
+	int err;
+
+	if (!mb || !pld)
+		return EINVAL;
+
+	switch (opcode) {
+
+	case PCP_MAP:
+		err = pcp_map_encode(mb, &pld->map);
+		break;
+
+	case PCP_PEER:
+		err = pcp_peer_encode(mb, &pld->peer);
+		break;
+
+	default:
+		re_fprintf(stderr, "pcp: dont know how to encode payload"
+			   " for opcode %d\n", opcode);
+		err = EPROTO;
+		break;
+	}
+
+	return err;
+}
diff --git a/src/pcp/pcp.c b/src/pcp/pcp.c
new file mode 100644
index 0000000..28961fb
--- /dev/null
+++ b/src/pcp/pcp.c
@@ -0,0 +1,122 @@
+/**
+ * @file pcp/pcp.c  PCP protocol details
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_pcp.h>
+#include "pcp.h"
+
+
+static const uint8_t pattern[12] = {0,0,0,0,0,0,0,0,0,0,0xff,0xff};
+
+
+int pcp_ipaddr_encode(struct mbuf *mb, const struct sa *sa)
+{
+	int err = 0;
+
+	if (!mb || !sa)
+		return EINVAL;
+
+	switch (sa_af(sa)) {
+
+	case AF_INET:
+		err |= mbuf_write_mem(mb, pattern, sizeof(pattern));
+		err |= mbuf_write_mem(mb, (void *)&sa->u.in.sin_addr.s_addr,
+				      4);
+		break;
+
+#ifdef HAVE_INET6
+	case AF_INET6:
+		err |= mbuf_write_mem(mb, sa->u.in6.sin6_addr.s6_addr, 16);
+		break;
+#endif
+
+	default:
+		err = EAFNOSUPPORT;
+		break;
+	}
+
+	return err;
+}
+
+
+int pcp_ipaddr_decode(struct mbuf *mb, struct sa *sa)
+{
+	uint8_t *p;
+
+	if (!mb || !sa)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < 16)
+		return EBADMSG;
+
+	p = mbuf_buf(mb);
+
+	if (0 == memcmp(p, pattern, sizeof(pattern))) {
+
+		sa_init(sa, AF_INET);
+		memcpy(&sa->u.in.sin_addr, p + 12, 4);
+	}
+#ifdef HAVE_INET6
+	else {
+		sa_init(sa, AF_INET6);
+		memcpy(sa->u.in6.sin6_addr.s6_addr, p, 16);
+	}
+#endif
+
+	mb->pos += 16;
+
+	return 0;
+}
+
+
+const char *pcp_result_name(enum pcp_result result)
+{
+	switch (result) {
+
+	case PCP_SUCCESS:                 return "SUCCESS";
+	case PCP_UNSUPP_VERSION:          return "UNSUPP_VERSION";
+	case PCP_NOT_AUTHORIZED:          return "NOT_AUTHORIZED";
+	case PCP_MALFORMED_REQUEST:       return "MALFORMED_REQUEST";
+	case PCP_UNSUPP_OPCODE:           return "UNSUPP_OPCODE";
+	case PCP_UNSUPP_OPTION:           return "UNSUPP_OPTION";
+	case PCP_MALFORMED_OPTION:        return "MALFORMED_OPTION";
+	case PCP_NETWORK_FAILURE:         return "NETWORK_FAILURE";
+	case PCP_NO_RESOURCES:            return "NO_RESOURCES";
+	case PCP_UNSUPP_PROTOCOL:         return "UNSUPP_PROTOCOL";
+	case PCP_USER_EX_QUOTA:           return "USER_EX_QUOTA";
+	case PCP_CANNOT_PROVIDE_EXTERNAL: return "CANNOT_PROVIDE_EXTERNAL";
+	case PCP_ADDRESS_MISMATCH:        return "ADDRESS_MISMATCH";
+	case PCP_EXCESSIVE_REMOTE_PEERS:  return "EXCESSIVE_REMOTE_PEERS";
+	case PCP_UNSUPP_FAMILY:           return "UNSUPP_FAMILY";
+	default: return "?";
+	}
+}
+
+
+const char *pcp_opcode_name(enum pcp_opcode opcode)
+{
+	switch (opcode) {
+
+	case PCP_ANNOUNCE: return "ANNOUNCE";
+	case PCP_MAP:      return "MAP";
+	case PCP_PEER:     return "PEER";
+	default:           return "?";
+	}
+}
+
+
+const char *pcp_proto_name(int proto)
+{
+	switch (proto) {
+
+	case IPPROTO_UDP: return "UDP";
+	case IPPROTO_TCP: return "TCP";
+	default: return "?";
+	}
+}
diff --git a/src/pcp/pcp.h b/src/pcp/pcp.h
new file mode 100644
index 0000000..5c85ecc
--- /dev/null
+++ b/src/pcp/pcp.h
@@ -0,0 +1,9 @@
+/**
+ * @file pcp/pcp.h  PCP protocol -- Internal interface
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+
+int pcp_payload_encode(struct mbuf *mb, enum pcp_opcode opcode,
+		       const union pcp_payload *pld);
diff --git a/src/pcp/reply.c b/src/pcp/reply.c
new file mode 100644
index 0000000..264dcbb
--- /dev/null
+++ b/src/pcp/reply.c
@@ -0,0 +1,93 @@
+/**
+ * @file pcp/reply.c  PCP reply
+ *
+ * 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_udp.h>
+#include <re_pcp.h>
+#include "pcp.h"
+
+
+static int pcp_header_encode_response(struct mbuf *mb, enum pcp_opcode opcode,
+				      enum pcp_result result,
+				      uint32_t lifetime, uint32_t epoch_time)
+{
+	int err = 0;
+
+	if (!mb)
+		return EINVAL;
+
+	err |= mbuf_write_u8(mb, PCP_VERSION);
+	err |= mbuf_write_u8(mb, 1<<7 | opcode);
+	err |= mbuf_write_u8(mb, 0x00);
+	err |= mbuf_write_u8(mb, result);
+	err |= mbuf_write_u32(mb, htonl(lifetime));
+	err |= mbuf_write_u32(mb, htonl(epoch_time));
+	err |= mbuf_fill(mb, 0x00, 12);
+
+	return err;
+}
+
+
+/**
+ * Send a PCP response message
+ *
+ * @param us         UDP Socket
+ * @param dst        Destination network address
+ * @param req        Buffer containing original PCP request (optional)
+ * @param opcode     PCP opcode
+ * @param result     PCP result for the response
+ * @param lifetime   Lifetime in [seconds]
+ * @param epoch_time Server Epoch-time
+ * @param payload    PCP payload, e.g. struct pcp_map (optional)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int pcp_reply(struct udp_sock *us, const struct sa *dst, struct mbuf *req,
+	      enum pcp_opcode opcode, enum pcp_result result,
+	      uint32_t lifetime, uint32_t epoch_time, const void *payload)
+{
+	struct mbuf *mb;
+	size_t start;
+	int err;
+
+	if (!us || !dst)
+		return EINVAL;
+
+	if (req) {
+		/* the complete Request must be included in the Response */
+		mb = mem_ref(req);
+	}
+	else {
+		mb = mbuf_alloc(128);
+		if (!mb)
+			return ENOMEM;
+	}
+
+	start = mb->pos;
+
+	/* encode the response packet */
+	err = pcp_header_encode_response(mb, opcode, result,
+					 lifetime, epoch_time);
+	if (err)
+		goto out;
+
+	if (payload) {
+		err = pcp_payload_encode(mb, opcode, payload);
+		if (err)
+			goto out;
+	}
+
+	mb->pos = start;
+	err = udp_send(us, dst, mb);
+
+ out:
+	mem_deref(mb);
+	return err;
+}
diff --git a/src/pcp/request.c b/src/pcp/request.c
new file mode 100644
index 0000000..60d58b4
--- /dev/null
+++ b/src/pcp/request.c
@@ -0,0 +1,346 @@
+/**
+ * @file pcp/request.c  PCP 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_sys.h>
+#include <re_sa.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_pcp.h>
+#include "pcp.h"
+
+
+/*
+ * Defines a PCP client request
+ *
+ * the application must keep a reference to this object and the
+ * object must be deleted by the application. the response handler
+ * might be called multiple times.
+ */
+struct pcp_request {
+	struct pcp_conf conf;
+	struct sa srv;
+	struct udp_sock *us;
+	struct mbuf *mb;
+	struct tmr tmr;
+	struct tmr tmr_dur;
+	struct tmr tmr_refresh;
+	enum pcp_opcode opcode;
+	union pcp_payload payload;
+	uint32_t lifetime;
+	bool granted;
+	unsigned txc;
+	double RT;
+	pcp_resp_h *resph;
+	void *arg;
+};
+
+
+/*
+ * RT:   Retransmission timeout
+ * IRT:  Initial retransmission time, SHOULD be 3 seconds
+ * MRC:  Maximum retransmission count, SHOULD be 0 (no maximum)
+ * MRT:  Maximum retransmission time, SHOULD be 1024 seconds
+ * MRD:  Maximum retransmission duration, SHOULD be 0 (no maximum)
+ * RAND: Randomization factor
+ */
+static const struct pcp_conf default_conf = {
+	3,
+	0,
+	1024,
+	0
+};
+
+
+static int start_sending(struct pcp_request *req);
+
+
+/* random number between -0.1 and +0.1 */
+static inline double RAND(void)
+{
+	return (1.0 * rand_u16() / 32768 - 1.0) / 10.0;
+}
+
+static double RT_init(const struct pcp_conf *conf)
+{
+	return (1.0 + RAND()) * conf->irt;
+}
+
+static double RT_next(const struct pcp_conf *conf, double RTprev)
+{
+	return (1.0 + RAND()) * min (2 * RTprev, conf->mrt);
+}
+
+
+static void destructor(void *arg)
+{
+	struct pcp_request *req = arg;
+
+	/* Destroy the mapping if it was granted */
+	if (req->granted && req->lifetime && req->mb) {
+
+		/* set the lifetime to zero */
+		req->mb->pos = 4;
+		mbuf_write_u32(req->mb, 0);
+
+		req->mb->pos = 0;
+		(void)udp_send(req->us, &req->srv, req->mb);
+	}
+
+	tmr_cancel(&req->tmr);
+	tmr_cancel(&req->tmr_dur);
+	tmr_cancel(&req->tmr_refresh);
+	mem_deref(req->us);
+	mem_deref(req->mb);
+}
+
+
+static void completed(struct pcp_request *req, int err, struct pcp_msg *msg)
+{
+	pcp_resp_h *resph = req->resph;
+	void *arg = req->arg;
+
+	tmr_cancel(&req->tmr);
+	tmr_cancel(&req->tmr_dur);
+
+	/* if the request failed, we only called the
+	   response handler once and never again */
+	if (err || msg->hdr.result != PCP_SUCCESS ) {
+		req->resph = NULL;
+	}
+
+	if (resph)
+		resph(err, msg, arg);
+}
+
+
+static void refresh_timeout(void *arg)
+{
+	struct pcp_request *req = arg;
+
+	/* todo: update request with new EXT-ADDR from server */
+	(void)start_sending(req);
+}
+
+
+static void timeout(void *arg)
+{
+	struct pcp_request *req = arg;
+	int err;
+
+	req->txc++;
+
+	if (req->conf.mrc > 0 && req->txc > req->conf.mrc) {
+		completed(req, ETIMEDOUT, NULL);
+		return;
+	}
+
+	req->mb->pos = 0;
+	err = udp_send(req->us, &req->srv, req->mb);
+	if (err) {
+		completed(req, err, NULL);
+		return;
+	}
+
+	req->RT = RT_next(&req->conf, req->RT);
+	tmr_start(&req->tmr, req->RT * 1000, timeout, req);
+}
+
+
+static void timeout_duration(void *arg)
+{
+	struct pcp_request *req = arg;
+
+	completed(req, ETIMEDOUT, NULL);
+}
+
+
+static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg)
+{
+	struct pcp_request *req = arg;
+	struct pcp_msg *msg;
+	int err;
+
+	if (!sa_cmp(src, &req->srv, SA_ALL))
+		return;
+
+	err = pcp_msg_decode(&msg, mb);
+	if (err)
+		return;
+
+	if (!msg->hdr.resp) {
+		(void)re_fprintf(stderr, "pcp: ignoring PCP request\n");
+		goto out;
+	}
+
+	if (msg->hdr.opcode != req->opcode)
+		goto out;
+
+	/* compare opcode-specific data */
+
+	switch (msg->hdr.opcode) {
+
+	case PCP_MAP:
+	case PCP_PEER:
+		if (0 != memcmp(msg->pld.map.nonce, req->payload.map.nonce,
+				PCP_NONCE_SZ)) {
+			(void)re_fprintf(stderr, "ignoring unknown nonce\n");
+			goto out;
+		}
+		req->payload.map.ext_addr = msg->pld.map.ext_addr;
+		break;
+
+	default:
+		break;
+	}
+
+	req->lifetime = msg->hdr.lifetime;
+	req->granted = (msg->hdr.result == PCP_SUCCESS);
+
+	/* todo:
+	 *
+	 * Once a PCP client has successfully received a response from a PCP
+	 * server on that interface, it resets RT to a value randomly selected
+	 * in the range 1/2 to 5/8 of the mapping lifetime, as described in
+	 * Section 11.2.1, "Renewing a Mapping", and sends subsequent PCP
+	 * requests for that mapping to that same server.
+	 */
+	if (req->granted && req->lifetime) {
+
+		uint32_t v = req->lifetime * 3/4;
+
+		tmr_start(&req->tmr_refresh, v * 1000, refresh_timeout, req);
+	}
+
+	completed(req, 0, msg);
+
+ out:
+	mem_deref(msg);
+}
+
+
+static int start_sending(struct pcp_request *req)
+{
+	int err;
+
+	req->txc = 1;
+
+	req->mb->pos = 0;
+	err = udp_send(req->us, &req->srv, req->mb);
+	if (err)
+		return err;
+
+	req->RT = RT_init(&req->conf);
+	tmr_start(&req->tmr, req->RT * 1000, timeout, req);
+
+	if (req->conf.mrd) {
+		tmr_start(&req->tmr_dur, req->conf.mrd * 1000,
+			  timeout_duration, req);
+	}
+
+	return err;
+}
+
+
+static int pcp_vrequest(struct pcp_request **reqp, const struct pcp_conf *conf,
+			const struct sa *srv, enum pcp_opcode opcode,
+			uint32_t lifetime, const void *payload,
+			pcp_resp_h *resph, void *arg,
+			uint32_t optionc, va_list ap)
+{
+	const union pcp_payload *up = payload;
+	struct pcp_request *req;
+	struct sa laddr;
+	int err;
+
+	if (!reqp || !srv)
+		return EINVAL;
+
+	sa_init(&laddr, sa_af(srv));
+
+	req = mem_zalloc(sizeof(*req), destructor);
+	if (!req)
+		return ENOMEM;
+
+	req->conf   = conf ? *conf : default_conf;
+	req->opcode = opcode;
+	req->srv    = *srv;
+	req->resph  = resph;
+	req->arg    = arg;
+
+	req->lifetime = lifetime;
+
+	if (up)
+		req->payload = *up;
+
+	err = udp_listen(&req->us, &laddr, udp_recv, req);
+	if (err)
+		goto out;
+
+	/*
+	 * see RFC 6887 section 16.4
+	 */
+	err = udp_connect(req->us, srv);
+	if (err)
+		goto out;
+	err = udp_local_get(req->us, &laddr);
+	if (err)
+		goto out;
+
+	req->mb = mbuf_alloc(128);
+	if (!req->mb) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	err = pcp_msg_req_vencode(req->mb, opcode, lifetime,
+				  &laddr, up, optionc, ap);
+	if (err)
+		goto out;
+
+	err = start_sending(req);
+
+ out:
+	if (err)
+		mem_deref(req);
+	else
+		*reqp = req;
+
+	return err;
+}
+
+
+int pcp_request(struct pcp_request **reqp, const struct pcp_conf *conf,
+		const struct sa *srv, enum pcp_opcode opcode,
+		uint32_t lifetime, const void *payload,
+		pcp_resp_h *resph, void *arg, uint32_t optionc, ...)
+{
+	va_list ap;
+	int err;
+
+	va_start(ap, optionc);
+	err = pcp_vrequest(reqp, conf, srv, opcode, lifetime, payload,
+			   resph, arg, optionc, ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+void pcp_force_refresh(struct pcp_request *req)
+{
+	if (!req)
+		return;
+
+	tmr_cancel(&req->tmr);
+	tmr_cancel(&req->tmr_dur);
+
+	tmr_start(&req->tmr_refresh, rand_u16() % 2000, refresh_timeout, req);
+}
diff --git a/src/shim/mod.mk b/src/shim/mod.mk
new file mode 100644
index 0000000..6593d57
--- /dev/null
+++ b/src/shim/mod.mk
@@ -0,0 +1,7 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS	+= shim/shim.c
diff --git a/src/shim/shim.c b/src/shim/shim.c
new file mode 100644
index 0000000..07e3b9c
--- /dev/null
+++ b/src/shim/shim.c
@@ -0,0 +1,200 @@
+
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_tcp.h>
+#include <re_net.h>
+#include <re_shim.h>
+
+
+#define DEBUG_MODULE "shim"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+struct shim {
+	struct tcp_conn *tc;
+	struct tcp_helper *th;
+	struct mbuf *mb;
+	shim_frame_h *frameh;
+	void *arg;
+
+	uint64_t n_tx;
+	uint64_t n_rx;
+};
+
+
+/* responsible for adding the SHIM header
+   - assumes that the sent MBUF contains a complete packet
+ */
+static bool shim_send_handler(int *err, struct mbuf *mb, void *arg)
+{
+	struct shim *shim = arg;
+	size_t len;
+	(void)shim;
+
+	if (mb->pos < SHIM_HDR_SIZE) {
+		DEBUG_WARNING("send: not enough space for SHIM header\n");
+		*err = ENOMEM;
+		return true;
+	}
+
+	len = mbuf_get_left(mb);
+
+	mb->pos -= SHIM_HDR_SIZE;
+	*err = mbuf_write_u16(mb, htons(len));
+	mb->pos -= SHIM_HDR_SIZE;
+
+	++shim->n_tx;
+
+	return false;
+}
+
+
+static bool shim_recv_handler(int *errp, struct mbuf *mbx, bool *estab,
+			      void *arg)
+{
+	struct shim *shim = arg;
+	int err = 0;
+	(void)estab;
+
+	/* handle re-assembly */
+	if (!shim->mb) {
+		shim->mb = mbuf_alloc(1024);
+		if (!shim->mb) {
+			*errp = ENOMEM;
+			return true;
+		}
+	}
+
+	if (shim->mb) {
+		size_t pos;
+
+		pos = shim->mb->pos;
+
+		shim->mb->pos = shim->mb->end;
+
+		err = mbuf_write_mem(shim->mb, mbuf_buf(mbx),
+				     mbuf_get_left(mbx));
+		if (err)
+			goto out;
+
+		shim->mb->pos = pos;
+	}
+
+	/* extract all SHIM-frames in the TCP-stream */
+	for (;;) {
+
+		size_t start, len, pos, end;
+		bool hdld;
+
+		start = shim->mb->pos;
+
+		if (mbuf_get_left(shim->mb) < (SHIM_HDR_SIZE))
+			break;
+
+		len = ntohs(mbuf_read_u16(shim->mb));
+
+		if (mbuf_get_left(shim->mb) < len)
+			goto rewind;
+
+		pos = shim->mb->pos;
+		end = shim->mb->end;
+
+		shim->mb->end = pos + len;
+
+		++shim->n_rx;
+
+		hdld = shim->frameh(shim->mb, shim->arg);
+		if (!hdld) {
+			/* XXX: handle multiple frames per segment */
+
+			shim->mb->pos = pos;
+			shim->mb->end = pos + len;
+
+			mbx->pos = mbx->end = 2;
+			err = mbuf_write_mem(mbx, mbuf_buf(shim->mb), len);
+			if (err)
+				goto out;
+			mbx->pos = 2;
+
+			shim->mb->pos = pos + len;
+			shim->mb->end = end;
+
+			return false;  /* continue recv-handlers */
+		}
+
+		shim->mb->pos = pos + len;
+		shim->mb->end = end;
+
+		if (shim->mb->pos >= shim->mb->end) {
+			shim->mb = mem_deref(shim->mb);
+			break;
+		}
+
+		continue;
+
+	rewind:
+		shim->mb->pos = start;
+		break;
+	}
+
+ out:
+	if (err)
+		*errp = err;
+
+	return true;  /* always handled */
+}
+
+
+static void destructor(void *arg)
+{
+	struct shim *shim = arg;
+
+	mem_deref(shim->th);
+	mem_deref(shim->tc);
+	mem_deref(shim->mb);
+}
+
+
+int shim_insert(struct shim **shimp, struct tcp_conn *tc, int layer,
+		shim_frame_h *frameh, void *arg)
+{
+	struct shim *shim;
+	int err;
+
+	if (!shimp || !tc || !frameh)
+		return EINVAL;
+
+	shim = mem_zalloc(sizeof(*shim), destructor);
+	if (!shim)
+		return ENOMEM;
+
+	shim->tc = mem_ref(tc);
+	err = tcp_register_helper(&shim->th, tc, layer, NULL,
+				  shim_send_handler,
+				  shim_recv_handler, shim);
+	if (err)
+		goto out;
+
+	shim->frameh = frameh;
+	shim->arg = arg;
+
+ out:
+	if (err)
+		mem_deref(shim);
+	else
+		*shimp = shim;
+
+	return err;
+}
+
+
+int shim_debug(struct re_printf *pf, const struct shim *shim)
+{
+	if (!shim)
+		return 0;
+
+	return re_hprintf(pf, "tx=%llu, rx=%llu", shim->n_tx, shim->n_rx);
+}
diff --git a/src/trice/README.md b/src/trice/README.md
new file mode 100644
index 0000000..8c2fcee
--- /dev/null
+++ b/src/trice/README.md
@@ -0,0 +1,109 @@
+ICE-NOTES:
+---------
+
+
+
+----------------------------------
+Application layer:
+
+- Can handle any modes (Full, Ice, Trickle)
+- Can implement any nomination-type (regular, aggressive)
+- Can encode/decode SDP attributes
+- Must gather all candidates (including STUN/TURN servers)
+- Application can choose the Default Local Candidate
+- Application can choose the Default Remote Candidate
+- Can measure RTT end-to-end
+- Can apply a STUN consent timer on top of ICE
+- the application should have the freedom to choose any selected candidate-pair
+- can install Keep-Alive timer (Binding Indication) for any pairs
+- can install a consent timer for any pair
+
+
+----------------------------------
+ICE layer:
+
+- All modes (Full-ICE and Trickle-ICE) are implemented
+- ICE-lite is NOT supported
+- agnostic to modes (Full, Trickle)
+- agnostic to nomination-type (regular, aggressive)
+- SDP encoding and decoding of ICE-relavant attributes
+- can handle between 1 and 2 components per media-stream
+- gathering: No candidate gathering, must be done in App
+- agnostic to transport protocol (should handle both UDP and TCP)
+- rel-addr (related address) is supported, but it is not used in the logic
+- ICE-stack does not choose the Default Local Candidate
+- ICE-stack does not choose the Default Remote Candidate
+- no TURN client
+- Each local candidate can have its own listen address/port. Yes
+- Support for UDP-transport
+- Support for TCP-transport
+- Support for IPv4 and IPv6
+- modular design with building blocks.
+- check all components before calling estabh ? no.
+- no support for "default" candidate/candidate-pair
+- component object: NO
+- selected pair: NO  
+- must be able to support custom UDP/TCP-transport via helpers
+
+
+----------------------------------
+Interop:
+
+OK - Firefox 31.0
+OK - Firefox 35.0
+OK - Firefox 36.0
+OK - Chrome 41
+OK - Chrome 58
+
+
+TODO:
+
+done - remove ICE-lite mode
+done - remove rel-addr from ICE-code
+done - interop testing with Chrome
+done - interop testing with Firefox - seems to be working with 31.0
+done - Firefox: test with trickling candidates over Websock
+done - add support for TCP-candidates (test with Chrome)
+done - make a new test-client (reicec) and test on a public server
+done - do we need to support LITE at all? No.
+done - split EOC flag into local_eoc and remote_eoc
+done - send trigged request from stunsrv
+done - new module "icesdp" for SDP encoding/decoding
+done - add a new module "shim" (RFC 4571)
+     - ICE module should be Conncheck-only, no data-transport
+     - test_ice_tcp: S-O not working on Linux
+done - check when adding PRFLX that EOC-flag is set/unset (not needed)
+done - move use_cand flag to checklist_start/send_conncheck ?
+     - verify that APP can grab udp/tcp-sock
+     - consider moving pacing-logic to application?
+
+
+
+
+Architecture Diagram:
+--------------------
+
+
+
+```
+
+  .-------.                  .-------. 
+  |  App  |                  |  App  |
+  '-------'                  '-------'
+      |                          |           \
+      |        .--------.        |           |
+      +--------+  STUN  +--------+           |
+      |        '--------'        |           | "ICE-layer"
+      |                      .-------.       |
+      |                      | SHIM  |       |
+      |                      '-------'       |
+      |                          |           /
+  .-------.                  .-------.
+  |  UDP  |                  |  TCP  |
+  '-------'                  '-------'
+      |                          |
+      !                          !
+
+
+
+```
diff --git a/src/trice/cand.c b/src/trice/cand.c
new file mode 100644
index 0000000..df4f0e6
--- /dev/null
+++ b/src/trice/cand.c
@@ -0,0 +1,88 @@
+/**
+ * @file cand.c  Common 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_net.h>
+#include <re_sys.h>
+#include <re_stun.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_ice.h>
+#include <re_trice.h>
+#include "trice.h"
+
+
+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 "???";
+	}
+}
+
+
+/*
+   Local           Remote
+   Candidate       Candidate
+   ---------------------------
+   tcp-so          tcp-so
+   tcp-active      tcp-passive
+   tcp-passive     tcp-active
+
+ */
+enum ice_tcptype ice_tcptype_reverse(enum ice_tcptype type)
+{
+	switch (type) {
+
+	case ICE_TCP_SO:      return ICE_TCP_SO;
+	case ICE_TCP_ACTIVE:  return ICE_TCP_PASSIVE;
+	case ICE_TCP_PASSIVE: return ICE_TCP_ACTIVE;
+	default:              return (enum ice_tcptype)-1;
+	}
+}
+
+
+enum ice_cand_type ice_cand_type_base(enum ice_cand_type type)
+{
+	switch (type) {
+
+	case ICE_CAND_TYPE_HOST:    return ICE_CAND_TYPE_HOST;
+	case ICE_CAND_TYPE_SRFLX:   return ICE_CAND_TYPE_HOST;
+	case ICE_CAND_TYPE_PRFLX:   return ICE_CAND_TYPE_HOST;
+	case ICE_CAND_TYPE_RELAY:   return ICE_CAND_TYPE_RELAY;
+	default: return type;
+	}
+}
+
+
+int trice_cand_print(struct re_printf *pf, const struct ice_cand_attr *cand)
+{
+	int err = 0;
+
+	if (!cand)
+		return 0;
+
+	err |= re_hprintf(pf, "%s|%s", ice_cand_type2name(cand->type),
+			  net_proto2name(cand->proto));
+
+	if (cand->proto == IPPROTO_TCP) {
+
+		err |= re_hprintf(pf, ".%s", ice_tcptype_name(cand->tcptype));
+	}
+
+	err |= re_hprintf(pf, "|%J", &cand->addr);
+
+	return err;
+}
diff --git a/src/trice/candpair.c b/src/trice/candpair.c
new file mode 100644
index 0000000..deb23c4
--- /dev/null
+++ b/src/trice/candpair.c
@@ -0,0 +1,563 @@
+/**
+ * @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_udp.h>
+#include <re_stun.h>
+#include <re_ice.h>
+#include <re_trice.h>
+#include "trice.h"
+
+
+#define DEBUG_MODULE "candpair"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/*
+ * generic routines to operate on "struct ice_candpair"
+ * (for both checkl and validl)
+ */
+
+
+/*
+ * g = controlling agent
+ * d = controlled agent
+
+ pair priority = 2^32*MIN(G,D) + 2*MAX(G,D) + (G>D?1:0)
+
+ */
+static 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);
+}
+
+
+static void candpair_destructor(void *arg)
+{
+	struct ice_candpair *cp = arg;
+
+	list_unlink(&cp->le);
+	mem_deref(cp->lcand);
+	mem_deref(cp->rcand);
+	mem_deref(cp->tc);
+
+	mem_deref(cp->conn);
+}
+
+
+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, bool controlling)
+{
+	uint32_t g, d;
+
+	if (controlling) {
+		g = cp->lcand->attr.prio;
+		d = cp->rcand->attr.prio;
+	}
+	else {
+		g = cp->rcand->attr.prio;
+		d = cp->lcand->attr.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 trice_candpair_alloc(struct ice_candpair **cpp, struct trice *icem,
+			struct ice_lcand *lcand, struct ice_rcand *rcand)
+{
+	struct ice_candpair *cp;
+
+	if (!icem || !lcand || !rcand)
+		return EINVAL;
+
+	if (icem->lrole == ICE_ROLE_UNKNOWN) {
+		DEBUG_WARNING("trice_candpair_alloc: invalid local role!\n");
+		return EINVAL;
+	}
+
+	cp = mem_zalloc(sizeof(*cp), candpair_destructor);
+	if (!cp)
+		return ENOMEM;
+
+	cp->lcand = mem_ref(lcand);
+	cp->rcand = mem_ref(rcand);
+	cp->state = ICE_CANDPAIR_FROZEN;
+
+	candpair_set_pprio(cp, icem->lrole == ICE_ROLE_CONTROLLING);
+
+	list_add_sorted(&icem->checkl, cp);
+
+	if (cpp)
+		*cpp = cp;
+
+	return 0;
+}
+
+
+/** Computing Pair Priority and Ordering Pairs */
+void trice_candpair_prio_order(struct list *lst, bool controlling)
+{
+	struct le *le;
+
+	for (le = list_head(lst); le; le = le->next) {
+		struct ice_candpair *cp = le->data;
+
+		candpair_set_pprio(cp, controlling);
+	}
+
+	list_sort(lst, sort_handler, NULL);
+}
+
+
+void trice_candpair_make_valid(struct trice *icem, struct ice_candpair *pair)
+{
+	if (!icem || !pair)
+		return;
+
+	if (pair->state == ICE_CANDPAIR_FAILED) {
+		DEBUG_WARNING("make_valid: pair already FAILED [%H]\n",
+			      trice_candpair_debug, pair);
+	}
+
+	pair->err = 0;
+	pair->scode = 0;
+	pair->valid = true;
+
+	trice_candpair_set_state(pair, ICE_CANDPAIR_SUCCEEDED);
+
+	list_unlink(&pair->le);
+	list_add_sorted(&icem->validl, pair);
+}
+
+
+void trice_candpair_failed(struct ice_candpair *cp, int err, uint16_t scode)
+{
+	if (!cp)
+		return;
+
+	if (cp->state == ICE_CANDPAIR_SUCCEEDED) {
+		DEBUG_WARNING("set_failed(%m): pair already SUCCEEDED [%H]\n",
+			      err, trice_candpair_debug, cp);
+	}
+
+	cp->err = err;
+	cp->scode = scode;
+	cp->valid = false;
+
+	cp->conn = mem_deref(cp->conn);
+
+	trice_candpair_set_state(cp, ICE_CANDPAIR_FAILED);
+}
+
+
+void trice_candpair_set_state(struct ice_candpair *pair,
+			     enum ice_candpair_state state)
+{
+	if (!pair)
+		return;
+	if (pair->state == state)
+		return;
+
+	if (trice_candpair_iscompleted(pair)) {
+		DEBUG_WARNING("set_state(%s): pair is already completed"
+			      " [%H]\n",
+			      trice_candpair_state2name(state),
+			      trice_candpair_debug, pair);
+	}
+
+#if 0
+	trice_printf(pair->lcand->icem,
+		    "%H new state \"%s\"\n",
+		    trice_candpair_debug, pair,
+		    trice_candpair_state2name(state));
+#endif
+
+	pair->state = state;
+}
+
+
+bool trice_candpair_iscompleted(const struct ice_candpair *cp)
+{
+	if (!cp)
+		return false;
+
+	return cp->state == ICE_CANDPAIR_FAILED ||
+		cp->state == ICE_CANDPAIR_SUCCEEDED;
+}
+
+
+/**
+ * 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 *trice_candpair_find(const struct list *lst,
+					const struct ice_lcand *lcand,
+					const struct ice_rcand *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;
+}
+
+
+/* find the first pair with a given state */
+struct ice_candpair *trice_candpair_find_state(const struct list *lst,
+					      enum ice_candpair_state state)
+{
+	struct le *le;
+
+	for (le = list_head(lst); le; le = le->next) {
+
+		struct ice_candpair *cp = le->data;
+
+		if (cp->state != state)
+			continue;
+
+		return cp;
+	}
+
+	return NULL;
+}
+
+
+bool trice_candpair_cmp_fnd(const struct ice_candpair *cp1,
+			   const struct ice_candpair *cp2)
+{
+	if (!cp1 || !cp2)
+		return false;
+
+	return 0 == strcmp(cp1->lcand->attr.foundation,
+			   cp2->lcand->attr.foundation) &&
+		0 == strcmp(cp1->rcand->attr.foundation,
+			    cp2->rcand->attr.foundation);
+}
+
+
+/*  RFC 6544 -- 6.2. Forming the Check Lists
+
+   Local           Remote
+   Candidate       Candidate
+   ---------------------------
+   tcp-so          tcp-so
+   tcp-active      tcp-passive
+   tcp-passive     tcp-active
+
+ */
+static bool tcptype_match(enum ice_tcptype loc, enum ice_tcptype rem)
+{
+	if (loc == ICE_TCP_SO      && rem == ICE_TCP_SO)      return true;
+	if (loc == ICE_TCP_ACTIVE  && rem == ICE_TCP_PASSIVE) return true;
+	if (loc == ICE_TCP_PASSIVE && rem == ICE_TCP_ACTIVE)  return true;
+
+	return false;
+}
+
+
+/* Replace server reflexive candidates by its base */
+static const struct sa *cand_srflx_addr(const struct ice_lcand *cand)
+{
+	if (ICE_CAND_TYPE_SRFLX == cand->attr.type)
+		return &cand->base_addr;
+	else
+		return &cand->attr.addr;
+}
+
+
+static struct ice_candpair *find_same_base_list(const struct list *lst,
+						const struct ice_lcand *lcand,
+						const struct ice_rcand *rcand)
+{
+	struct le *le;
+
+	for (le = list_head(lst); le; le = le->next) {
+
+		struct ice_candpair *cp = le->data;
+
+		if (cp->lcand->attr.compid == lcand->attr.compid
+		    &&
+		    cp->lcand->attr.proto == lcand->attr.proto
+		    &&
+		    sa_cmp(cand_srflx_addr(cp->lcand),
+			   cand_srflx_addr(lcand), SA_ALL)
+		    &&
+		    sa_cmp(&cp->rcand->attr.addr,
+			   &rcand->attr.addr, SA_ALL)) {
+
+			return cp;
+		}
+	}
+
+	return NULL;
+}
+
+
+/* look in both check-list and valid-list */
+static struct ice_candpair *find_same_base(struct trice *icem,
+					   const struct ice_lcand *lcand,
+					   const struct ice_rcand *rcand)
+{
+	struct ice_candpair *cp;
+
+	cp = find_same_base_list(&icem->checkl, lcand, rcand);
+	if (cp)
+		return cp;
+
+	cp = find_same_base_list(&icem->validl, lcand, rcand);
+	if (cp)
+		return cp;
+
+	return NULL;
+}
+
+
+/* Pair a local candidate with a remote candidate */
+static int create_pair(struct trice *icem, struct ice_lcand *lcand,
+		       struct ice_rcand *rcand)
+{
+	struct ice_candpair *cpx;
+
+	if (lcand->attr.compid != rcand->attr.compid ||
+	    lcand->attr.proto != rcand->attr.proto ||
+	    sa_af(&lcand->attr.addr) != sa_af(&rcand->attr.addr)) {
+		return 0;
+	}
+
+	/*
+	 * IPv6 link-local: only pair with IPv6 link-local addresses
+	 * see: RFC5245bis, section 6.1.2.2
+	 */
+	if (sa_af(&lcand->attr.addr) == AF_INET6 &&
+	    sa_is_linklocal(&lcand->attr.addr) !=
+	    sa_is_linklocal(&rcand->attr.addr)) {
+		return 0;
+	}
+
+	/* loopback pairing optimization: only pair with loopback addresses */
+	if (icem->conf.optimize_loopback_pairing &&
+	    sa_is_loopback(&lcand->attr.addr) !=
+	    sa_is_loopback(&rcand->attr.addr)) {
+		return 0;
+	}
+
+	cpx = find_same_base(icem, lcand, rcand);
+	if (cpx) {
+		trice_printf(icem,
+				"with: pair with same"
+				" base already exist"
+				" (%H)\n",
+				trice_candpair_debug, cpx);
+
+		return 0;
+	}
+
+	if (lcand->attr.proto == IPPROTO_TCP) {
+		if (!tcptype_match(lcand->attr.tcptype,
+				   rcand->attr.tcptype))
+			return 0;
+	}
+
+	/* add sorted */
+	return trice_candpair_alloc(NULL, icem, lcand, rcand);
+}
+
+
+/* Pair a candidate with all other candidates of the opposite kind */
+int trice_candpair_with_local(struct trice *icem, struct ice_lcand *lcand)
+{
+	struct list *lst = &icem->rcandl;
+	struct le *le;
+	int err = 0;
+
+	if (icem->lrole == ICE_ROLE_UNKNOWN) {
+		DEBUG_WARNING("trice_candpair_with_local: invalid local role!"
+					  "\n");
+		return EINVAL;
+	}
+
+	for (le = list_head(lst); le; le = le->next) {
+
+		struct ice_rcand *rcand = le->data;
+
+		err = create_pair(icem, lcand, rcand);
+		if (err)
+			goto out;
+	}
+
+ out:
+	return err;
+}
+
+
+/* Pair a candidate with all other candidates of the opposite kind */
+int trice_candpair_with_remote(struct trice *icem, struct ice_rcand *rcand)
+{
+	struct list *lst = &icem->lcandl;
+	struct le *le;
+	int err = 0;
+
+	if (icem->lrole == ICE_ROLE_UNKNOWN) {
+		DEBUG_WARNING("trice_candpair_with_remote: invalid local role!"
+					  "\n");
+		return EINVAL;
+	}
+
+	for (le = list_head(lst); le; le = le->next) {
+
+		struct ice_lcand *lcand = le->data;
+
+		err = create_pair(icem, lcand, rcand);
+		if (err)
+			goto out;
+	}
+
+ out:
+	return err;
+}
+
+
+int trice_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%c} %28H <---> %28H",
+			 cp->lcand->attr.compid,
+			 trice_candpair_state2name(cp->state),
+			 cp->valid ? 'V' : ' ',
+			 cp->nominated ? 'N' : ' ',
+			 cp->estab ? 'E' : ' ',
+			 cp->trigged ? 'T' : ' ',
+			 trice_cand_print, cp->lcand,
+			 trice_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 trice_candpairs_debug(struct re_printf *pf, bool ansi_output,
+			  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 ansi = false;
+
+		if (ansi_output) {
+			if (cp->state == ICE_CANDPAIR_SUCCEEDED) {
+				err |= re_hprintf(pf, "\x1b[32m");
+				ansi = true;
+			}
+			else if (cp->err || cp->scode) {
+				err |= re_hprintf(pf, "\x1b[31m");
+				ansi = true;
+			}
+		}
+
+		err |= re_hprintf(pf, "    %H\n",
+				 trice_candpair_debug, cp);
+
+		if (ansi)
+			err |= re_hprintf(pf, "\x1b[;m");
+	}
+
+	return err;
+}
+
+
+const char *trice_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 "???";
+	}
+}
diff --git a/src/trice/chklist.c b/src/trice/chklist.c
new file mode 100644
index 0000000..f4e7409
--- /dev/null
+++ b/src/trice/chklist.c
@@ -0,0 +1,334 @@
+/**
+ * @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 <re_trice.h>
+#include "trice.h"
+
+
+#define DEBUG_MODULE "checklist"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static void destructor(void *arg)
+{
+	struct ice_checklist *ic = arg;
+
+	tmr_cancel(&ic->tmr_pace);
+	list_flush(&ic->conncheckl);  /* flush before stun deref */
+	mem_deref(ic->stun);
+}
+
+
+static void pace_timeout(void *arg)
+{
+	struct ice_checklist *ic = arg;
+	struct trice *icem = (struct trice *)ic->icem;
+
+	tmr_start(&ic->tmr_pace, ic->interval,
+		  pace_timeout, ic);
+
+	trice_conncheck_schedule_check(icem);
+
+	trice_checklist_update(icem);
+}
+
+
+int trice_checklist_start(struct trice *icem, struct stun *stun,
+			  uint32_t interval,
+			  trice_estab_h *estabh, trice_failed_h *failh,
+			  void *arg)
+{
+	struct ice_checklist *ic;
+	int err = 0;
+
+	if (!icem)
+		return EINVAL;
+
+	if (icem->lrole == ICE_ROLE_UNKNOWN) {
+		DEBUG_WARNING("trice_checklist_start: missing local role!\n");
+		return EINVAL;
+	}
+
+	if (icem->checklist) {
+		ic = icem->checklist;
+
+		if (!tmr_isrunning(&ic->tmr_pace)) {
+			tmr_start(&ic->tmr_pace, 1, pace_timeout, ic);
+		}
+		return 0;
+	}
+
+	/* The password is equal to the password provided by the peer */
+	if (!str_isset(icem->rpwd)) {
+		DEBUG_WARNING("start: remote password not set\n");
+		return EINVAL;
+	}
+
+	ic = mem_zalloc(sizeof(*ic), destructor);
+	if (!ic)
+		return ENOMEM;
+
+	if (stun) {
+		ic->stun = mem_ref(stun);
+	}
+	else {
+		err = stun_alloc(&ic->stun, NULL, NULL, NULL);
+		if (err)
+			goto out;
+
+		/* Update STUN Transport */
+		stun_conf(ic->stun)->rto = 100;
+		stun_conf(ic->stun)->rc = 4;
+
+	}
+
+	tmr_init(&ic->tmr_pace);
+
+	ic->interval = interval;
+	ic->icem = icem;
+	ic->estabh = estabh;
+	ic->failh  = failh;
+	ic->arg    = arg;
+
+	ic->is_running = true;
+	tmr_start(&ic->tmr_pace, 0, pace_timeout, ic);
+
+	icem->checklist = ic;
+
+ out:
+	if (err)
+		mem_deref(ic);
+
+	return err;
+}
+
+
+void trice_checklist_stop(struct trice *icem)
+{
+	struct ice_checklist *ic;
+
+	if (!icem || !icem->checklist)
+		return;
+
+	ic = icem->checklist;
+
+	ic->is_running = false;
+	tmr_cancel(&ic->tmr_pace);
+}
+
+
+/* If all of the pairs in the check list are now either in the Failed or
+   Succeeded state:
+ */
+bool trice_checklist_iscompleted(const struct trice *icem)
+{
+	struct le *le;
+
+	if (!icem)
+		return false;
+
+	for (le = icem->checkl.head; le; le = le->next) {
+
+		const struct ice_candpair *cp = le->data;
+
+		if (!trice_candpair_iscompleted(cp))
+			return false;
+	}
+
+	return true;
+}
+
+
+/**
+ * Scheduling Checks
+ */
+void trice_conncheck_schedule_check(struct trice *icem)
+{
+	struct ice_candpair *pair;
+	bool use_cand;
+	int err = 0;
+
+	if (!icem)
+		return;
+
+	switch (icem->conf.nom) {
+
+	case ICE_NOMINATION_REGULAR:
+		use_cand = false;
+		break;
+
+	case ICE_NOMINATION_AGGRESSIVE:
+		use_cand = true;
+		break;
+
+	default:
+		DEBUG_WARNING("schedule_check: invalid nomination %d\n",
+			      icem->conf.nom);
+		return;
+	}
+
+	/* Find the highest priority pair in that check list that is in the
+	   Waiting state. */
+	pair = trice_candpair_find_state(&icem->checkl, ICE_CANDPAIR_WAITING);
+	if (pair) {
+		err = trice_conncheck_send(icem, pair,
+					  use_cand);
+		if (err)
+			trice_candpair_failed(pair, err, 0);
+		return;
+	}
+
+	/* If there is no such pair: */
+
+	/* Find the highest priority pair in that check list that is in
+	   the Frozen state. */
+	pair = trice_candpair_find_state(&icem->checkl, ICE_CANDPAIR_FROZEN);
+	if (pair) { /* If there is such a pair: */
+
+		/* Unfreeze the pair.
+		   Perform a check for that pair, causing its state to
+		   transition to In-Progress. */
+		err = trice_conncheck_send(icem, pair,
+					  use_cand);
+		if (err)
+			trice_candpair_failed(pair, err, 0);
+		return;
+	}
+
+	/* If there is no such pair: */
+
+	/* Terminate the timer for that check list. */
+}
+
+
+/**
+ * Computing States
+ */
+void trice_checklist_set_waiting(struct trice *icem)
+{
+	struct le *le, *le2;
+
+	if (!icem)
+		return;
+
+	if (icem->lrole == ICE_ROLE_UNKNOWN) {
+		DEBUG_WARNING("trice_checklist_set_waiting: invalid local"
+					  "role!\n");
+		return;
+	}
+
+	/*
+	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 (!trice_candpair_cmp_fnd(cp, cp2))
+				continue;
+
+			if (cp2->lcand->attr.compid < cp->lcand->attr.compid &&
+			    cp2->pprio > cp->pprio)
+				cp = cp2;
+		}
+
+		if (cp->state == ICE_CANDPAIR_FROZEN)
+			trice_candpair_set_state(cp, ICE_CANDPAIR_WAITING);
+	}
+}
+
+
+int trice_checklist_update(struct trice *icem)
+{
+	struct ice_checklist *ic;
+
+	if (!icem)
+		return EINVAL;
+
+	ic = icem->checklist;
+	if (!ic)
+		return ENOSYS;
+
+	if (trice_checklist_iscompleted(icem)) {
+		tmr_cancel(&ic->tmr_pace);
+
+		trice_printf(icem, "ICE checklist is complete"
+			     " (checkl=%u, valid=%u)\n",
+			     list_count(&icem->checkl),
+			     list_count(&icem->validl));
+	}
+
+	return 0;
+}
+
+
+void trice_checklist_refresh(struct trice *icem)
+{
+	struct ice_checklist *ic;
+
+	if (!icem || !icem->checklist)
+		return;
+
+	ic = icem->checklist;
+
+	tmr_start(&ic->tmr_pace, ic->interval, pace_timeout, ic);
+}
+
+
+bool trice_checklist_isrunning(const struct trice *icem)
+{
+	struct ice_checklist *ic;
+
+	if (!icem || !icem->checklist)
+		return false;
+
+	ic = icem->checklist;
+
+	return ic->is_running;
+}
+
+
+int trice_checklist_debug(struct re_printf *pf, const struct ice_checklist *ic)
+{
+	struct le *le;
+	int err = 0;
+
+	if (!ic)
+		return 0;
+
+	err |= re_hprintf(pf, " Checklist: %s, interval=%ums\n",
+		  tmr_isrunning(&ic->tmr_pace) ? "Running" : "Not-Running",
+			  ic->interval);
+	err |= re_hprintf(pf, " Pending connchecks: %u\n",
+			  list_count(&ic->conncheckl));
+	for (le = ic->conncheckl.head; le; le = le->next) {
+		struct ice_conncheck *cc = le->data;
+
+		err |= re_hprintf(pf, " ...%H\n", trice_conncheck_debug, cc);
+	}
+
+	err |= stun_debug(pf, ic->stun);
+
+	return err;
+}
diff --git a/src/trice/connchk.c b/src/trice/connchk.c
new file mode 100644
index 0000000..f25ca16
--- /dev/null
+++ b/src/trice/connchk.c
@@ -0,0 +1,532 @@
+/**
+ * @file connchk.c  ICE 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_net.h>
+#include <re_stun.h>
+#include <re_ice.h>
+#include <re_trice.h>
+#include "trice.h"
+
+
+#define DEBUG_MODULE "conncheck"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+enum {PRESZ_RELAY = 36};
+
+
+static void conncheck_destructor(void *arg)
+{
+	struct ice_conncheck *cc = arg;
+
+	cc->term = true;
+	list_unlink(&cc->le);
+	mem_deref(cc->ct_conn);
+}
+
+
+static void pair_established(struct trice *icem, struct ice_candpair *pair,
+			     const struct stun_msg *msg)
+{
+	struct ice_tcpconn *conn;
+
+	if (!icem || !pair)
+		return;
+
+	if (pair->lcand->attr.proto == IPPROTO_TCP) {
+
+		conn = pair->conn;
+
+		if (!conn) {
+			/* todo: Hack to grab TCPCONN */
+			conn = trice_conn_find(&icem->connl,
+					     pair->lcand->attr.compid,
+					     &pair->lcand->attr.addr,
+					     &pair->rcand->attr.addr);
+		}
+
+		if (conn) {
+			pair->tc = mem_deref(pair->tc);
+			pair->tc = mem_ref(conn->tc);
+		}
+		else {
+			DEBUG_WARNING("pair_established: TCP-connection "
+				      " from %H to %H not found!\n",
+				      trice_cand_print, pair->lcand,
+				      trice_cand_print, pair->rcand);
+		}
+
+	}
+
+	if (!pair->estab) {
+		pair->estab = true;
+
+		if (icem->checklist->estabh) {
+			icem->checklist->estabh(pair, msg,
+						icem->checklist->arg);
+		}
+	}
+}
+
+
+/*
+ * NOTE for TCP-candidates:
+ *
+ *     Note also that STUN responses received on an active TCP candidate
+ *     will typically produce a peer reflexive candidate.
+ *
+ *
+ * NOTE for Trickle ICE and Peer Reflexive Candidates:
+ *
+ *     With Trickle ICE, it is possible that
+ *     server reflexive candidates be discovered as peer reflexive in cases
+ *     where incoming connectivity checks are received from these candidates
+ *     before the trickle updates that carry them.
+ *
+ */
+static void handle_success(struct trice *icem, struct ice_candpair *pair,
+			   const struct sa *mapped_addr,
+			   const struct stun_msg *msg,
+			   struct ice_conncheck *cc)
+{
+	unsigned compid = pair->lcand->attr.compid;
+	int err;
+
+	if (!icem || !pair) {
+		DEBUG_WARNING("handle_success: invalid params\n");
+	}
+
+	if (icem->conf.enable_prflx &&
+	    !trice_lcand_find(icem, -1, compid,
+			      pair->lcand->attr.proto, mapped_addr)) {
+
+		struct ice_lcand *lcand;
+		struct ice_candpair *pair_prflx;
+		uint32_t prio;
+
+		prio = ice_cand_calc_prio(ICE_CAND_TYPE_PRFLX, 0, compid);
+
+		err = trice_add_lcandidate(&lcand, icem, &icem->lcandl, compid,
+					   "FND", pair->lcand->attr.proto,
+					   prio, mapped_addr,
+					   &pair->lcand->attr.addr,
+					   ICE_CAND_TYPE_PRFLX,
+					   &pair->lcand->attr.addr,
+					   pair->lcand->attr.tcptype);
+		if (err) {
+			DEBUG_WARNING("failed to add PRFLX: %m\n", err);
+			return;
+		}
+
+		if (str_isset(pair->lcand->ifname)) {
+			str_ncpy(lcand->ifname, pair->lcand->ifname,
+				 sizeof(lcand->ifname));
+		}
+
+		trice_printf(icem, "added PRFLX local candidate (%H)"
+			     " from base (%H)\n",
+			     trice_cand_print, lcand,
+			     trice_cand_print, pair->lcand);
+
+		/* newly created Candidate-PAir */
+		err = trice_candpair_alloc(&pair_prflx, icem,
+					  lcand, pair->rcand);
+		if (err) {
+			DEBUG_WARNING("prflx alloc: %m\n", err);
+			return;
+		}
+
+		lcand->us = mem_ref(pair->lcand->us);
+		pair_prflx->conn = mem_ref(pair->conn);
+
+		/* mark the original HOST-one as failed */
+		trice_candpair_failed(pair, 0, 0);
+
+		trice_candpair_make_valid(icem, pair_prflx);
+
+		pair_established(icem, pair_prflx, msg);
+		return;
+	}
+
+	pair->state = ICE_CANDPAIR_FROZEN;
+	trice_candpair_make_valid(icem, pair);
+
+	/* Updating the Nominated Flag */
+	if (ICE_ROLE_CONTROLLING == icem->lrole) {
+
+		if (cc->use_cand)
+			pair->nominated = true;
+	}
+
+	pair_established(icem, pair, msg);
+}
+
+
+static int print_err(struct re_printf *pf, const int *err)
+{
+	if (err && *err)
+		return re_hprintf(pf, " (%m)", *err);
+
+	return 0;
+}
+
+
+static void stunc_resp_handler(int err, uint16_t scode, const char *reason,
+			       const struct stun_msg *msg, void *arg)
+{
+	struct ice_conncheck *cc = arg;
+	struct ice_candpair *pair = cc->pair;
+	struct trice *icem = cc->icem;
+	struct stun_attr *attr;
+	bool success = (err == 0) && (scode == 0);
+	(void)reason;
+
+	if (!icem) {
+		DEBUG_WARNING("stun response: no icem\n");
+	}
+
+	if (cc->term)
+		return;
+
+	trice_tracef(icem, success ? 32 : 31,
+		     "[%u] Rx %H <--- %H '%u %s'%H\n",
+		     pair->lcand->attr.compid,
+		     trice_cand_print, pair->lcand,
+		     trice_cand_print, pair->rcand,
+		     scode, reason, print_err, &err);
+
+	if (err) {
+		DEBUG_NOTICE("stun response: [%H --> %H] %m\n",
+			     trice_cand_print, pair->lcand,
+			     trice_cand_print, pair->rcand,
+			     err);
+
+		trice_candpair_failed(pair, 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");
+			trice_candpair_failed(pair, EPROTO, 0);
+			break;
+		}
+
+		handle_success(icem, pair, &attr->v.sa, msg, cc);
+		break;
+
+	case 487: /* Role Conflict */
+		trice_switch_local_role(icem);
+		(void)trice_conncheck_send(icem, pair, cc->use_cand);
+		break;
+
+	default:
+		trice_candpair_failed(pair, err, scode);
+		break;
+	}
+
+ out:
+	if (err || scode) {
+		icem->checklist->failh(err, scode, pair, icem->checklist->arg);
+	}
+
+	mem_deref(cc);
+	return;
+}
+
+
+int trice_conncheck_stun_request(struct ice_checklist *ic,
+			       struct ice_conncheck *cc,
+			       struct ice_candpair *cp, void *sock,
+			       bool cc_use_cand)
+{
+	struct ice_lcand *lcand = cp->lcand;
+	struct trice *icem = ic->icem;
+	char username_buf[256];
+	uint32_t prio_prflx;
+	uint16_t ctrl_attr;
+	bool use_cand = false;
+	size_t presz = 0;
+	int err = 0;
+
+	if (!cp)
+		return EINVAL;
+
+	if (!ic)
+		return ENOSYS;
+
+	if (!sock) {
+		DEBUG_NOTICE("conncheck: no SOCK\n");
+		return EINVAL;
+	}
+
+	/* The password is equal to the password provided by the peer */
+	if (!str_isset(icem->rpwd)) {
+		DEBUG_WARNING("conncheck: remote password missing for"
+			      " raddr=%J\n", &cp->rcand->attr.addr);
+		err = EINVAL;
+		goto out;
+	}
+
+	if (lcand->attr.proto == IPPROTO_UDP &&
+	    lcand->attr.type == ICE_CAND_TYPE_RELAY)
+		presz = PRESZ_RELAY;
+	else if (lcand->attr.proto == IPPROTO_TCP)
+		presz = 2;
+
+	if (re_snprintf(username_buf, sizeof(username_buf),
+			"%s:%s", icem->rufrag, icem->lufrag) < 0) {
+		DEBUG_WARNING("conncheck: username buffer too small\n");
+		err = ENOMEM;
+		goto out;
+	}
+
+	/* PRIORITY and USE-CANDIDATE */
+	prio_prflx = ice_cand_calc_prio(ICE_CAND_TYPE_PRFLX, 0,
+					lcand->attr.compid);
+
+	switch (icem->lrole) {
+
+	case ICE_ROLE_CONTROLLING:
+		ctrl_attr = STUN_ATTR_CONTROLLING;
+		use_cand = cc_use_cand;
+		break;
+
+	case ICE_ROLE_CONTROLLED:
+		ctrl_attr = STUN_ATTR_CONTROLLED;
+		break;
+
+	default:
+		DEBUG_WARNING("conncheck: invalid local role\n");
+		return EINVAL;
+	}
+
+	trice_tracef(icem, 36,
+		     "[%u] Tx [presz=%zu] %H ---> %H (%s) %s\n",
+		     lcand->attr.compid,
+		     presz,
+		     trice_cand_print, cp->lcand, trice_cand_print, cp->rcand,
+		     trice_candpair_state2name(cp->state),
+		     use_cand ? "[USE]" : "");
+
+	/* A connectivity check MUST utilize the STUN short term credential
+	   mechanism. */
+
+	err = stun_request(&cc->ct_conn, ic->stun, lcand->attr.proto,
+			   sock, &cp->rcand->attr.addr, presz,
+			   STUN_METHOD_BINDING,
+			   (uint8_t *)icem->rpwd, str_len(icem->rpwd),
+			   true, stunc_resp_handler, cc,
+			   4,
+			   STUN_ATTR_USERNAME, username_buf,
+			   STUN_ATTR_PRIORITY, &prio_prflx,
+			   ctrl_attr, &icem->tiebrk,
+			   STUN_ATTR_USE_CAND,
+			   use_cand ? &use_cand : 0);
+	if (err) {
+		DEBUG_NOTICE("stun_request from %H to %H failed (%m)\n",
+			      trice_cand_print, lcand,
+			      trice_cand_print, cp->rcand,
+			      err);
+		goto out;
+	}
+
+ out:
+	if (err) {
+		trice_candpair_failed(cp, err, 0);
+	}
+
+	return err;
+}
+
+
+static bool tcpconn_frame_handler(struct trice *icem,
+				  struct tcp_conn *tc, struct sa *src,
+				  struct mbuf *mb, void *arg)
+{
+	struct ice_lcand *lcand = arg;
+
+	return trice_stun_process(icem, lcand,
+				  IPPROTO_TCP, tc, src, mb);
+}
+
+
+int trice_conncheck_send(struct trice *icem, struct ice_candpair *pair,
+			bool use_cand)
+{
+	struct ice_checklist *ic;
+	struct ice_lcand *lcand;
+	struct ice_tcpconn *conn;
+	struct ice_conncheck *cc = NULL;
+	void *sock;
+	int err = 0;
+
+	if (!icem || !pair)
+		return EINVAL;
+
+	lcand = pair->lcand;
+	ic = icem->checklist;
+	if (!ic) {
+		DEBUG_WARNING("conncheck_send: no checklist\n");
+		return EINVAL;
+	}
+
+	cc = mem_zalloc(sizeof(*cc), conncheck_destructor);
+	if (!cc)
+		return ENOMEM;
+
+	cc->icem = icem;
+	cc->pair = pair;
+	cc->use_cand = use_cand;
+
+	if (pair->state < ICE_CANDPAIR_INPROGRESS)
+		trice_candpair_set_state(pair, ICE_CANDPAIR_INPROGRESS);
+
+	switch (pair->lcand->attr.proto) {
+
+	case IPPROTO_UDP:
+		sock = trice_lcand_sock(icem, lcand);
+
+		err = trice_conncheck_stun_request(ic, cc, pair,
+						 sock, use_cand);
+		if (err)
+			goto out;
+		break;
+
+	case IPPROTO_TCP:
+		conn = trice_conn_find(&icem->connl, lcand->attr.compid,
+				     &pair->lcand->attr.addr,
+				     &pair->rcand->attr.addr);
+		if (conn) {
+			trice_printf(icem, "TCP-connection"
+				    " already exist [%H]\n",
+				    trice_conn_debug, conn);
+
+			pair->conn = mem_ref(conn);  /* todo: */
+
+			err = trice_conncheck_stun_request(ic, cc, pair,
+							 conn->tc, use_cand);
+			if (err)
+				goto out;
+			break;
+		}
+
+		switch (pair->lcand->attr.tcptype) {
+
+		case ICE_TCP_ACTIVE:
+		case ICE_TCP_SO:
+			err = trice_conn_alloc(&icem->connl, icem,
+					     lcand->attr.compid, true,
+					     &lcand->attr.addr,
+					     &pair->rcand->attr.addr,
+					     lcand->ts, lcand->layer,
+					     tcpconn_frame_handler, lcand);
+			if (err) {
+				DEBUG_NOTICE("trice_conn_alloc to"
+					      " %J failed (%m)\n",
+					      &pair->rcand->attr.addr, err);
+				goto out;
+			}
+			break;
+
+		case ICE_TCP_PASSIVE:
+			/* do nothing now. */
+
+			/* we must wait for the other side to create a
+			   TCP-connection to us. when this TCP-connection
+			   is established, we can then send our
+			   Connectivity-check */
+
+			trice_candpair_set_state(pair,
+						 ICE_CANDPAIR_INPROGRESS);
+			break;
+		}
+		break;
+
+	default:
+		err = EPROTONOSUPPORT;
+		goto out;
+	}
+
+	list_append(&ic->conncheckl, &cc->le, cc);
+
+ out:
+	if (err) {
+		mem_deref(cc);
+		trice_candpair_failed(pair, err, 0);
+	}
+
+	return err;
+}
+
+
+int trice_conncheck_trigged(struct trice *icem, struct ice_candpair *pair,
+			   void *sock, bool use_cand)
+{
+	struct ice_checklist *ic;
+	struct ice_conncheck *cc = NULL;
+	int err = 0;
+
+	if (!icem || !pair)
+		return EINVAL;
+
+	ic = icem->checklist;
+	if (!ic) {
+		DEBUG_WARNING("conncheck_send: no checklist\n");
+		return EINVAL;
+	}
+
+	cc = mem_zalloc(sizeof(*cc), conncheck_destructor);
+	if (!cc)
+		return ENOMEM;
+
+	cc->icem = icem;
+	cc->pair = pair;
+	cc->use_cand = use_cand;
+
+	if (pair->state < ICE_CANDPAIR_INPROGRESS)
+		trice_candpair_set_state(pair, ICE_CANDPAIR_INPROGRESS);
+
+	err = trice_conncheck_stun_request(icem->checklist, cc,
+					 pair, sock, use_cand);
+	if (err)
+		goto out;
+
+	list_append(&ic->conncheckl, &cc->le, cc);
+
+ out:
+	if (err) {
+		mem_deref(cc);
+		trice_candpair_failed(pair, err, 0);
+	}
+
+	return err;
+}
+
+
+int trice_conncheck_debug(struct re_printf *pf, const struct ice_conncheck *cc)
+{
+	if (!cc)
+		return 0;
+
+	return re_hprintf(pf, "proto=%s stun=%p use_cand=%d"
+			  " state=%s"
+			  ,
+			  net_proto2name(cc->pair->lcand->attr.proto),
+			  cc->ct_conn, cc->use_cand,
+			  trice_candpair_state2name(cc->pair->state));
+}
diff --git a/src/trice/lcand.c b/src/trice/lcand.c
new file mode 100644
index 0000000..a5ab080
--- /dev/null
+++ b/src/trice/lcand.c
@@ -0,0 +1,625 @@
+/**
+ * @file lcand.c  Local 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_net.h>
+#include <re_sys.h>
+#include <re_stun.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_ice.h>
+#include <re_trice.h>
+#include "trice.h"
+
+
+#define DEBUG_MODULE "icelcand"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static bool tcpconn_frame_handler(struct trice *icem,
+				  struct tcp_conn *tc, struct sa *src,
+				  struct mbuf *mb, void *arg)
+{
+	struct ice_lcand *lcand = arg;
+	(void)icem;
+
+	return lcand->recvh(lcand, IPPROTO_TCP, tc,
+			    src, mb, lcand->arg);
+}
+
+
+static void tcp_conn_handler(const struct sa *peer, void *arg)
+{
+	struct ice_lcand *lcand = arg;
+	int err;
+
+#if 0
+	trice_printf(lcand->icem,
+		    "[local=%H] incoming TCP-connect from %J\n",
+		    trice_cand_print, lcand, peer);
+#endif
+
+	err = trice_conn_alloc(&lcand->icem->connl, lcand->icem,
+			     lcand->attr.compid, false,
+			     &lcand->attr.addr, peer, lcand->ts, lcand->layer,
+			     tcpconn_frame_handler, lcand);
+	if (err) {
+		DEBUG_WARNING("ice_conn_alloc error (%m)\n", err);
+	}
+}
+
+
+static void lcand_destructor(void *arg)
+{
+	struct ice_lcand *cand = arg;
+
+	list_unlink(&cand->le);
+
+	mem_deref(cand->ts);
+	mem_deref(cand->uh);
+	mem_deref(cand->us);
+}
+
+
+/** Foundation is a hash of IP address and candidate type */
+static int compute_foundation(struct ice_lcand *cand,
+			      const struct sa *addr, enum ice_cand_type type)
+{
+	uint32_t v;
+
+	v  = sa_hash(addr, SA_ADDR);
+	v ^= type;
+
+	if (re_snprintf(cand->attr.foundation, sizeof(cand->attr.foundation),
+			"%08x", v) < 0)
+		return ENOMEM;
+
+	return 0;
+}
+
+
+static bool trice_lcand_recv_handler(struct ice_lcand *lcand,
+				  int proto, void *sock, const struct sa *src,
+				  struct mbuf *mb, void *arg)
+{
+	struct trice *icem = arg;
+
+	return trice_stun_process(icem, lcand, proto, sock, src, mb);
+}
+
+
+int trice_add_lcandidate(struct ice_lcand **candp,
+			 struct trice *icem, struct list *lst,
+			 unsigned compid, char *foundation, int proto,
+			 uint32_t prio, const struct sa *addr,
+			 const struct sa *base_addr,
+			 enum ice_cand_type type,
+			 const struct sa *rel_addr,
+			 enum ice_tcptype tcptype)
+{
+	struct ice_lcand *cand;
+	int err = 0;
+
+	if (!lst || !compid || !proto || !addr)
+		return EINVAL;
+
+	cand = mem_zalloc(sizeof(*cand), lcand_destructor);
+	if (!cand)
+		return ENOMEM;
+
+	cand->attr.compid = compid;
+	if (foundation)
+		str_ncpy(cand->attr.foundation, foundation,
+			       sizeof(cand->attr.foundation));
+	else
+		err = compute_foundation(cand, addr, type);
+	cand->attr.proto  = proto;
+	cand->attr.prio   = prio;
+	cand->attr.addr   = *addr;
+	cand->attr.type   = type;
+	cand->attr.tcptype = tcptype;
+	if (rel_addr)
+		cand->attr.rel_addr = *rel_addr;
+	if (err)
+		goto out;
+
+	cand->icem = icem;
+
+	cand->recvh = trice_lcand_recv_handler;
+	cand->arg = icem;
+
+	if (base_addr)
+		cand->base_addr = *base_addr;
+
+	list_append(lst, &cand->le, cand);
+
+ out:
+	if (err)
+		mem_deref(cand);
+	else if (candp)
+		*candp = cand;
+
+	return err;
+}
+
+
+/* this one is only for Send statistics on Local Candidate */
+static bool udp_helper_send_handler(int *err, struct sa *dst,
+				    struct mbuf *mb, void *arg)
+{
+	struct ice_lcand *lcand = arg;
+	(void)err;
+	(void)dst;
+	(void)mb;
+
+	lcand->stats.n_tx += 1;
+
+	return false;  /* NOT handled */
+}
+
+
+/*
+ * lcand: on which Local Candidate to receive the packet
+ *
+ * return TRUE if handled
+ */
+static bool udp_helper_recv_handler(struct sa *src, struct mbuf *mb, void *arg)
+{
+	struct ice_lcand *lcand = arg;
+
+	lcand->stats.n_rx += 1;
+
+	return lcand->recvh(lcand, IPPROTO_UDP, lcand->us,
+			    src, mb, lcand->arg);
+}
+
+
+/* The incoming data should not get here */
+static void dummy_udp_recv(const struct sa *src, struct mbuf *mb, void *arg)
+{
+	struct ice_lcand *lcand = arg;
+
+	DEBUG_NOTICE("@@@@ NO-ONE cared about this UDP packet? @@@@@"
+		     " (%zu bytes from %J to %s.%J)\n",
+		     mbuf_get_left(mb), src,
+		     ice_cand_type2name(lcand->attr.type),
+		     &lcand->attr.addr);
+}
+
+static int udp_listen_range(struct udp_sock **usp, const struct sa *ip,
+			    uint16_t min_port, uint16_t max_port,
+			    udp_recv_h *rh, void *arg)
+{
+	struct sa laddr;
+	int tries = 64;
+	int err = 0;
+
+	sa_cpy(&laddr, ip);
+
+	/* try hard */
+	while (tries--) {
+		struct udp_sock *us;
+		uint16_t port;
+
+		port = (min_port + (rand_u16() % (max_port - min_port)));
+
+		sa_set_port(&laddr, port);
+		err = udp_listen(&us, &laddr, rh, arg);
+		if (err)
+			continue;
+
+		/* OK */
+		if (usp)
+			*usp = us;
+		break;
+	}
+
+	return err;
+}
+
+
+/*
+ * you can call this at any time
+ *
+ * @param addr HOST:     SA_ADDR portion is used
+ *             non-HOST: SA_ADDR + SA_PORT portion is used
+ *
+ * @param base_addr  Optional
+ * @param rel_addr   Optional
+ *
+ * @param layer  mandatory for HOST and RELAY candidates
+ */
+int trice_lcand_add(struct ice_lcand **lcandp, struct trice *icem,
+		    unsigned compid, int proto,
+		    uint32_t prio, const struct sa *addr,
+		    const struct sa *base_addr,
+		    enum ice_cand_type type, const struct sa *rel_addr,
+		    enum ice_tcptype tcptype,
+		    void *sock, int layer)
+{
+	struct ice_lcand *lcand;
+	int err = 0;
+
+	if (!icem || !compid || !proto || !addr)
+		return EINVAL;
+
+	if (!sa_isset(addr, SA_ADDR)) {
+		DEBUG_WARNING("lcand_add: SA_ADDR is not set\n");
+		return EINVAL;
+	}
+	if (type != ICE_CAND_TYPE_HOST) {
+		if (!sa_isset(addr, SA_PORT)) {
+			DEBUG_WARNING("lcand_add: %s: SA_PORT"
+				      " must be set (%J)\n",
+				      ice_cand_type2name(type), addr);
+			return EINVAL;
+		}
+	}
+
+	/* lookup candidate, replace if PRIO is higher */
+
+	/* TODO: dont look up TCP-ACTIVE types for now (port is zero) */
+	if (proto == IPPROTO_UDP) {
+
+		lcand = trice_lcand_find(icem, -1, compid, proto, addr);
+		if (lcand) {
+			trice_printf(icem,
+				    "add_local[%s.%J] --"
+				    " candidate already exists"
+				    " (%H)\n",
+				    ice_cand_type2name(type), addr,
+				    trice_cand_print, lcand);
+
+			if (prio > lcand->attr.prio)
+				lcand = mem_deref(lcand);
+			else {
+				goto out;
+			}
+		}
+	}
+
+	err = trice_add_lcandidate(&lcand, icem, &icem->lcandl, compid, NULL,
+				   proto, prio, addr, base_addr,
+				   type, rel_addr, tcptype);
+	if (err)
+		return err;
+
+	if (type == ICE_CAND_TYPE_HOST) {
+
+		switch (proto) {
+
+		case IPPROTO_UDP:
+			if (sock) {
+				struct sa laddr;
+
+				lcand->us = mem_ref(sock);
+
+				err = udp_local_get(lcand->us,
+						    &laddr);
+				if (err)
+					goto out;
+
+				lcand->attr.addr = *addr;
+				sa_set_port(&lcand->attr.addr,
+					    sa_port(&laddr));
+			}
+			else {
+				if (icem->ports.min && icem->ports.max) {
+					err = udp_listen_range(
+						      &lcand->us,
+						      addr,
+						      icem->ports.min,
+						      icem->ports.max,
+						      dummy_udp_recv,
+						      lcand);
+				}
+				else {
+					err = udp_listen(&lcand->us, addr,
+							 dummy_udp_recv,
+							 lcand);
+				}
+				if (err)
+					goto out;
+
+				err = udp_local_get(lcand->us,
+						    &lcand->attr.addr);
+				if (err)
+					goto out;
+			}
+
+			err = udp_register_helper(&lcand->uh, lcand->us,
+						  layer,
+						  udp_helper_send_handler,
+						  udp_helper_recv_handler,
+						  lcand);
+			if (err)
+				goto out;
+			break;
+
+		case IPPROTO_TCP:
+
+			/* TCP-transport has 3 variants:
+			   active, passive, so */
+
+			if (lcand->attr.tcptype == ICE_TCP_ACTIVE) {
+
+				/* the port MUST be set to 9 (i.e., Discard) */
+				/*sa_set_port(&lcand->attr.addr, 9); */
+			}
+			else if (lcand->attr.tcptype == ICE_TCP_PASSIVE ||
+				 lcand->attr.tcptype == ICE_TCP_SO) {
+
+				err = tcp_listen(&lcand->ts, addr,
+						 tcp_conn_handler, lcand);
+				if (err)
+					goto out;
+				err = tcp_local_get(lcand->ts,
+						    &lcand->attr.addr);
+				if (err)
+					goto out;
+			}
+			else {
+				err = EPROTONOSUPPORT;
+				goto out;
+			}
+			break;
+
+		default:
+			err = EPROTONOSUPPORT;
+			goto out;
+		}
+	}
+	else if (type == ICE_CAND_TYPE_RELAY) {
+
+		switch (proto) {
+
+		case IPPROTO_UDP:
+
+			if (sock) {
+				lcand->us = mem_ref(sock);
+			}
+			else {
+				err = udp_listen(&lcand->us, NULL,
+						 dummy_udp_recv, lcand);
+				if (err)
+					goto out;
+			}
+
+			err = udp_register_helper(&lcand->uh, lcand->us,
+						  layer,
+						  udp_helper_send_handler,
+						  udp_helper_recv_handler,
+						  lcand);
+			if (err)
+				goto out;
+
+			break;
+
+		default:
+			err = EPROTONOSUPPORT;
+			goto out;
+		}
+	}
+	else if (type == ICE_CAND_TYPE_SRFLX ||
+		 type == ICE_CAND_TYPE_PRFLX) {
+
+		/* Special case for SRFLX UDP candidates, if he has
+		 * its own UDP-socket that can be used.
+		 */
+		if (proto == IPPROTO_UDP && sock) {
+
+			lcand->us = mem_ref(sock);
+
+			err = udp_register_helper(&lcand->uh, lcand->us,
+						  layer,
+						  udp_helper_send_handler,
+						  udp_helper_recv_handler,
+						  lcand);
+			if (err)
+				goto out;
+		}
+	}
+
+	lcand->layer = layer;
+
+	if (icem->lrole != ICE_ROLE_UNKNOWN) {
+		/* pair this local-candidate with all existing
+		 * remote-candidates */
+		err = trice_candpair_with_local(icem, lcand);
+		if (err)
+			goto out;
+
+		/* new pair -- refresh the checklist timer */
+		trice_checklist_refresh(icem);
+	}
+
+ out:
+	if (err)
+		mem_deref(lcand);
+	else if (lcandp)
+		*lcandp = lcand;
+
+	return err;
+}
+
+
+struct ice_lcand *trice_lcand_find(struct trice *icem,
+				   enum ice_cand_type type,
+				   unsigned compid, int proto,
+				   const struct sa *addr)
+{
+	struct list *lst;
+	struct le *le;
+
+	if (!icem)
+		return NULL;
+
+	if (!proto) {
+		DEBUG_WARNING("find_candidate: invalid args\n");
+		return NULL;
+	}
+
+	lst = &icem->lcandl;
+
+	for (le = list_head(lst); le; le = le->next) {
+
+		struct ice_cand_attr *cand = le->data;
+
+		if (type != (enum ice_cand_type)-1 && type != cand->type)
+			continue;
+
+		if (compid && cand->compid != compid)
+			continue;
+
+		if (cand->proto != proto)
+			continue;
+
+		if (addr && !sa_cmp(&cand->addr, addr, SA_ALL))
+			continue;
+
+		return (void *)cand;
+	}
+
+	return NULL;
+}
+
+
+struct ice_lcand *trice_lcand_find2(const struct trice *icem,
+				    enum ice_cand_type type, int af)
+{
+	struct le *le;
+
+	if (!icem)
+		return NULL;
+
+	for (le = list_head(&icem->lcandl); le; le = le->next) {
+
+		struct ice_cand_attr *cand = le->data;
+
+		if (cand->type != type)
+			continue;
+
+		if (af != sa_af(&cand->addr))
+			continue;
+
+		return (void *)cand;
+	}
+
+	return NULL;
+}
+
+
+int trice_lcands_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_lcand *cand = le->data;
+
+		err |= re_hprintf(pf, "  {%u} [tx=%3zu, rx=%3zu] "
+				  "fnd=%-8s prio=%08x ",
+				  cand->attr.compid,
+				  cand->stats.n_tx,
+				  cand->stats.n_rx,
+				  cand->attr.foundation,
+				  cand->attr.prio);
+
+		if (str_isset(cand->ifname))
+			err |= re_hprintf(pf, "%s:", cand->ifname);
+
+		err |= re_hprintf(pf, "%24H", trice_cand_print, cand);
+
+		if (sa_isset(&cand->base_addr, SA_ADDR)) {
+			err |= re_hprintf(pf, " (base-addr = %J)",
+					  &cand->base_addr);
+		}
+
+		if (sa_isset(&cand->attr.rel_addr, SA_ADDR)) {
+			err |= re_hprintf(pf, " (rel-addr = %J)",
+					  &cand->attr.rel_addr);
+		}
+
+		err |= re_hprintf(pf, "\n");
+	}
+
+	return err;
+}
+
+
+void *trice_lcand_sock(struct trice *icem, const struct ice_lcand *lcand)
+{
+	struct ice_lcand *base = NULL;
+
+	if (!icem || !lcand)
+		return NULL;
+
+	if (sa_isset(&lcand->base_addr, SA_ALL)) {
+
+		enum ice_cand_type base_type;
+
+		base_type = ice_cand_type_base(lcand->attr.type);
+
+		base = trice_lcand_find(icem,
+					base_type,
+					lcand->attr.compid,
+					lcand->attr.proto,
+					&lcand->base_addr);
+	}
+
+	/* note: original lcand has presedence, fallback to base-candidate */
+	switch (lcand->attr.type) {
+
+	case ICE_CAND_TYPE_HOST:
+		return lcand->us;
+
+	case ICE_CAND_TYPE_SRFLX:
+	case ICE_CAND_TYPE_PRFLX:
+		if (lcand->us)
+			return lcand->us;
+		else if (base && base->us)
+			return base->us;
+		else {
+			DEBUG_NOTICE("lcand_sock: no SOCK or BASE for "
+				     " type '%s'\n",
+				     ice_cand_type2name(lcand->attr.type));
+			return NULL;
+		}
+		break;
+
+	case ICE_CAND_TYPE_RELAY:
+		return lcand->us;
+
+	default:
+		return NULL;
+	}
+
+	return NULL;
+}
+
+
+void trice_lcand_recv_packet(struct ice_lcand *lcand,
+			     const struct sa *src, struct mbuf *mb)
+{
+	struct sa addr;
+
+	if (!lcand || !src || !mb)
+		return;
+
+	addr = *src;
+
+	udp_helper_recv_handler(&addr, mb, lcand);
+}
diff --git a/src/trice/mod.mk b/src/trice/mod.mk
new file mode 100644
index 0000000..c830b2d
--- /dev/null
+++ b/src/trice/mod.mk
@@ -0,0 +1,15 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS	+= trice/cand.c
+SRCS	+= trice/candpair.c
+SRCS	+= trice/chklist.c
+SRCS	+= trice/connchk.c
+SRCS	+= trice/lcand.c
+SRCS	+= trice/rcand.c
+SRCS	+= trice/stunsrv.c
+SRCS	+= trice/tcpconn.c
+SRCS	+= trice/trice.c
diff --git a/src/trice/rcand.c b/src/trice/rcand.c
new file mode 100644
index 0000000..9516699
--- /dev/null
+++ b/src/trice/rcand.c
@@ -0,0 +1,205 @@
+/**
+ * @file rcand.c  Remote ICE Candidates
+ *
+ * Copyright (C) 2010 - 2015 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 <re_trice.h>
+#include "trice.h"
+
+
+#define DEBUG_MODULE "rcand"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static void rcand_destructor(void *data)
+{
+	struct ice_rcand *cand = data;
+
+	list_unlink(&cand->le);
+}
+
+
+static int trice_add_rcandidate(struct ice_rcand **candp,
+			       struct list *lst,
+			       unsigned compid, const char *foundation,
+			       int proto,
+			       uint32_t prio, const struct sa *addr,
+			       enum ice_cand_type type,
+			       enum ice_tcptype tcptype)
+{
+	struct ice_rcand *cand;
+	int err = 0;
+
+	if (!lst || !compid || !foundation || !proto || !addr)
+		return EINVAL;
+
+	cand = mem_zalloc(sizeof(*cand), rcand_destructor);
+	if (!cand)
+		return ENOMEM;
+
+	cand->attr.compid = compid;
+	str_ncpy(cand->attr.foundation, foundation,
+		 sizeof(cand->attr.foundation));
+	cand->attr.proto  = proto;
+	cand->attr.prio   = prio;
+	cand->attr.addr   = *addr;
+	cand->attr.type   = type;
+	cand->attr.tcptype = tcptype;
+
+	if (err)
+		goto out;
+
+	list_append(lst, &cand->le, cand);
+
+ out:
+	if (err)
+		mem_deref(cand);
+	else if (candp)
+		*candp = cand;
+
+	return err;
+}
+
+
+/* you can call this at any time */
+int trice_rcand_add(struct ice_rcand **rcandp, struct trice *icem,
+		    unsigned compid, const char *foundation,
+		    int proto, uint32_t prio,
+		    const struct sa *addr, enum ice_cand_type type,
+		    enum ice_tcptype tcptype)
+{
+	struct ice_rcand *rcand;
+	int sa_flags = SA_ADDR;
+	int err = 0;
+
+	if (!icem || !foundation)
+		return EINVAL;
+
+	if (proto == IPPROTO_UDP)
+		sa_flags |= SA_PORT;
+
+	if (proto == IPPROTO_TCP &&
+	    (tcptype == ICE_TCP_PASSIVE || tcptype == ICE_TCP_SO))
+		sa_flags |= SA_PORT;
+
+	if (!sa_isset(addr, sa_flags)) {
+		DEBUG_WARNING("add_remote_candidate: invalid address"
+			      " (%J) for %s.%s\n",
+			      addr, net_proto2name(proto),
+			      ice_tcptype_name(tcptype));
+		return EINVAL;
+	}
+
+	/* avoid duplicates */
+	rcand = trice_rcand_find(icem, compid, proto, addr);
+	if (rcand) {
+
+		if (rcand->attr.type == ICE_CAND_TYPE_PRFLX &&
+		    prio > rcand->attr.prio) {
+
+			rcand->attr.type = type;
+			rcand->attr.prio = prio;
+		}
+
+		goto out;
+	}
+
+	err = trice_add_rcandidate(&rcand, &icem->rcandl,
+				 compid, foundation,
+				 proto, prio, addr, type, tcptype);
+	if (err)
+		goto out;
+
+	if (icem->lrole != ICE_ROLE_UNKNOWN) {
+		/* pair this remote-candidate with all existing
+		 * local-candidates */
+		err = trice_candpair_with_remote(icem, rcand);
+		if (err)
+			goto out;
+
+		/* new pair -- refresh the checklist timer */
+		trice_checklist_refresh(icem);
+	}
+
+ out:
+	if (err)
+		mem_deref(rcand);
+	else if (rcandp)
+		*rcandp = rcand;
+
+	return err;
+}
+
+
+struct ice_rcand *trice_rcand_find(struct trice *icem,
+				   unsigned compid, int proto,
+				   const struct sa *addr)
+{
+	struct list *lst;
+	struct le *le;
+
+	if (!icem)
+		return NULL;
+
+	if (!proto) {
+		DEBUG_WARNING("find_candidate: invalid args\n");
+		return NULL;
+	}
+
+	lst = &icem->rcandl;
+
+	for (le = list_head(lst); le; le = le->next) {
+
+		struct ice_cand_attr *cand = le->data;
+
+		if (compid && cand->compid != compid)
+			continue;
+
+		if (cand->proto != proto)
+			continue;
+
+		if (addr && !sa_cmp(&cand->addr, addr, SA_ALL))
+			continue;
+
+		return (void *)cand;
+	}
+
+	return NULL;
+}
+
+
+int trice_rcands_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_rcand *rcand = le->data;
+
+		err |= re_hprintf(pf, "  {%u} "
+				  "fnd=%-8s prio=%08x %24H",
+				  rcand->attr.compid,
+				  rcand->attr.foundation,
+				  rcand->attr.prio,
+				  trice_cand_print, rcand);
+
+		err |= re_hprintf(pf, "\n");
+	}
+
+	return err;
+}
diff --git a/src/trice/stunsrv.c b/src/trice/stunsrv.c
new file mode 100644
index 0000000..b32785c
--- /dev/null
+++ b/src/trice/stunsrv.c
@@ -0,0 +1,280 @@
+/**
+ * @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_udp.h>
+#include <re_tcp.h>
+#include <re_sys.h>
+#include <re_trice.h>
+#include "trice.h"
+
+
+#define DEBUG_MODULE "stunsrv"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static const char *sw = "ice stunsrv v" VERSION " (" ARCH "/" OS ")";
+
+
+/*
+ * NOTE about TCP-candidates:
+ *
+ *     Note that STUN requests received on a passive TCP candidate
+ *     will typically produce a remote peer reflexive candidate.
+ */
+static int handle_stun_full(struct trice *icem, struct ice_lcand *lcand,
+			    void *sock, const struct sa *src,
+			    uint32_t prio, bool use_cand)
+{
+	struct ice_candpair *pair = NULL;
+	struct ice_rcand *rcand;
+	enum ice_tcptype tcptype_rev;
+	int err = 0;
+
+	trice_tracef(icem, 36,
+		     "[%u] STUNSRV: Rx Binding Request [%H <--- %J] %s\n",
+		     lcand->attr.compid,
+		     trice_cand_print, lcand,
+		     src,
+		     use_cand ? "[USE]" : "");
+
+	tcptype_rev = ice_tcptype_reverse(lcand->attr.tcptype);
+
+	rcand = trice_rcand_find(icem, lcand->attr.compid,
+				 lcand->attr.proto, src);
+	if (!rcand) {
+
+		if (icem->conf.enable_prflx) {
+
+			err = trice_rcand_add(&rcand, icem,
+					      lcand->attr.compid,
+					      "444", lcand->attr.proto, prio,
+					      src, ICE_CAND_TYPE_PRFLX,
+					      tcptype_rev);
+			if (err)
+				return err;
+
+			trice_printf(icem, "{%u} added PRFLX "
+				     "remote candidate (%H)\n",
+				     lcand->attr.compid,
+				     trice_cand_print, rcand);
+		}
+	}
+
+	/* already valid, skip */
+	pair = trice_candpair_find(&icem->validl, lcand, rcand);
+	if (pair)
+		goto out;
+
+	/* note: the candidate-pair can exist in either list */
+	pair = trice_candpair_find(&icem->checkl, lcand, rcand);
+	if (!pair) {
+		if (icem->conf.enable_prflx) {
+			DEBUG_WARNING("{%u} candidate pair not found:"
+				      " source=%J\n",
+				      lcand->attr.compid, src);
+		}
+		goto out;
+	}
+
+	/* 7.2.1.5.  Updating the Nominated Flag */
+	if (use_cand) {
+		if (icem->lrole == ICE_ROLE_CONTROLLED) {
+
+			pair->nominated = true;
+		}
+	}
+
+ out:
+	/*
+	  send a triggered request
+	 */
+	if (pair && use_cand) {
+
+		if (icem->checklist && !pair->trigged) {
+
+			err = trice_conncheck_trigged(icem, pair,
+						     sock, use_cand);
+			if (err) {
+				DEBUG_WARNING("ice_checklist_stun_request"
+					      " failed (%m)\n",
+					      err);
+			}
+			pair->trigged = true;
+		}
+	}
+
+	return 0;
+}
+
+
+static int stunsrv_ereply(struct trice *icem, struct ice_lcand *lcand,
+			  void *sock, const struct sa *src,
+			  size_t presz, const struct stun_msg *req,
+			  uint16_t scode, const char *reason)
+{
+	DEBUG_WARNING("[%H] replying error to %J (%u %s)\n",
+		      trice_cand_print, lcand,
+		      src,
+		      scode, reason);
+
+	trice_tracef(icem, 31,
+		     "[%u] STUNSRV: Tx error [%J <--- %H] (%u %s)\n",
+		     lcand->attr.compid,
+		     src,
+		     trice_cand_print, lcand,
+		     scode, reason);
+
+	return stun_ereply(lcand->attr.proto, sock, src, presz, req,
+			   scode, reason,
+			   (uint8_t *)icem->lpwd, strlen(icem->lpwd), true, 1,
+			   STUN_ATTR_SOFTWARE, icem->sw ? icem->sw : sw);
+}
+
+
+int trice_stund_recv(struct trice *icem, struct ice_lcand *lcand,
+		    void *sock, const struct sa *src,
+		    struct stun_msg *req, size_t presz)
+{
+	struct stun_attr *attr;
+	struct pl lu, ru;
+	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) {
+		DEBUG_WARNING("message-integrity failed (src=%J)\n", src);
+		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)) {
+		DEBUG_WARNING("local ufrag err (expected %s, actual %r)\n",
+			      icem->lufrag, &lu);
+		goto unauth;
+	}
+	if (str_isset(icem->rufrag) && pl_strcmp(&ru, icem->rufrag)) {
+		DEBUG_WARNING("remote ufrag err (expected %s, actual %r)\n",
+			      icem->rufrag, &ru);
+		goto unauth;
+	}
+
+	if (icem->lrole == ICE_ROLE_UNKNOWN) {
+		err = trice_reqbuf_append(icem, lcand, sock, src, req, presz);
+		if (err) {
+			DEBUG_WARNING("unable to buffer STUN request: %m\n",
+				      err);
+		}
+	}
+
+	return trice_stund_recv_role_set(icem, lcand, sock, src, req, presz);
+
+ badmsg:
+	return stunsrv_ereply(icem, lcand, sock, src, presz, req,
+			      400, "Bad Request");
+
+ unauth:
+	return stunsrv_ereply(icem, lcand, sock, src, presz, req,
+			      401, "Unauthorized");
+}
+
+
+int trice_stund_recv_role_set(struct trice *icem, struct ice_lcand *lcand,
+		    void *sock, const struct sa *src,
+		    struct stun_msg *req, size_t presz)
+{
+	struct stun_attr *attr;
+	enum ice_role remote_role = ICE_ROLE_UNKNOWN;
+	uint64_t tiebrk = 0;
+	uint32_t prio_prflx;
+	int err;
+	bool use_cand = false;
+
+	attr = stun_msg_attr(req, STUN_ATTR_CONTROLLED);
+	if (attr) {
+		remote_role = ICE_ROLE_CONTROLLED;
+		tiebrk = attr->v.uint64;
+	}
+
+	attr = stun_msg_attr(req, STUN_ATTR_CONTROLLING);
+	if (attr) {
+		remote_role = ICE_ROLE_CONTROLLING;
+		tiebrk = attr->v.uint64;
+	}
+
+	if (remote_role == ICE_ROLE_UNKNOWN)
+		goto badmsg;
+
+	if (remote_role == icem->lrole) {
+		DEBUG_NOTICE("role conflict detected (both %s)\n",
+			     ice_role2name(remote_role));
+
+		if (icem->tiebrk >= tiebrk)
+			trice_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;
+
+	err = handle_stun_full(icem, lcand, sock, src, prio_prflx, use_cand);
+
+	if (err)
+		goto badmsg;
+
+	trice_tracef(icem, 32,
+		     "[%u] STUNSRV: Tx success respons [%H ---> %J]\n",
+		     lcand->attr.compid,
+		     trice_cand_print, lcand, src);
+
+	return stun_reply(lcand->attr.proto, sock, src, presz, req,
+			  (uint8_t *)icem->lpwd, strlen(icem->lpwd), true, 2,
+			  STUN_ATTR_XOR_MAPPED_ADDR, src,
+			  STUN_ATTR_SOFTWARE, icem->sw ? icem->sw : sw);
+
+
+ badmsg:
+	return stunsrv_ereply(icem, lcand, sock, src, presz, req,
+			      400, "Bad Request");
+
+ conflict:
+	return stunsrv_ereply(icem, lcand, sock, src, presz, req,
+			      487, "Role Conflict");
+}
diff --git a/src/trice/tcpconn.c b/src/trice/tcpconn.c
new file mode 100644
index 0000000..5159ee7
--- /dev/null
+++ b/src/trice/tcpconn.c
@@ -0,0 +1,286 @@
+/**
+ * @file tcpconn.c  ICE handling of TCP-connections
+ *
+ * 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_tcp.h>
+#include <re_udp.h>
+#include <re_stun.h>
+#include <re_ice.h>
+#include <re_shim.h>
+#include <re_trice.h>
+#include "trice.h"
+
+
+#define DEBUG_MODULE "tcpconn"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/* `mb' contains a complete frame */
+static bool shim_frame_handler(struct mbuf *mb, void *arg)
+{
+	struct ice_tcpconn *conn = arg;
+
+	return conn->frameh(conn->icem, conn->tc, &conn->paddr, mb, conn->arg);
+}
+
+
+static void tcp_estab_handler(void *arg)
+{
+	struct ice_tcpconn *conn = arg;
+	struct trice *icem = conn->icem;
+	struct le *le;
+	int err;
+
+	conn->estab = true;
+
+	trice_printf(icem, "TCP established (local=%J <---> peer=%J)\n",
+		    &conn->laddr, &conn->paddr);
+
+	err = shim_insert(&conn->shim, conn->tc, conn->layer,
+			  shim_frame_handler, conn);
+	if (err)
+		goto out;
+
+	if (!icem->checklist)
+		goto out;
+
+	/* check all pending CONNCHECKs for TCP */
+	le = icem->checklist->conncheckl.head;
+	while (le) {
+		struct ice_conncheck *cc = le->data;
+		struct ice_candpair *pair = cc->pair;
+		le = le->next;
+
+		if (pair->state == ICE_CANDPAIR_INPROGRESS &&
+		    pair->lcand->attr.compid == conn->compid &&
+		    pair->lcand->attr.proto == IPPROTO_TCP &&
+		    sa_cmp(&pair->lcand->attr.addr, &conn->laddr, SA_ADDR) &&
+		    sa_cmp(&pair->rcand->attr.addr, &conn->paddr, SA_ALL)) {
+
+			trice_printf(icem,
+				    "   estab: sending pending check"
+				    " from %j to %J\n",
+				    &pair->lcand->attr.addr,
+				    &pair->rcand->attr.addr);
+
+			/* todo: */
+			pair->conn = mem_ref(conn);
+
+			err = trice_conncheck_stun_request(icem->checklist, cc,
+							 pair, conn->tc,
+							 cc->use_cand);
+			if (err) {
+				DEBUG_WARNING("stun_request error (%m)\n",
+					      err);
+			}
+		}
+	}
+
+ out:
+	if (err) {
+		DEBUG_WARNING("estab: errors (%m)\n", err);
+	}
+}
+
+
+/* todo: re-connect if estab and active (with a timer) */
+static void tcp_close_handler(int err, void *arg)
+{
+	struct ice_tcpconn *conn = arg;
+	struct trice *icem = conn->icem;
+	struct le *le;
+
+	trice_printf(conn->icem, "TCP-connection [%J -> %J] closed (%m)\n",
+		    &conn->laddr, &conn->paddr, err);
+
+	err = err ? err : ECONNRESET;
+
+	/* note: helper must be closed before tc */
+	conn->shim = mem_deref(conn->shim);
+	conn->tc = mem_deref(conn->tc);
+
+	/* todo: iterate through conncheckl and cancel all checks
+	 * that are using this conn
+	 */
+
+	le = conn->icem->checkl.head;
+	while (le) {
+		struct ice_candpair *pair = le->data;
+
+		le = le->next;
+
+		if (pair->lcand->attr.compid == conn->compid &&
+		    pair->lcand->attr.proto == IPPROTO_TCP &&
+		    sa_cmp(&pair->rcand->attr.addr, &conn->paddr, SA_ALL)) {
+
+			trice_candpair_failed(pair, err, 0);
+
+			if (icem->checklist) {
+				icem->checklist->failh(err, 0,
+						       pair,
+						       icem->checklist->arg);
+			}
+		}
+	}
+
+	mem_deref(conn);
+}
+
+
+static void conn_destructor(void *arg)
+{
+	struct ice_tcpconn *conn = arg;
+
+	list_unlink(&conn->le);
+	mem_deref(conn->shim);
+	mem_deref(conn->tc);
+}
+
+
+/* ts: only for accept */
+int trice_conn_alloc(struct list *connl, struct trice *icem, unsigned compid,
+		   bool active, const struct sa *laddr, const struct sa *peer,
+		   struct tcp_sock *ts, int layer,
+		   tcpconn_frame_h *frameh, void *arg)
+{
+	struct ice_tcpconn *conn;
+	int err = 0;
+
+	if (!connl || !icem || !laddr || !peer || !frameh)
+		return EINVAL;
+
+	conn = mem_zalloc(sizeof(*conn), conn_destructor);
+	if (!conn)
+		return ENOMEM;
+
+	conn->icem = icem;
+	conn->active = active;
+	conn->paddr = *peer;
+	conn->compid = compid;
+	conn->layer = layer;
+	conn->frameh = frameh;
+	conn->arg = arg;
+
+	if (active) {
+
+		trice_printf(conn->icem, "<%p> TCP connecting"
+			    " [laddr=%J paddr=%J] ..\n",
+			    icem, laddr, peer);
+
+		/* This connection is opened from the local candidate of the
+		   pair to the remote candidate of the pair.
+		 */
+		err = tcp_conn_alloc(&conn->tc, peer, tcp_estab_handler,
+				     NULL, tcp_close_handler,
+				     conn);
+		if (err) {
+			DEBUG_WARNING("tcp_conn_alloc [peer=%J] (%m)\n",
+				      peer, err);
+			goto out;
+		}
+
+		err = tcp_conn_bind(conn->tc, laddr);
+		if (err) {
+			DEBUG_WARNING("tcp_conn_bind [laddr=%J paddr=%J]"
+				      " (%m)\n",
+				      laddr, peer, err);
+			goto out;
+		}
+
+		err = tcp_conn_connect(conn->tc, peer);
+		if (err) {
+			/* NOTE: this happens sometimes on OSX when
+			 *       setting up two S-O connections
+			 */
+			if (err == EADDRINUSE) {
+				re_printf("EADDRINUSE\n");
+				err = 0;
+			}
+			else {
+				DEBUG_NOTICE("tcp_conn_connect [peer=%J]"
+					      " (%d/%m)\n",
+					      peer, err, err);
+				goto out;
+			}
+		}
+	}
+	else {
+		err = tcp_accept(&conn->tc, ts, tcp_estab_handler,
+				 NULL, tcp_close_handler, conn);
+		if (err) {
+			tcp_reject(ts);
+			goto out;
+		}
+	}
+
+	err = tcp_conn_local_get(conn->tc, &conn->laddr);
+	if (err)
+		goto out;
+
+	list_append(connl, &conn->le, conn);
+
+ out:
+	if (err)
+		mem_deref(conn);
+
+	return err;
+}
+
+
+/* NOTE: laddr matching is SA_ADDR only */
+struct ice_tcpconn *trice_conn_find(struct list *connl, unsigned compid,
+				  const struct sa *laddr,
+				  const struct sa *peer)
+{
+	struct le *le;
+
+	for (le = list_head(connl); le; le = le->next) {
+
+		struct ice_tcpconn *conn = le->data;
+
+		if (compid != conn->compid)
+			continue;
+
+		/* NOTE: only for established */
+		if (!conn->estab)
+			continue;
+
+		if (sa_cmp(laddr, &conn->laddr, SA_ADDR) &&
+		    sa_cmp(peer, &conn->paddr, SA_ALL))
+			return conn;
+	}
+
+	return NULL;
+}
+
+
+int trice_conn_debug(struct re_printf *pf, const struct ice_tcpconn *conn)
+{
+	int err;
+
+	if (!conn)
+		return 0;
+
+	err = re_hprintf(pf, "... {%u} [%s|%5s] %J - %J "
+			  " (usage = %u) ",
+			  conn->compid,
+			  conn->active ? "Active" : "Passive",
+			  conn->estab ? "ESTAB" : "     ",
+			  &conn->laddr, &conn->paddr,
+			  mem_nrefs(conn)-1);
+
+	if (conn->shim)
+		err |= shim_debug(pf, conn->shim);
+
+	return err;
+}
diff --git a/src/trice/trice.c b/src/trice/trice.c
new file mode 100644
index 0000000..780226d
--- /dev/null
+++ b/src/trice/trice.c
@@ -0,0 +1,551 @@
+/**
+ * @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_ice.h>
+#include <re_sys.h>
+#include <re_trice.h>
+#include "trice.h"
+
+
+#define DEBUG_MODULE "icem"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+static const struct trice_conf conf_default = {
+	ICE_NOMINATION_REGULAR,
+	false,
+	false,
+	false,
+	true,
+	false
+};
+
+
+static void trice_destructor(void *data)
+{
+	struct trice *icem = data;
+
+	mem_deref(icem->checklist);
+
+	list_flush(&icem->validl);
+	list_flush(&icem->checkl);
+	list_flush(&icem->lcandl);
+	list_flush(&icem->rcandl);
+	list_flush(&icem->reqbufl);
+
+	list_flush(&icem->connl);
+
+	mem_deref(icem->rufrag);
+	mem_deref(icem->rpwd);
+	mem_deref(icem->lufrag);
+	mem_deref(icem->lpwd);
+	mem_deref(icem->sw);
+}
+
+
+/**
+ * Allocate a new ICE Media object
+ *
+ * @param icemp       Pointer to allocated ICE Media object
+ * @param conf        ICE configuration
+ * @param role        Local role
+ * @param lufrag      Local username fragment
+ * @param lpwd        Local password
+ * @param estabh      Candidate pair established handler
+ * @param closeh      Close / error handler
+ * @param arg         Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+
+int trice_alloc(struct trice **icemp, const struct trice_conf *conf,
+	       enum ice_role role,
+	       const char *lufrag, const char *lpwd)
+{
+	struct trice *icem;
+	int err = 0;
+
+	if (!icemp || !lufrag || !lpwd)
+		return EINVAL;
+
+	if (str_len(lufrag) < 4 || str_len(lpwd) < 22) {
+		DEBUG_WARNING("alloc: lufrag/lpwd is too short\n");
+		return EINVAL;
+	}
+
+	icem = mem_zalloc(sizeof(*icem), trice_destructor);
+	if (!icem)
+		return ENOMEM;
+
+	icem->conf = conf ? *conf : conf_default;
+	list_init(&icem->reqbufl);
+	list_init(&icem->lcandl);
+	list_init(&icem->rcandl);
+	list_init(&icem->checkl);
+	list_init(&icem->validl);
+
+	icem->lrole = role;
+	icem->tiebrk = rand_u64();
+
+	err |= str_dup(&icem->lufrag, lufrag);
+	err |= str_dup(&icem->lpwd, lpwd);
+	if (err)
+		goto out;
+
+ out:
+	if (err)
+		mem_deref(icem);
+	else
+		*icemp = icem;
+
+	return err;
+}
+
+
+int trice_set_remote_ufrag(struct trice *icem, const char *rufrag)
+{
+	if (!icem || !rufrag)
+		return EINVAL;
+
+	icem->rufrag = mem_deref(icem->rufrag);
+	return str_dup(&icem->rufrag, rufrag);
+}
+
+
+int trice_set_remote_pwd(struct trice *icem, const char *rpwd)
+{
+	if (!icem || !rpwd)
+		return EINVAL;
+
+	icem->rpwd = mem_deref(icem->rpwd);
+
+	return str_dup(&icem->rpwd, rpwd);
+}
+
+
+int trice_set_software(struct trice *icem, const char *sw)
+{
+	if (!icem)
+		return EINVAL;
+
+	icem->sw = mem_deref(icem->sw);
+
+	if (sw)
+		return str_dup(&icem->sw, sw);
+
+	return 0;
+}
+
+
+struct trice_conf *trice_conf(struct trice *icem)
+{
+	return icem ? &icem->conf : NULL;
+}
+
+
+/* note: call this ONCE AFTER role has been set */
+static void trice_create_candpairs(struct trice *icem)
+{
+	struct list *lst;
+	struct le *le;
+	bool refresh_checklist = false;
+	int err;
+
+	lst = &icem->lcandl;
+	for (le = list_head(lst); le; le = le->next) {
+		struct ice_lcand *lcand = le->data;
+
+		/* pair this local-candidate with all existing
+		 * remote-candidates */
+		err = trice_candpair_with_local(icem, lcand);
+		if (err) {
+			DEBUG_WARNING("trice_candpair_with_local: %m\n", err);
+		}
+		else {
+			refresh_checklist = true;
+		}
+	}
+
+	lst = &icem->rcandl;
+	for (le = list_head(lst); le; le = le->next) {
+		struct ice_rcand *rcand = le->data;
+
+		/* pair this remote-candidate with all existing
+		 * local-candidates */
+		err = trice_candpair_with_remote(icem, rcand);
+		if (err) {
+			DEBUG_WARNING("trice_candpair_with_remote: %m\n", err);
+		}
+		else {
+			refresh_checklist = true;
+		}
+	}
+
+	/* new pair -- refresh the checklist timer */
+	if (refresh_checklist)
+		trice_checklist_refresh(icem);
+}
+
+
+/* note: call this AFTER role has been set AND candidate pairs
+ * have been created */
+static void trice_reqbuf_process(struct trice *icem)
+{
+	struct le *le;
+
+	le = list_head(&icem->reqbufl);
+	while (le) {
+		struct trice_reqbuf *reqbuf = le->data;
+		le = le->next;
+
+		DEBUG_PRINTF("trice_reqbuf_process: Processing buffered "
+			     "request\n");
+
+		(void)trice_stund_recv_role_set(icem, reqbuf->lcand,
+				reqbuf->sock, &reqbuf->src, reqbuf->req,
+				reqbuf->presz);
+
+		mem_deref(reqbuf);
+	}
+}
+
+
+/**
+ * Set the local role to either CONTROLLING or CONTROLLED.
+ * Note: The role can be set multiple times.
+ *
+ * @param icem ICE Media object
+ * @param role New local role
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int trice_set_role(struct trice *trice, enum ice_role role)
+{
+	bool refresh;
+
+	if (!trice)
+		return EINVAL;
+
+	/* Cannot change the role to unknown */
+	if (role == ICE_ROLE_UNKNOWN)
+		return EINVAL;
+
+	if (trice->lrole == role)
+		return 0;
+
+	/* Cannot switch role manually once it has been set */
+	if (trice->lrole == ICE_ROLE_UNKNOWN)
+		refresh = false;
+	else
+		refresh = true;
+
+	trice->lrole = role;
+
+	/* Create candidate pairs and process pending requests */
+	if (refresh) {
+		trice_candpair_prio_order(&trice->checkl,
+					  role == ICE_ROLE_CONTROLLING);
+	}
+	else {
+		trice_create_candpairs(trice);
+	}
+
+	trice_reqbuf_process(trice);
+
+	return 0;
+}
+
+
+/**
+ * Get the local role
+ *
+ * @param icem  ICE Media object
+ *
+ * @return Local role
+ */
+enum ice_role trice_local_role(const struct trice *icem)
+{
+	if (!icem)
+		return ICE_ROLE_UNKNOWN;
+
+	return icem->lrole;
+}
+
+
+/**
+ * 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 trice_debug(struct re_printf *pf, const struct trice *icem)
+{
+	struct le *le;
+	int err = 0;
+
+	if (!icem)
+		return 0;
+
+	err |= re_hprintf(pf, "----- ICE Media <%p> -----\n", icem);
+
+	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, " Local Candidates: %H",
+			  trice_lcands_debug, &icem->lcandl);
+	err |= re_hprintf(pf, " Remote Candidates: %H",
+			  trice_rcands_debug, &icem->rcandl);
+	err |= re_hprintf(pf, " Check list: ");
+	err |= trice_candpairs_debug(pf, icem->conf.ansi, &icem->checkl);
+
+	err |= re_hprintf(pf, " Valid list: ");
+	err |= trice_candpairs_debug(pf, icem->conf.ansi, &icem->validl);
+
+	err |= re_hprintf(pf, " Buffered STUN Requests: (%u)\n",
+			  list_count(&icem->reqbufl));
+
+	if (icem->checklist)
+		err |= trice_checklist_debug(pf, icem->checklist);
+
+	err |= re_hprintf(pf, " TCP Connections: (%u)\n",
+			  list_count(&icem->connl));
+
+	for (le = list_head(&icem->connl); le; le = le->next) {
+		struct ice_tcpconn *conn = le->data;
+
+		err |= re_hprintf(pf, "      %H\n",
+				  trice_conn_debug, conn);
+	}
+
+	return err;
+}
+
+
+/**
+ * Get the list of Local Candidates (struct cand)
+ *
+ * @param icem ICE Media object
+ *
+ * @return List of Local Candidates
+ */
+struct list *trice_lcandl(const struct trice *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 *trice_rcandl(const struct trice *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 *trice_checkl(const struct trice *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 *trice_validl(const struct trice *icem)
+{
+	return icem ? (struct list *)&icem->validl : NULL;
+}
+
+
+void trice_printf(struct trice *icem, const char *fmt, ...)
+{
+	va_list ap;
+
+	if (!icem || !icem->conf.debug)
+		return;
+
+	va_start(ap, fmt);
+	(void)re_printf("%v", fmt, &ap);
+	va_end(ap);
+}
+
+
+void trice_tracef(struct trice *icem, int color, const char *fmt, ...)
+{
+	va_list ap;
+
+	if (!icem || !icem->conf.trace)
+		return;
+
+	if (icem->conf.ansi && color) {
+		re_printf("\x1b[%dm", color);
+	}
+
+	va_start(ap, fmt);
+	(void)re_printf("%v", fmt, &ap);
+	va_end(ap);
+
+	if (icem->conf.ansi && color) {
+		re_printf("\x1b[;m");
+	}
+}
+
+
+void trice_switch_local_role(struct trice *ice)
+{
+	enum ice_role new_role;
+
+	if (!ice)
+		return;
+
+	switch (ice->lrole) {
+
+	case ICE_ROLE_CONTROLLING:
+		new_role = ICE_ROLE_CONTROLLED;
+		break;
+
+	case ICE_ROLE_CONTROLLED:
+		new_role = ICE_ROLE_CONTROLLING;
+		break;
+
+	default:
+		DEBUG_WARNING("trice_switch_local_role: local role unknown\n");
+		return;
+	}
+
+	DEBUG_NOTICE("Switch local role from %s to %s\n",
+		     ice_role2name(ice->lrole), ice_role2name(new_role));
+
+	ice->lrole = new_role;
+
+	/* recompute pair priorities for all media streams */
+	trice_candpair_prio_order(&ice->checkl,
+				  ice->lrole == ICE_ROLE_CONTROLLING);
+}
+
+
+/* sock = [ struct udp_sock | struct tcp_conn ] */
+bool trice_stun_process(struct trice *icem, struct ice_lcand *lcand,
+		       int proto, void *sock, const struct sa *src,
+		       struct mbuf *mb)
+{
+	struct stun_msg *msg = NULL;
+	struct stun_unknown_attr ua;
+	size_t start = mb->pos;
+	(void)proto;
+
+	if (stun_msg_decode(&msg, mb, &ua)) {
+		return false;  /* continue recv-processing */
+	}
+
+	if (STUN_METHOD_BINDING == stun_msg_method(msg)) {
+
+		switch (stun_msg_class(msg)) {
+
+		case STUN_CLASS_REQUEST:
+			(void)trice_stund_recv(icem, lcand, sock,
+					      src, msg, start);
+			break;
+
+		default:
+			if (icem->checklist) {
+				(void)stun_ctrans_recv(icem->checklist->stun,
+						       msg, &ua);
+			}
+			else {
+				DEBUG_NOTICE("STUN resp from %J dropped"
+					     " (no checklist)\n",
+					     src);
+			}
+			break;
+		}
+	}
+
+	mem_deref(msg);
+
+	return true;
+}
+
+
+static void trice_reqbuf_destructor(void *data)
+{
+	struct trice_reqbuf *reqbuf = data;
+
+	list_unlink(&reqbuf->le);
+
+	mem_deref(reqbuf->req);
+	mem_deref(reqbuf->sock);
+	mem_deref(reqbuf->lcand);
+}
+
+
+int trice_reqbuf_append(struct trice *icem, struct ice_lcand *lcand,
+		    void *sock, const struct sa *src,
+		    struct stun_msg *req, size_t presz)
+{
+	struct trice_reqbuf *reqbuf;
+
+	if (!icem || !src ||!req)
+		return EINVAL;
+
+	reqbuf = mem_zalloc(sizeof(*reqbuf), trice_reqbuf_destructor);
+	if (!reqbuf)
+		return ENOMEM;
+
+	DEBUG_PRINTF("trice_reqbuf_append: Buffering request\n");
+	reqbuf->lcand = mem_ref(lcand);
+	reqbuf->sock = mem_ref(sock);
+	reqbuf->src = *src;
+	reqbuf->req = mem_ref(req);
+	reqbuf->presz = presz;
+
+	list_append(&icem->reqbufl, &reqbuf->le, reqbuf);
+
+	return 0;
+}
+
+int trice_set_port_range(struct trice *trice,
+			 uint16_t min_port, uint16_t max_port)
+{
+	if (!trice)
+		return EINVAL;
+
+	if (max_port < min_port)
+		return ERANGE;
+
+	trice->ports.min = min_port;
+	trice->ports.max = max_port;
+
+	return 0;
+}
diff --git a/src/trice/trice.h b/src/trice/trice.h
new file mode 100644
index 0000000..eafcb2c
--- /dev/null
+++ b/src/trice/trice.h
@@ -0,0 +1,208 @@
+/**
+ * @file ice.h  Internal Interface to ICE
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+struct ice_tcpconn;
+struct ice_conncheck;
+
+
+/**
+ * Active Checklist. Only used by Full-ICE and Trickle-ICE
+ */
+struct ice_checklist {
+	struct trice *icem;     /* parent */
+
+	struct tmr tmr_pace;         /**< Timer for pacing STUN requests     */
+	uint32_t interval;           /**< Interval in [ms]                   */
+	struct stun *stun;           /**< STUN Transport                     */
+	struct list conncheckl;
+	bool is_running;             /**< Checklist is running               */
+
+	/* callback handlers */
+	trice_estab_h *estabh;
+	trice_failed_h *failh;
+	void *arg;
+};
+
+
+/**
+ * Defines an ICE media-stream
+ *
+ * NOTE: We try to follow the Resource Acquisition Is Initialization (RAII)
+ *       programming idiom, which means:
+ *
+ * - at any time is the number of local/remote candidates correct
+ * - at any time is the checklist up to date (matching local/remote candidates)
+ *
+ */
+struct trice {
+	struct trice_conf conf;
+	enum ice_role lrole;         /**< Local role                         */
+	uint64_t tiebrk;             /**< Tie-break value for roleconflict   */
+
+	/* stun/authentication */
+	char *lufrag;                /**< Local Username fragment            */
+	char *lpwd;                  /**< Local Password                     */
+	char *rufrag;                /**< Remote Username fragment           */
+	char *rpwd;                  /**< Remote Password                    */
+
+	struct list lcandl;          /**< local candidates (add order)       */
+	struct list rcandl;          /**< remote candidates (add order)      */
+	struct list checkl;          /**< Check List of cand pairs (sorted)  */
+	struct list validl;          /**< Valid List of cand pairs (sorted)  */
+	struct list reqbufl;         /**< buffered incoming requests         */
+
+	struct ice_checklist *checklist;
+
+	struct list connl;           /**< TCP-connections for all components */
+
+	char *sw;
+
+	/* Port range */
+	struct {
+		uint16_t min;
+		uint16_t max;
+	} ports;
+};
+
+
+/**
+ * Holds an unhandled STUN request message that will be handled once
+ * the role has been determined.
+ */
+struct trice_reqbuf {
+	struct le le;                /**< list element                       */
+	struct ice_lcand *lcand;     /**< corresponding local candidate      */
+	void *sock;                  /**< request's socket                   */
+	struct sa src;               /**< source address                     */
+	struct stun_msg *req;        /**< buffered STUN request              */
+	size_t presz;                /**< number of bytes in preamble        */
+};
+
+
+/* return TRUE if handled */
+typedef bool (tcpconn_frame_h)(struct trice *icem,
+			       struct tcp_conn *tc, struct sa *src,
+			       struct mbuf *mb, void *arg);
+
+/**
+ * Defines a TCP-connection from local-adress to remote-address
+ *
+ * - one TCP-connection can be shared by multiple candidate pairs
+ *
+ * - one TCP-connection is always created by the Local Candidate
+ */
+struct ice_tcpconn {
+	struct trice *icem;      /* parent */
+	struct le le;
+	struct tcp_conn *tc;
+	struct shim *shim;
+	struct sa laddr;
+	struct sa paddr;
+	unsigned compid;
+	int layer;
+	bool active;
+	bool estab;
+
+	tcpconn_frame_h *frameh;
+	void *arg;
+};
+
+struct ice_conncheck {
+	struct le le;
+	struct ice_candpair *pair;    /* pointer */
+	struct stun_ctrans *ct_conn;
+	struct trice *icem;           /* owner */
+	bool use_cand;
+	bool term;
+};
+
+
+/* cand */
+int trice_add_lcandidate(struct ice_lcand **candp,
+			 struct trice *icem, struct list *lst,
+			 unsigned compid, char *foundation, int proto,
+			 uint32_t prio, const struct sa *addr,
+			 const struct sa *base_addr,
+			 enum ice_cand_type type,
+			 const struct sa *rel_addr,
+			 enum ice_tcptype tcptype);
+int trice_lcands_debug(struct re_printf *pf, const struct list *lst);
+int trice_rcands_debug(struct re_printf *pf, const struct list *lst);
+
+
+/* candpair */
+int  trice_candpair_alloc(struct ice_candpair **cpp, struct trice *icem,
+			 struct ice_lcand *lcand, struct ice_rcand *rcand);
+void trice_candpair_prio_order(struct list *lst, bool controlling);
+void trice_candpair_make_valid(struct trice *icem, struct ice_candpair *pair);
+void trice_candpair_failed(struct ice_candpair *cp, int err, uint16_t scode);
+void trice_candpair_set_state(struct ice_candpair *cp,
+			     enum ice_candpair_state state);
+bool trice_candpair_iscompleted(const struct ice_candpair *cp);
+bool trice_candpair_cmp_fnd(const struct ice_candpair *cp1,
+			   const struct ice_candpair *cp2);
+struct ice_candpair *trice_candpair_find(const struct list *lst,
+					const struct ice_lcand *lcand,
+					const struct ice_rcand *rcand);
+int  trice_candpair_with_local(struct trice *icem, struct ice_lcand *lcand);
+int  trice_candpair_with_remote(struct trice *icem, struct ice_rcand *rcand);
+const char    *trice_candpair_state2name(enum ice_candpair_state st);
+
+
+/* STUN server */
+int trice_stund_recv(struct trice *icem, struct ice_lcand *lcand,
+		    void *sock, const struct sa *src,
+		    struct stun_msg *req, size_t presz);
+int trice_stund_recv_role_set(struct trice *icem, struct ice_lcand *lcand,
+		    void *sock, const struct sa *src,
+		    struct stun_msg *req, size_t presz);
+
+
+/* ICE media */
+void trice_switch_local_role(struct trice *ice);
+void trice_printf(struct trice *icem, const char *fmt, ...);
+void trice_tracef(struct trice *icem, int color, const char *fmt, ...);
+
+
+/* ICE checklist */
+int  trice_checklist_debug(struct re_printf *pf,
+			   const struct ice_checklist *ic);
+void trice_conncheck_schedule_check(struct trice *icem);
+int  trice_checklist_update(struct trice *icem);
+void trice_checklist_refresh(struct trice *icem);
+
+
+/* ICE conncheck */
+int trice_conncheck_stun_request(struct ice_checklist *ic,
+			       struct ice_conncheck *cc,
+			       struct ice_candpair *cp, void *sock,
+			       bool cc_use_cand);
+int trice_conncheck_trigged(struct trice *icem, struct ice_candpair *pair,
+			   void *sock, bool use_cand);
+int trice_conncheck_debug(struct re_printf *pf,
+			  const struct ice_conncheck *cc);
+
+
+/* TCP connections */
+
+
+int trice_conn_alloc(struct list *connl, struct trice *icem, unsigned compid,
+		   bool active, const struct sa *laddr, const struct sa *peer,
+		   struct tcp_sock *ts, int layer,
+		   tcpconn_frame_h *frameh, void *arg);
+struct ice_tcpconn *trice_conn_find(struct list *connl, unsigned compid,
+				  const struct sa *laddr,
+				  const struct sa *peer);
+int trice_conn_debug(struct re_printf *pf, const struct ice_tcpconn *conn);
+
+
+bool trice_stun_process(struct trice *icem, struct ice_lcand *lcand,
+		       int proto, void *sock, const struct sa *src,
+		       struct mbuf *mb);
+int trice_reqbuf_append(struct trice *icem, struct ice_lcand *lcand,
+		    void *sock, const struct sa *src,
+		    struct stun_msg *req, size_t presz);