blob: 780226de7812f4738df7ba8e2596dcab02bc36b6 [file] [log] [blame]
/**
* @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;
}