| /** |
| * @file dialog.c SIP Dialog |
| * |
| * Copyright (C) 2010 Creytiv.com |
| */ |
| #include <string.h> |
| #include <re_types.h> |
| #include <re_mem.h> |
| #include <re_mbuf.h> |
| #include <re_sa.h> |
| #include <re_list.h> |
| #include <re_hash.h> |
| #include <re_fmt.h> |
| #include <re_uri.h> |
| #include <re_sys.h> |
| #include <re_tmr.h> |
| #include <re_udp.h> |
| #include <re_msg.h> |
| #include <re_sip.h> |
| #include "sip.h" |
| |
| |
| enum { |
| ROUTE_OFFSET = 7, |
| X64_STRSIZE = 17, |
| }; |
| |
| struct sip_dialog { |
| struct uri route; |
| struct mbuf *mb; |
| char *callid; |
| char *ltag; |
| char *rtag; |
| char *uri; |
| uint32_t hash; |
| uint32_t lseq; |
| uint32_t rseq; |
| size_t cpos; |
| }; |
| |
| |
| struct route_enc { |
| struct mbuf *mb; |
| size_t end; |
| }; |
| |
| |
| static int x64_strdup(char **strp, uint64_t val) |
| { |
| char *str; |
| |
| str = mem_alloc(X64_STRSIZE, NULL); |
| if (!str) |
| return ENOMEM; |
| |
| (void)re_snprintf(str, X64_STRSIZE, "%016llx", val); |
| |
| *strp = str; |
| |
| return 0; |
| } |
| |
| |
| static void destructor(void *arg) |
| { |
| struct sip_dialog *dlg = arg; |
| |
| mem_deref(dlg->callid); |
| mem_deref(dlg->ltag); |
| mem_deref(dlg->rtag); |
| mem_deref(dlg->uri); |
| mem_deref(dlg->mb); |
| } |
| |
| |
| /** |
| * Allocate a SIP Dialog |
| * |
| * @param dlgp Pointer to allocated SIP Dialog |
| * @param uri Target URI |
| * @param to_uri To URI |
| * @param from_name From displayname (optional) |
| * @param from_uri From URI |
| * @param routev Route vector |
| * @param routec Route count |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int sip_dialog_alloc(struct sip_dialog **dlgp, |
| const char *uri, const char *to_uri, |
| const char *from_name, const char *from_uri, |
| const char *routev[], uint32_t routec) |
| { |
| const uint64_t ltag = rand_u64(); |
| struct sip_dialog *dlg; |
| struct sip_addr addr; |
| size_t rend = 0; |
| struct pl pl; |
| uint32_t i; |
| int err; |
| |
| if (!dlgp || !uri || !to_uri || !from_uri) |
| return EINVAL; |
| |
| dlg = mem_zalloc(sizeof(*dlg), destructor); |
| if (!dlg) |
| return ENOMEM; |
| |
| dlg->hash = hash_fast_str(from_uri); |
| dlg->lseq = rand_u16(); |
| |
| err = str_dup(&dlg->uri, uri); |
| if (err) |
| goto out; |
| |
| err = x64_strdup(&dlg->callid, rand_u64()); |
| if (err) |
| goto out; |
| |
| err = x64_strdup(&dlg->ltag, ltag); |
| if (err) |
| goto out; |
| |
| dlg->mb = mbuf_alloc(512); |
| if (!dlg->mb) { |
| err = ENOMEM; |
| goto out; |
| } |
| |
| for (i=0; i<routec; i++) { |
| err |= mbuf_printf(dlg->mb, "Route: <%s;lr>\r\n", routev[i]); |
| if (i == 0) |
| rend = dlg->mb->pos - 2; |
| } |
| err |= mbuf_printf(dlg->mb, "To: <%s>\r\n", to_uri); |
| dlg->cpos = dlg->mb->pos; |
| err |= mbuf_printf(dlg->mb, "From: %s%s%s<%s>;tag=%016llx\r\n", |
| from_name ? "\"" : "", from_name, |
| from_name ? "\" " : "", |
| from_uri, ltag); |
| if (err) |
| goto out; |
| |
| dlg->mb->pos = 0; |
| |
| if (rend) { |
| pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET; |
| pl.l = rend - ROUTE_OFFSET; |
| err = sip_addr_decode(&addr, &pl); |
| dlg->route = addr.uri; |
| } |
| else { |
| pl_set_str(&pl, dlg->uri); |
| err = uri_decode(&dlg->route, &pl); |
| } |
| |
| out: |
| if (err) |
| mem_deref(dlg); |
| else |
| *dlgp = dlg; |
| |
| return err; |
| } |
| |
| |
| static bool record_route_handler(const struct sip_hdr *hdr, |
| const struct sip_msg *msg, |
| void *arg) |
| { |
| struct route_enc *renc = arg; |
| (void)msg; |
| |
| if (mbuf_printf(renc->mb, "Route: %r\r\n", &hdr->val)) |
| return true; |
| |
| if (!renc->end) |
| renc->end = renc->mb->pos - 2; |
| |
| return false; |
| } |
| |
| |
| /** |
| * Accept and create a SIP Dialog from an incoming SIP Message |
| * |
| * @param dlgp Pointer to allocated SIP Dialog |
| * @param msg SIP Message |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int sip_dialog_accept(struct sip_dialog **dlgp, const struct sip_msg *msg) |
| { |
| const struct sip_hdr *contact; |
| struct sip_dialog *dlg; |
| struct route_enc renc; |
| struct sip_addr addr; |
| struct pl pl; |
| int err; |
| |
| if (!dlgp || !msg || !msg->req) |
| return EINVAL; |
| |
| contact = sip_msg_hdr(msg, SIP_HDR_CONTACT); |
| |
| if (!contact || !msg->callid.p) |
| return EBADMSG; |
| |
| if (sip_addr_decode(&addr, &contact->val)) |
| return EBADMSG; |
| |
| dlg = mem_zalloc(sizeof(*dlg), destructor); |
| if (!dlg) |
| return ENOMEM; |
| |
| dlg->hash = rand_u32(); |
| dlg->lseq = rand_u16(); |
| dlg->rseq = msg->cseq.num; |
| |
| err = pl_strdup(&dlg->uri, &addr.auri); |
| if (err) |
| goto out; |
| |
| err = pl_strdup(&dlg->callid, &msg->callid); |
| if (err) |
| goto out; |
| |
| err = x64_strdup(&dlg->ltag, msg->tag); |
| if (err) |
| goto out; |
| |
| err = pl_strdup(&dlg->rtag, &msg->from.tag); |
| if (err) |
| goto out; |
| |
| dlg->mb = mbuf_alloc(512); |
| if (!dlg->mb) { |
| err = ENOMEM; |
| goto out; |
| } |
| |
| renc.mb = dlg->mb; |
| renc.end = 0; |
| |
| err |= sip_msg_hdr_apply(msg, true, SIP_HDR_RECORD_ROUTE, |
| record_route_handler, &renc) ? ENOMEM : 0; |
| err |= mbuf_printf(dlg->mb, "To: %r\r\n", &msg->from.val); |
| err |= mbuf_printf(dlg->mb, "From: %r;tag=%016llx\r\n", &msg->to.val, |
| msg->tag); |
| if (err) |
| goto out; |
| |
| dlg->mb->pos = 0; |
| |
| if (renc.end) { |
| pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET; |
| pl.l = renc.end - ROUTE_OFFSET; |
| err = sip_addr_decode(&addr, &pl); |
| dlg->route = addr.uri; |
| } |
| else { |
| pl_set_str(&pl, dlg->uri); |
| err = uri_decode(&dlg->route, &pl); |
| } |
| |
| out: |
| if (err) |
| mem_deref(dlg); |
| else |
| *dlgp = dlg; |
| |
| return err; |
| } |
| |
| |
| /** |
| * Initialize a SIP Dialog from an incoming SIP Message |
| * |
| * @param dlg SIP Dialog to initialize |
| * @param msg SIP Message |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int sip_dialog_create(struct sip_dialog *dlg, const struct sip_msg *msg) |
| { |
| char *uri = NULL, *rtag = NULL; |
| const struct sip_hdr *contact; |
| struct route_enc renc; |
| struct sip_addr addr; |
| struct pl pl; |
| int err; |
| |
| if (!dlg || dlg->rtag || !dlg->cpos || !msg) |
| return EINVAL; |
| |
| contact = sip_msg_hdr(msg, SIP_HDR_CONTACT); |
| |
| if (!contact) |
| return EBADMSG; |
| |
| if (sip_addr_decode(&addr, &contact->val)) |
| return EBADMSG; |
| |
| renc.mb = mbuf_alloc(512); |
| if (!renc.mb) |
| return ENOMEM; |
| |
| err = pl_strdup(&uri, &addr.auri); |
| if (err) |
| goto out; |
| |
| err = pl_strdup(&rtag, msg->req ? &msg->from.tag : &msg->to.tag); |
| if (err) |
| goto out; |
| |
| renc.end = 0; |
| |
| err |= sip_msg_hdr_apply(msg, msg->req, SIP_HDR_RECORD_ROUTE, |
| record_route_handler, &renc) ? ENOMEM : 0; |
| err |= mbuf_printf(renc.mb, "To: %r\r\n", |
| msg->req ? &msg->from.val : &msg->to.val); |
| |
| dlg->mb->pos = dlg->cpos; |
| err |= mbuf_write_mem(renc.mb, mbuf_buf(dlg->mb), |
| mbuf_get_left(dlg->mb)); |
| dlg->mb->pos = 0; |
| |
| if (err) |
| goto out; |
| |
| renc.mb->pos = 0; |
| |
| if (renc.end) { |
| pl.p = (const char *)mbuf_buf(renc.mb) + ROUTE_OFFSET; |
| pl.l = renc.end - ROUTE_OFFSET; |
| err = sip_addr_decode(&addr, &pl); |
| if (err) |
| goto out; |
| |
| dlg->route = addr.uri; |
| } |
| else { |
| struct uri tmp; |
| |
| pl_set_str(&pl, uri); |
| err = uri_decode(&tmp, &pl); |
| if (err) |
| goto out; |
| |
| dlg->route = tmp; |
| } |
| |
| mem_deref(dlg->mb); |
| mem_deref(dlg->uri); |
| |
| dlg->mb = mem_ref(renc.mb); |
| dlg->rtag = mem_ref(rtag); |
| dlg->uri = mem_ref(uri); |
| dlg->rseq = msg->req ? msg->cseq.num : 0; |
| dlg->cpos = 0; |
| |
| out: |
| mem_deref(renc.mb); |
| mem_deref(rtag); |
| mem_deref(uri); |
| |
| return err; |
| } |
| |
| |
| /** |
| * Fork a SIP Dialog from an incoming SIP Message |
| * |
| * @param dlgp Pointer to allocated SIP Dialog |
| * @param odlg Original SIP Dialog |
| * @param msg SIP Message |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int sip_dialog_fork(struct sip_dialog **dlgp, struct sip_dialog *odlg, |
| const struct sip_msg *msg) |
| { |
| const struct sip_hdr *contact; |
| struct sip_dialog *dlg; |
| struct route_enc renc; |
| struct sip_addr addr; |
| struct pl pl; |
| int err; |
| |
| if (!dlgp || !odlg || !odlg->cpos || !msg) |
| return EINVAL; |
| |
| contact = sip_msg_hdr(msg, SIP_HDR_CONTACT); |
| |
| if (!contact || !msg->callid.p) |
| return EBADMSG; |
| |
| if (sip_addr_decode(&addr, &contact->val)) |
| return EBADMSG; |
| |
| dlg = mem_zalloc(sizeof(*dlg), destructor); |
| if (!dlg) |
| return ENOMEM; |
| |
| dlg->callid = mem_ref(odlg->callid); |
| dlg->ltag = mem_ref(odlg->ltag); |
| dlg->hash = odlg->hash; |
| dlg->lseq = odlg->lseq; |
| dlg->rseq = msg->req ? msg->cseq.num : 0; |
| |
| err = pl_strdup(&dlg->uri, &addr.auri); |
| if (err) |
| goto out; |
| |
| err = pl_strdup(&dlg->rtag, msg->req ? &msg->from.tag : &msg->to.tag); |
| if (err) |
| goto out; |
| |
| dlg->mb = mbuf_alloc(512); |
| if (!dlg->mb) { |
| err = ENOMEM; |
| goto out; |
| } |
| |
| renc.mb = dlg->mb; |
| renc.end = 0; |
| |
| err |= sip_msg_hdr_apply(msg, msg->req, SIP_HDR_RECORD_ROUTE, |
| record_route_handler, &renc) ? ENOMEM : 0; |
| err |= mbuf_printf(dlg->mb, "To: %r\r\n", |
| msg->req ? &msg->from.val : &msg->to.val); |
| |
| odlg->mb->pos = odlg->cpos; |
| err |= mbuf_write_mem(dlg->mb, mbuf_buf(odlg->mb), |
| mbuf_get_left(odlg->mb)); |
| odlg->mb->pos = 0; |
| |
| if (err) |
| goto out; |
| |
| dlg->mb->pos = 0; |
| |
| if (renc.end) { |
| pl.p = (const char *)mbuf_buf(dlg->mb) + ROUTE_OFFSET; |
| pl.l = renc.end - ROUTE_OFFSET; |
| err = sip_addr_decode(&addr, &pl); |
| dlg->route = addr.uri; |
| } |
| else { |
| pl_set_str(&pl, dlg->uri); |
| err = uri_decode(&dlg->route, &pl); |
| } |
| |
| out: |
| if (err) |
| mem_deref(dlg); |
| else |
| *dlgp = dlg; |
| |
| return err; |
| } |
| |
| |
| /** |
| * Update an existing SIP Dialog from a SIP Message |
| * |
| * @param dlg SIP Dialog to update |
| * @param msg SIP Message |
| * |
| * @return 0 if success, otherwise errorcode |
| */ |
| int sip_dialog_update(struct sip_dialog *dlg, const struct sip_msg *msg) |
| { |
| const struct sip_hdr *contact; |
| struct sip_addr addr; |
| char *uri; |
| int err; |
| |
| if (!dlg || !msg) |
| return EINVAL; |
| |
| contact = sip_msg_hdr(msg, SIP_HDR_CONTACT); |
| if (!contact) |
| return EBADMSG; |
| |
| if (sip_addr_decode(&addr, &contact->val)) |
| return EBADMSG; |
| |
| err = pl_strdup(&uri, &addr.auri); |
| if (err) |
| return err; |
| |
| if (dlg->route.scheme.p == dlg->uri) { |
| |
| struct uri tmp; |
| struct pl pl; |
| |
| pl_set_str(&pl, uri); |
| err = uri_decode(&tmp, &pl); |
| if (err) |
| goto out; |
| |
| dlg->route = tmp; |
| } |
| |
| mem_deref(dlg->uri); |
| dlg->uri = mem_ref(uri); |
| |
| out: |
| mem_deref(uri); |
| |
| return err; |
| } |
| |
| |
| /** |
| * Check if a remote sequence number is valid |
| * |
| * @param dlg SIP Dialog |
| * @param msg SIP Message |
| * |
| * @return True if valid, False if invalid |
| */ |
| bool sip_dialog_rseq_valid(struct sip_dialog *dlg, const struct sip_msg *msg) |
| { |
| if (!dlg || !msg || !msg->req) |
| return false; |
| |
| if (msg->cseq.num < dlg->rseq) |
| return false; |
| |
| dlg->rseq = msg->cseq.num; |
| |
| return true; |
| } |
| |
| |
| int sip_dialog_encode(struct mbuf *mb, struct sip_dialog *dlg, uint32_t cseq, |
| const char *met) |
| { |
| int err = 0; |
| |
| if (!mb || !dlg || !met) |
| return EINVAL; |
| |
| err |= mbuf_write_mem(mb, mbuf_buf(dlg->mb), mbuf_get_left(dlg->mb)); |
| err |= mbuf_printf(mb, "Call-ID: %s\r\n", dlg->callid); |
| err |= mbuf_printf(mb, "CSeq: %u %s\r\n", strcmp(met, "ACK") ? |
| dlg->lseq++ : cseq, met); |
| |
| return err; |
| } |
| |
| |
| const char *sip_dialog_uri(const struct sip_dialog *dlg) |
| { |
| return dlg ? dlg->uri : NULL; |
| } |
| |
| |
| const struct uri *sip_dialog_route(const struct sip_dialog *dlg) |
| { |
| return dlg ? &dlg->route : NULL; |
| } |
| |
| |
| uint32_t sip_dialog_hash(const struct sip_dialog *dlg) |
| { |
| return dlg ? dlg->hash : 0; |
| } |
| |
| |
| /** |
| * Get the Call-ID from a SIP Dialog |
| * |
| * @param dlg SIP Dialog |
| * |
| * @return Call-ID string |
| */ |
| const char *sip_dialog_callid(const struct sip_dialog *dlg) |
| { |
| return dlg ? dlg->callid : NULL; |
| } |
| |
| |
| /** |
| * Get the local sequence number from a SIP Dialog |
| * |
| * @param dlg SIP Dialog |
| * |
| * @return Local sequence number |
| */ |
| uint32_t sip_dialog_lseq(const struct sip_dialog *dlg) |
| { |
| return dlg ? dlg->lseq : 0; |
| } |
| |
| |
| /** |
| * Check if a SIP Dialog is established |
| * |
| * @param dlg SIP Dialog |
| * |
| * @return True if established, False if not |
| */ |
| bool sip_dialog_established(const struct sip_dialog *dlg) |
| { |
| return dlg && dlg->rtag; |
| } |
| |
| |
| /** |
| * Compare a SIP Dialog against a SIP Message |
| * |
| * @param dlg SIP Dialog |
| * @param msg SIP Message |
| * |
| * @return True if match, False if no match |
| */ |
| bool sip_dialog_cmp(const struct sip_dialog *dlg, const struct sip_msg *msg) |
| { |
| if (!dlg || !msg) |
| return false; |
| |
| if (pl_strcmp(&msg->callid, dlg->callid)) |
| return false; |
| |
| if (pl_strcmp(msg->req ? &msg->to.tag : &msg->from.tag, dlg->ltag)) |
| return false; |
| |
| if (pl_strcmp(msg->req ? &msg->from.tag : &msg->to.tag, dlg->rtag)) |
| return false; |
| |
| return true; |
| } |
| |
| |
| /** |
| * Compare a half SIP Dialog against a SIP Message |
| * |
| * @param dlg SIP Dialog |
| * @param msg SIP Message |
| * |
| * @return True if match, False if no match |
| */ |
| bool sip_dialog_cmp_half(const struct sip_dialog *dlg, |
| const struct sip_msg *msg) |
| { |
| if (!dlg || !msg) |
| return false; |
| |
| if (pl_strcmp(&msg->callid, dlg->callid)) |
| return false; |
| |
| if (pl_strcmp(msg->req ? &msg->to.tag : &msg->from.tag, dlg->ltag)) |
| return false; |
| |
| return true; |
| } |