| /** |
| * @file strans.c SIP Server Transaction |
| * |
| * Copyright (C) 2010 Creytiv.com |
| */ |
| #include <re_types.h> |
| #include <re_mem.h> |
| #include <re_mbuf.h> |
| #include <re_sa.h> |
| #include <re_list.h> |
| #include <re_hash.h> |
| #include <re_fmt.h> |
| #include <re_uri.h> |
| #include <re_sys.h> |
| #include <re_tmr.h> |
| #include <re_udp.h> |
| #include <re_msg.h> |
| #include <re_sip.h> |
| #include "sip.h" |
| |
| |
| enum state { |
| TRYING, |
| PROCEEDING, |
| ACCEPTED, |
| COMPLETED, |
| CONFIRMED, |
| }; |
| |
| |
| struct sip_strans { |
| struct le he; |
| struct le he_mrg; |
| struct tmr tmr; |
| struct tmr tmrg; |
| struct sa dst; |
| struct sip *sip; |
| struct sip_msg *msg; |
| struct mbuf *mb; |
| sip_cancel_h *cancelh; |
| void *arg; |
| enum state state; |
| uint32_t txc; |
| bool invite; |
| }; |
| |
| |
| static void destructor(void *arg) |
| { |
| struct sip_strans *st = arg; |
| |
| hash_unlink(&st->he); |
| hash_unlink(&st->he_mrg); |
| tmr_cancel(&st->tmr); |
| tmr_cancel(&st->tmrg); |
| mem_deref(st->msg); |
| mem_deref(st->mb); |
| } |
| |
| |
| static bool strans_cmp(const struct sip_msg *msg1, const struct sip_msg *msg2) |
| { |
| if (pl_cmp(&msg1->via.branch, &msg2->via.branch)) |
| return false; |
| |
| if (pl_cmp(&msg1->via.sentby, &msg2->via.sentby)) |
| return false; |
| |
| return true; |
| } |
| |
| |
| static bool cmp_handler(struct le *le, void *arg) |
| { |
| struct sip_strans *st = le->data; |
| const struct sip_msg *msg = arg; |
| |
| if (!strans_cmp(st->msg, msg)) |
| return false; |
| |
| if (pl_cmp(&st->msg->cseq.met, &msg->cseq.met)) |
| return false; |
| |
| return true; |
| } |
| |
| |
| static bool cmp_ack_handler(struct le *le, void *arg) |
| { |
| struct sip_strans *st = le->data; |
| const struct sip_msg *msg = arg; |
| |
| if (!strans_cmp(st->msg, msg)) |
| return false; |
| |
| if (pl_strcmp(&st->msg->cseq.met, "INVITE")) |
| return false; |
| |
| return true; |
| } |
| |
| |
| static bool cmp_cancel_handler(struct le *le, void *arg) |
| { |
| struct sip_strans *st = le->data; |
| const struct sip_msg *msg = arg; |
| |
| if (!strans_cmp(st->msg, msg)) |
| return false; |
| |
| if (!pl_strcmp(&st->msg->cseq.met, "CANCEL")) |
| return false; |
| |
| return true; |
| } |
| |
| |
| static bool cmp_merge_handler(struct le *le, void *arg) |
| { |
| struct sip_strans *st = le->data; |
| const struct sip_msg *msg = arg; |
| |
| if (pl_cmp(&st->msg->cseq.met, &msg->cseq.met)) |
| return false; |
| |
| if (st->msg->cseq.num != msg->cseq.num) |
| return false; |
| |
| if (pl_cmp(&st->msg->callid, &msg->callid)) |
| return false; |
| |
| if (pl_cmp(&st->msg->from.tag, &msg->from.tag)) |
| return false; |
| |
| if (pl_cmp(&st->msg->ruri, &msg->ruri)) |
| return false; |
| |
| return true; |
| } |
| |
| |
| static void dummy_handler(void *arg) |
| { |
| (void)arg; |
| } |
| |
| |
| static void tmr_handler(void *arg) |
| { |
| struct sip_strans *st = arg; |
| |
| mem_deref(st); |
| } |
| |
| |
| static void retransmit_handler(void *arg) |
| { |
| struct sip_strans *st = arg; |
| |
| (void)sip_send(st->sip, st->msg->sock, st->msg->tp, &st->dst, |
| st->mb); |
| |
| st->txc++; |
| tmr_start(&st->tmrg, MIN(SIP_T1<<st->txc, SIP_T2), retransmit_handler, |
| st); |
| } |
| |
| |
| static bool ack_handler(struct sip *sip, const struct sip_msg *msg) |
| { |
| struct sip_strans *st; |
| |
| st = list_ledata(hash_lookup(sip->ht_strans, |
| hash_joaat_pl(&msg->via.branch), |
| cmp_ack_handler, (void *)msg)); |
| if (!st) |
| return false; |
| |
| switch (st->state) { |
| |
| case ACCEPTED: |
| /* make sure ACKs for 2xx are passed to TU */ |
| return false; |
| |
| case COMPLETED: |
| if (sip_transp_reliable(st->msg->tp)) { |
| mem_deref(st); |
| break; |
| } |
| |
| tmr_start(&st->tmr, SIP_T4, tmr_handler, st); |
| tmr_cancel(&st->tmrg); |
| st->state = CONFIRMED; |
| break; |
| |
| default: |
| break; |
| } |
| |
| return true; |
| } |
| |
| |
| static bool cancel_handler(struct sip *sip, const struct sip_msg *msg) |
| { |
| struct sip_strans *st; |
| |
| st = list_ledata(hash_lookup(sip->ht_strans, |
| hash_joaat_pl(&msg->via.branch), |
| cmp_cancel_handler, (void *)msg)); |
| if (!st) |
| return false; |
| |
| ((struct sip_msg *)msg)->tag = st->msg->tag; |
| |
| (void)sip_reply(sip, msg, 200, "OK"); |
| |
| switch (st->state) { |
| |
| case TRYING: |
| case PROCEEDING: |
| st->cancelh(st->arg); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return true; |
| } |
| |
| |
| static bool request_handler(const struct sip_msg *msg, void *arg) |
| { |
| struct sip_strans *st; |
| struct sip *sip = arg; |
| |
| if (!pl_strcmp(&msg->met, "ACK")) |
| return ack_handler(sip, msg); |
| |
| st = list_ledata(hash_lookup(sip->ht_strans, |
| hash_joaat_pl(&msg->via.branch), |
| cmp_handler, (void *)msg)); |
| if (st) { |
| switch (st->state) { |
| |
| case PROCEEDING: |
| case COMPLETED: |
| (void)sip_send(st->sip, st->msg->sock, st->msg->tp, |
| &st->dst, st->mb); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return true; |
| } |
| else if (!pl_isset(&msg->to.tag)) { |
| |
| st = list_ledata(hash_lookup(sip->ht_strans_mrg, |
| hash_joaat_pl(&msg->callid), |
| cmp_merge_handler, (void *)msg)); |
| if (st) { |
| (void)sip_reply(sip, msg, 482, "Loop Detected"); |
| return true; |
| } |
| } |
| |
| if (!pl_strcmp(&msg->met, "CANCEL")) |
| return cancel_handler(sip, msg); |
| |
| return false; |
| } |
| |
| |
| /** |
| * Allocate a SIP Server Transaction |
| * |
| * @param stp Pointer to allocated SIP Server Transaction |
| * @param sip SIP Stack instance |
| * @param msg Incoming SIP message |
| * @param cancelh Cancel handler |
| * @param arg Handler argument |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int sip_strans_alloc(struct sip_strans **stp, struct sip *sip, |
| const struct sip_msg *msg, sip_cancel_h *cancelh, |
| void *arg) |
| { |
| struct sip_strans *st; |
| |
| if (!stp || !sip || !msg) |
| return EINVAL; |
| |
| st = mem_zalloc(sizeof(*st), destructor); |
| if (!st) |
| return ENOMEM; |
| |
| hash_append(sip->ht_strans, hash_joaat_pl(&msg->via.branch), |
| &st->he, st); |
| |
| hash_append(sip->ht_strans_mrg, hash_joaat_pl(&msg->callid), |
| &st->he_mrg, st); |
| |
| st->invite = !pl_strcmp(&msg->met, "INVITE"); |
| st->msg = mem_ref((void *)msg); |
| st->state = TRYING; |
| st->cancelh = cancelh ? cancelh : dummy_handler; |
| st->arg = arg; |
| st->sip = sip; |
| |
| *stp = st; |
| |
| return 0; |
| } |
| |
| |
| /** |
| * Reply using a SIP Server Transaction |
| * |
| * @param stp Pointer to allocated SIP Server Transaction |
| * @param sip SIP Stack instance |
| * @param msg Incoming SIP message |
| * @param dst Destination network address |
| * @param scode Response status code |
| * @param mb Buffer containing SIP response |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int sip_strans_reply(struct sip_strans **stp, struct sip *sip, |
| const struct sip_msg *msg, const struct sa *dst, |
| uint16_t scode, struct mbuf *mb) |
| { |
| struct sip_strans *st = NULL; |
| int err; |
| |
| if (!sip || !mb || !dst || (scode < 200 && !stp)) |
| return EINVAL; |
| |
| if (stp) |
| st = *stp; |
| |
| if (!st) { |
| err = sip_strans_alloc(&st, sip, msg, NULL, NULL); |
| if (err) |
| return err; |
| } |
| |
| mem_deref(st->mb); |
| st->mb = mem_ref(mb); |
| st->dst = *dst; |
| |
| err = sip_send(sip, st->msg->sock, st->msg->tp, dst, mb); |
| |
| if (stp) |
| *stp = (err || scode >= 200) ? NULL : st; |
| |
| if (err) { |
| mem_deref(st); |
| return err; |
| } |
| |
| if (st->invite) { |
| if (scode < 200) { |
| st->state = PROCEEDING; |
| } |
| else if (scode < 300) { |
| tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler, st); |
| st->state = ACCEPTED; |
| } |
| else { |
| tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler, st); |
| st->state = COMPLETED; |
| |
| if (!sip_transp_reliable(st->msg->tp)) |
| tmr_start(&st->tmrg, SIP_T1, |
| retransmit_handler, st); |
| } |
| } |
| else { |
| if (scode < 200) { |
| st->state = PROCEEDING; |
| } |
| else { |
| if (!sip_transp_reliable(st->msg->tp)) { |
| tmr_start(&st->tmr, 64 * SIP_T1, tmr_handler, |
| st); |
| st->state = COMPLETED; |
| } |
| else { |
| mem_deref(st); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| int sip_strans_init(struct sip *sip, uint32_t sz) |
| { |
| int err; |
| |
| err = sip_listen(NULL, sip, true, request_handler, sip); |
| if (err) |
| return err; |
| |
| err = hash_alloc(&sip->ht_strans_mrg, sz); |
| if (err) |
| return err; |
| |
| return hash_alloc(&sip->ht_strans, sz); |
| } |
| |
| |
| static const char *statename(enum state state) |
| { |
| switch (state) { |
| |
| case TRYING: return "TRYING"; |
| case PROCEEDING: return "PROCEEDING"; |
| case ACCEPTED: return "ACCEPTED"; |
| case COMPLETED: return "COMPLETED"; |
| case CONFIRMED: return "CONFIRMED"; |
| default: return "???"; |
| } |
| } |
| |
| |
| static bool debug_handler(struct le *le, void *arg) |
| { |
| struct sip_strans *st = le->data; |
| struct re_printf *pf = arg; |
| |
| (void)re_hprintf(pf, " %-10r %-10s %2llus (%r)\n", |
| &st->msg->met, |
| statename(st->state), |
| tmr_get_expire(&st->tmr)/1000, |
| &st->msg->via.branch); |
| |
| return false; |
| } |
| |
| |
| int sip_strans_debug(struct re_printf *pf, const struct sip *sip) |
| { |
| int err; |
| |
| err = re_hprintf(pf, "server transactions:\n"); |
| hash_apply(sip->ht_strans, debug_handler, pf); |
| |
| return err; |
| } |