diff options
author | Benny Prijono <bennylp@teluu.com> | 2006-02-27 00:00:30 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2006-02-27 00:00:30 +0000 |
commit | 887750106a0999aca4638c31d01ffa89eb3d4a74 (patch) | |
tree | f32476008af99f7a6be473ecc86166d76a0a1e20 /pjsip/src/pjsua-lib | |
parent | e74059628256afdb725c367d23b9503f011b0d0c (diff) |
Moved pjsua framework to pjsua-lib
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@238 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip/src/pjsua-lib')
-rw-r--r-- | pjsip/src/pjsua-lib/getopt.c | 751 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_call.c | 1090 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_core.c | 910 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_opt.c | 865 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_pres.c | 504 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_reg.c | 167 |
6 files changed, 4287 insertions, 0 deletions
diff --git a/pjsip/src/pjsua-lib/getopt.c b/pjsip/src/pjsua-lib/getopt.c new file mode 100644 index 00000000..5f39edda --- /dev/null +++ b/pjsip/src/pjsua-lib/getopt.c @@ -0,0 +1,751 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * getopt entry points + * + * modified by Mike Borella <mike_borella@mw.3com.com> + * + * $Id$ + */ + +#include <pjsua-lib/getopt.h> +#include <pj/string.h> + +/* Internal only. Users should not call this directly. */ +static +int _getopt_internal (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind, + int long_only); + +/* getopt_long and getopt_long_only entry points for GNU getopt. + Copyright (C) 1987,88,89,90,91,92,93,94,96,97 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +# define GETOPT_INTERFACE_VERSION 2 + + +int +getopt_long (int argc, char *const *argv, const char *options, + const struct option *long_options, int *opt_index) +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 0); +} + +/* Like getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +getopt (int argc, char * const * argv, const char * optstring) +{ + return _getopt_internal (argc, argv, optstring, + (const struct option *) 0, + (int *) 0, + 0); +} + + +#define _(msgid) (msgid) + +/* This version of `getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg = NULL; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* 1003.2 says this must be 1 before any call. */ +int optind = 1; + +/* Formerly, initialization of getopt depended on optind==0, which + causes problems with re-calling getopt as programs generally don't + know that. */ + +int __getopt_initialized = 0; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return -1 with `optind' != ARGC. */ + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +/* Value of POSIXLY_CORRECT environment variable. */ +static char *posixly_correct; + +static char * +my_index (const char *str, int chr) +{ + while (*str) + { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +# define SWAP_FLAGS(ch1, ch2) + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +static void +exchange (char **argv) +{ + int bottom = first_nonopt; + int middle = last_nonopt; + int top = optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else + { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + SWAP_FLAGS (bottom + i, middle + i); + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (optind - last_nonopt); + last_nonopt = optind; +} + +/* Initialize the internal data when the first call is made. */ + +static const char *_getopt_initialize (int argc, char *const *argv, + const char *optstring) +{ + PJ_UNUSED_ARG(argc); + PJ_UNUSED_ARG(argv); + + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + first_nonopt = last_nonopt = optind; + + nextchar = NULL; + + //posixly_correct = getenv ("POSIXLY_CORRECT"); + posixly_correct = NULL; + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (posixly_correct != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns -1. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +static int +_getopt_internal (int argc, char *const *argv, const char *optstring, + const struct option *longopts, int *longind, + int long_only) +{ + optarg = NULL; + + if (optind == 0 || !__getopt_initialized) + { + if (optind == 0) + optind = 1; /* Don't scan ARGV[0], the program name. */ + optstring = _getopt_initialize (argc, argv, optstring); + __getopt_initialized = 1; + } + + /* Test whether ARGV[optind] points to a non-option argument. + Either it does not have option syntax, or there is an environment flag + from the shell indicating it is not an option. The later information + is only used when the used in the GNU libc. */ +#define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') + + if (nextchar == NULL || *nextchar == '\0') + { + /* Advance to the next ARGV-element. */ + + /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been + moved back by the user (who may also have changed the arguments). */ + if (last_nonopt > optind) + last_nonopt = optind; + if (first_nonopt > optind) + first_nonopt = optind; + + if (ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (optind < argc && NONOPTION_P) + optind++; + last_nonopt = optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (optind != argc && !pj_ansi_strcmp(argv[optind], "--")) + { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return -1; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if (NONOPTION_P) + { + if (ordering == REQUIRE_ORDER) + return -1; + optarg = argv[optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + + nextchar = (argv[optind] + 1 + + (longopts != NULL && argv[optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + if (longopts != NULL + && (argv[optind][1] == '-' + || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = -1; + int option_index; + + for (nameend = nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) + == (unsigned int) strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + nextchar += strlen (nextchar); + optind++; + optopt = 0; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + optind++; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + nextchar += strlen (nextchar); + + optopt = pfound->val; + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + nextchar += strlen (nextchar); + optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[optind][1] == '-' + || my_index (optstring, *nextchar) == NULL) + { + nextchar = (char *) ""; + optind++; + optopt = 0; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *nextchar++; + char *temp = my_index (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++optind; + + if (temp == NULL || c == ':') + { + optopt = c; + return '?'; + } + /* Convenience. Treat POSIX -W foo same as long option --foo */ + if (temp[0] == 'W' && temp[1] == ';') + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + + /* optarg is now the argument, see if it's in the + table of longopts. */ + + for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + if (ambig && !exact) + { + nextchar += strlen (nextchar); + optind++; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + nextchar = NULL; + return 'W'; /* Let the application handle it. */ + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else + optarg = NULL; + nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = NULL; + } + } + return c; + } +} + diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c new file mode 100644 index 00000000..157b2bb1 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -0,0 +1,1090 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <pj/log.h> + + +/* + * pjsua_inv.c + * + * Invite session specific functionalities. + */ + +#define THIS_FILE "pjsua_inv.c" + + +/** + * Make outgoing call. + */ +pj_status_t pjsua_make_call(int acc_index, + const char *cstr_dest_uri, + int *p_call_index) +{ + pj_str_t dest_uri; + pjsip_dialog *dlg; + pjmedia_sdp_session *offer; + pjsip_inv_session *inv; + int call_index = -1; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Convert cstr_dest_uri to dest_uri */ + + dest_uri = pj_str((char*)cstr_dest_uri); + + /* Find free call slot. */ + for (call_index=0; call_index<pjsua.max_calls; ++call_index) { + if (pjsua.calls[call_index].inv == NULL) + break; + } + + if (call_index == pjsua.max_calls) { + PJ_LOG(3,(THIS_FILE, "Error: too many calls!")); + return PJ_ETOOMANY; + } + + /* Create outgoing dialog: */ + + status = pjsip_dlg_create_uac( pjsip_ua_instance(), + &pjsua.acc[acc_index].local_uri, + &pjsua.acc[acc_index].contact_uri, + &dest_uri, &dest_uri, + &dlg); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Dialog creation failed", status); + return status; + } + + /* Get media capability from media endpoint: */ + + status = pjmedia_endpt_create_sdp( pjsua.med_endpt, dlg->pool, 1, + &pjsua.calls[call_index].skinfo, + &offer); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "pjmedia unable to create SDP", status); + goto on_error; + } + + /* Create the INVITE session: */ + + status = pjsip_inv_create_uac( dlg, offer, 0, &inv); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Invite session creation failed", status); + goto on_error; + } + + + /* Create and associate our data in the session. */ + + pjsua.calls[call_index].inv = inv; + + dlg->mod_data[pjsua.mod.id] = &pjsua.calls[call_index]; + inv->mod_data[pjsua.mod.id] = &pjsua.calls[call_index]; + + + /* Set dialog Route-Set: */ + + if (!pj_list_empty(&pjsua.acc[acc_index].route_set)) + pjsip_dlg_set_route_set(dlg, &pjsua.acc[acc_index].route_set); + + + /* Set credentials: */ + + pjsip_auth_clt_set_credentials( &dlg->auth_sess, pjsua.cred_count, + pjsua.cred_info); + + + /* Create initial INVITE: */ + + status = pjsip_inv_invite(inv, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create initial INVITE request", + status); + goto on_error; + } + + + /* Send initial INVITE: */ + + status = pjsip_inv_send_msg(inv, tdata, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send initial INVITE request", + status); + goto on_error; + } + + + /* Done. */ + + ++pjsua.call_cnt; + + if (p_call_index) + *p_call_index = call_index; + + return PJ_SUCCESS; + + +on_error: + PJ_TODO(DESTROY_DIALOG_ON_FAIL); + if (call_index != -1) { + pjsua.calls[call_index].inv = NULL; + } + return status; +} + + +/** + * Handle incoming INVITE request. + */ +pj_bool_t pjsua_call_on_incoming(pjsip_rx_data *rdata) +{ + pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata); + pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata); + pjsip_msg *msg = rdata->msg_info.msg; + pjsip_tx_data *response = NULL; + unsigned options = 0; + pjsip_inv_session *inv; + int acc_index; + int call_index = -1; + pjmedia_sdp_session *answer; + pj_status_t status; + + /* Don't want to handle anything but INVITE */ + if (msg->line.req.method.id != PJSIP_INVITE_METHOD) + return PJ_FALSE; + + /* Don't want to handle anything that's already associated with + * existing dialog or transaction. + */ + if (dlg || tsx) + return PJ_FALSE; + + + /* Verify that we can handle the request. */ + status = pjsip_inv_verify_request(rdata, &options, NULL, NULL, + pjsua.endpt, &response); + if (status != PJ_SUCCESS) { + + /* + * No we can't handle the incoming INVITE request. + */ + + if (response) { + pjsip_response_addr res_addr; + + pjsip_get_response_addr(response->pool, rdata, &res_addr); + pjsip_endpt_send_response(pjsua.endpt, &res_addr, response, + NULL, NULL); + + } else { + + /* Respond with 500 (Internal Server Error) */ + pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, + NULL, NULL); + } + + return PJ_TRUE; + } + + + /* + * Yes we can handle the incoming INVITE request. + */ + + /* Find free call slot. */ + for (call_index=0; call_index < pjsua.max_calls; ++call_index) { + if (pjsua.calls[call_index].inv == NULL) + break; + } + + if (call_index == PJSUA_MAX_CALLS) { + pjsip_endpt_respond_stateless(pjsua.endpt, rdata, + PJSIP_SC_BUSY_HERE, NULL, + NULL, NULL); + return PJ_TRUE; + } + + + /* Get media capability from media endpoint: */ + + status = pjmedia_endpt_create_sdp( pjsua.med_endpt, rdata->tp_info.pool, 1, + &pjsua.calls[call_index].skinfo, + &answer ); + if (status != PJ_SUCCESS) { + pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, + NULL, NULL); + + return PJ_TRUE; + } + + /* TODO: + * + * Get which account is most likely to be associated with this incoming + * call. We need the account to find which contact URI to put for + * the call. + */ + acc_index = 0; + + /* Create dialog: */ + + status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, + &pjsua.acc[acc_index].contact_uri, + &dlg); + if (status != PJ_SUCCESS) { + pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, + NULL, NULL); + + return PJ_TRUE; + } + + + /* Create invite session: */ + + status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &inv); + if (status != PJ_SUCCESS) { + + pjsip_dlg_respond(dlg, rdata, 500, NULL); + + // TODO: Need to delete dialog + return PJ_TRUE; + } + + + /* Create and attach pjsua data to the dialog: */ + + pjsua.calls[call_index].inv = inv; + + dlg->mod_data[pjsua.mod.id] = &pjsua.calls[call_index]; + inv->mod_data[pjsua.mod.id] = &pjsua.calls[call_index]; + + + /* Must answer with some response to initial INVITE. + * If auto-answer flag is set, send 200 straight away, otherwise send 100. + */ + + status = pjsip_inv_initial_answer(inv, rdata, + (pjsua.auto_answer ? 200 : 100), + NULL, NULL, &response); + if (status != PJ_SUCCESS) { + + pjsua_perror(THIS_FILE, "Unable to create 100 response", status); + + pjsip_dlg_respond(dlg, rdata, 500, NULL); + + // TODO: Need to delete dialog + + } else { + status = pjsip_inv_send_msg(inv, response, NULL); + if (status != PJ_SUCCESS) + pjsua_perror(THIS_FILE, "Unable to send 100 response", status); + } + + if (pjsua.auto_answer < 200) { + PJ_LOG(3,(THIS_FILE, + "\nIncoming call!!\n" + "From: %.*s\n" + "To: %.*s\n" + "(press 'a' to answer, 'h' to decline)", + (int)dlg->remote.info_str.slen, + dlg->remote.info_str.ptr, + (int)dlg->local.info_str.slen, + dlg->local.info_str.ptr)); + } else { + PJ_LOG(3,(THIS_FILE, + "Call From:%.*s To:%.*s was answered with %d (%s)", + (int)dlg->remote.info_str.slen, + dlg->remote.info_str.ptr, + (int)dlg->local.info_str.slen, + dlg->local.info_str.ptr, + pjsua.auto_answer, + pjsip_get_status_text(pjsua.auto_answer)->ptr )); + } + + ++pjsua.call_cnt; + + /* This INVITE request has been handled. */ + return PJ_TRUE; +} + + +/* + * This callback receives notification from invite session when the + * session state has changed. + */ +static void pjsua_call_on_state_changed(pjsip_inv_session *inv, + pjsip_event *e) +{ + pjsua_call *call = inv->dlg->mod_data[pjsua.mod.id]; + + /* If this is an outgoing INVITE that was created because of + * REFER/transfer, send NOTIFY to transferer. + */ + if (call && call->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE) { + int st_code = -1; + pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE; + + + switch (call->inv->state) { + case PJSIP_INV_STATE_NULL: + case PJSIP_INV_STATE_CALLING: + /* Do nothing */ + break; + + case PJSIP_INV_STATE_EARLY: + case PJSIP_INV_STATE_CONNECTING: + st_code = e->body.tsx_state.tsx->status_code; + ev_state = PJSIP_EVSUB_STATE_ACTIVE; + break; + + case PJSIP_INV_STATE_CONFIRMED: + /* When state is confirmed, send the final 200/OK and terminate + * subscription. + */ + st_code = e->body.tsx_state.tsx->status_code; + ev_state = PJSIP_EVSUB_STATE_TERMINATED; + break; + + case PJSIP_INV_STATE_DISCONNECTED: + st_code = e->body.tsx_state.tsx->status_code; + ev_state = PJSIP_EVSUB_STATE_TERMINATED; + break; + } + + if (st_code != -1) { + pjsip_tx_data *tdata; + pj_status_t status; + + status = pjsip_xfer_notify( call->xfer_sub, + ev_state, st_code, + NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY", status); + } else { + status = pjsip_xfer_send_request(call->xfer_sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY", status); + } + } + } + } + + + pjsua_ui_inv_on_state_changed(call->index, e); + + /* call->inv may be NULL now */ + + /* Destroy media session when invite session is disconnected. */ + if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { + + pj_assert(call != NULL); + + if (call && call->session) { + pjmedia_conf_remove_port(pjsua.mconf, call->conf_slot); + pjmedia_session_destroy(call->session); + call->session = NULL; + + PJ_LOG(3,(THIS_FILE,"Media session is destroyed")); + } + + call->inv = NULL; + --pjsua.call_cnt; + } +} + + +/* + * Callback called by event framework when the xfer subscription state + * has changed. + */ +static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ + + PJ_UNUSED_ARG(event); + + /* + * We're only interested when subscription is terminated, to + * clear the xfer_sub member of the inv_data. + */ + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + pjsua_call *call; + + call = pjsip_evsub_get_mod_data(sub, pjsua.mod.id); + if (!call) + return; + + pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL); + call->xfer_sub = NULL; + + PJ_LOG(3,(THIS_FILE, "Xfer subscription terminated")); + } +} + + +/* + * Follow transfer (REFER) request. + */ +static void on_call_transfered( pjsip_inv_session *inv, + pjsip_rx_data *rdata ) +{ + pj_status_t status; + pjsip_tx_data *tdata; + pjsua_call *existing_call; + int new_call; + const pj_str_t str_refer_to = { "Refer-To", 8}; + pjsip_generic_string_hdr *refer_to; + char *uri; + struct pjsip_evsub_user xfer_cb; + pjsip_evsub *sub; + + existing_call = inv->dlg->mod_data[pjsua.mod.id]; + + /* Find the Refer-To header */ + refer_to = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL); + + if (refer_to == NULL) { + /* Invalid Request. + * No Refer-To header! + */ + PJ_LOG(4,(THIS_FILE, "Received REFER without Refer-To header!")); + pjsip_dlg_respond( inv->dlg, rdata, 400, NULL); + return; + } + + PJ_LOG(3,(THIS_FILE, "Call to %.*s is being transfered to %.*s", + (int)inv->dlg->remote.info_str.slen, + inv->dlg->remote.info_str.ptr, + (int)refer_to->hvalue.slen, + refer_to->hvalue.ptr)); + + /* Init callback */ + pj_memset(&xfer_cb, 0, sizeof(xfer_cb)); + xfer_cb.on_evsub_state = &xfer_on_evsub_state; + + /* Create transferee event subscription */ + status = pjsip_xfer_create_uas( inv->dlg, &xfer_cb, rdata, &sub); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create xfer uas", status); + pjsip_dlg_respond( inv->dlg, rdata, 500, NULL); + return; + } + + /* Accept the REFER request, send 200 (OK). */ + pjsip_xfer_accept(sub, rdata, 200, NULL); + + /* Create initial NOTIFY request */ + status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_ACTIVE, + 100, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", status); + return; + } + + /* Send initial NOTIFY request */ + status = pjsip_xfer_send_request( sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status); + return; + } + + /* We're cheating here. + * We need to get a null terminated string from a pj_str_t. + * So grab the pointer from the hvalue and NULL terminate it, knowing + * that the NULL position will be occupied by a newline. + */ + uri = refer_to->hvalue.ptr; + uri[refer_to->hvalue.slen] = '\0'; + + /* Now make the outgoing call. */ + status = pjsua_make_call(existing_call->acc_index, uri, &new_call); + if (status != PJ_SUCCESS) { + + /* Notify xferer about the error */ + status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, + 500, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", + status); + return; + } + status = pjsip_xfer_send_request(sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", + status); + return; + } + return; + } + + /* Put the server subscription in inv_data. + * Subsequent state changed in pjsua_inv_on_state_changed() will be + * reported back to the server subscription. + */ + pjsua.calls[new_call].xfer_sub = sub; + + /* Put the invite_data in the subscription. */ + pjsip_evsub_set_mod_data(sub, pjsua.mod.id, &pjsua.calls[new_call]); +} + + +/* + * This callback is called when transaction state has changed in INVITE + * session. We use this to trap incoming REFER request. + */ +static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv, + pjsip_transaction *tsx, + pjsip_event *e) +{ + pjsua_call *call = inv->dlg->mod_data[pjsua.mod.id]; + + if (tsx->role==PJSIP_ROLE_UAS && + tsx->state==PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_refer_method)==0) + { + /* + * Incoming REFER request. + */ + on_call_transfered(call->inv, e->body.tsx_state.src.rdata); + } +} + + +/* + * This callback is called by invite session framework when UAC session + * has forked. + */ +static void pjsua_call_on_forked( pjsip_inv_session *inv, + pjsip_event *e) +{ + PJ_UNUSED_ARG(inv); + PJ_UNUSED_ARG(e); + + PJ_TODO(HANDLE_FORKED_DIALOG); +} + + +/* + * Create inactive SDP for call hold. + */ +static pj_status_t create_inactive_sdp(pjsua_call *call, + pjmedia_sdp_session **p_answer) +{ + pj_status_t status; + pjmedia_sdp_conn *conn; + pjmedia_sdp_attr *attr; + pjmedia_sdp_session *sdp; + + /* Create new offer */ + status = pjmedia_endpt_create_sdp(pjsua.med_endpt, pjsua.pool, 1, + &call->skinfo, &sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create local SDP", status); + return status; + } + + /* Get SDP media connection line */ + conn = sdp->media[0]->conn; + if (!conn) + conn = sdp->conn; + + /* Modify address */ + conn->addr = pj_str("0.0.0.0"); + + /* Remove existing directions attributes */ + pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendrecv"); + pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendonly"); + pjmedia_sdp_media_remove_all_attr(sdp->media[0], "recvonly"); + pjmedia_sdp_media_remove_all_attr(sdp->media[0], "inactive"); + + /* Add inactive attribute */ + attr = pjmedia_sdp_attr_create(pjsua.pool, "inactive", NULL); + pjmedia_sdp_media_add_attr(sdp->media[0], attr); + + *p_answer = sdp; + + return status; +} + +/* + * Called when session received new offer. + */ +static void pjsua_call_on_rx_offer(pjsip_inv_session *inv, + const pjmedia_sdp_session *offer) +{ + pjsua_call *call; + pjmedia_sdp_conn *conn; + pjmedia_sdp_session *answer; + pj_bool_t is_remote_active; + pj_status_t status; + + call = inv->dlg->mod_data[pjsua.mod.id]; + + /* + * See if remote is offering active media (i.e. not on-hold) + */ + is_remote_active = PJ_TRUE; + + conn = offer->media[0]->conn; + if (!conn) + conn = offer->conn; + + if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 || + pj_strcmp2(&conn->addr, "0")==0) + { + is_remote_active = PJ_FALSE; + + } + else if (pjmedia_sdp_media_find_attr2(offer->media[0], "inactive", NULL)) + { + is_remote_active = PJ_FALSE; + } + + PJ_LOG(4,(THIS_FILE, "Received SDP offer, remote media is %s", + (is_remote_active ? "active" : "inactive"))); + + /* Supply candidate answer */ + if (is_remote_active) { + status = pjmedia_endpt_create_sdp( pjsua.med_endpt, call->inv->pool, 1, + &call->skinfo, &answer); + } else { + status = create_inactive_sdp( call, &answer ); + } + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create local SDP", status); + return; + } + + status = pjsip_inv_set_sdp_answer(call->inv, answer); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to set answer", status); + return; + } + +} + + +/* + * Callback to be called when SDP offer/answer negotiation has just completed + * in the session. This function will start/update media if negotiation + * has succeeded. + */ +static void pjsua_call_on_media_update(pjsip_inv_session *inv, + pj_status_t status) +{ + pjsua_call *call; + const pjmedia_sdp_session *local_sdp; + const pjmedia_sdp_session *remote_sdp; + pjmedia_port *media_port; + pj_str_t port_name; + char tmp[PJSIP_MAX_URL_SIZE]; + + call = inv->dlg->mod_data[pjsua.mod.id]; + + if (status != PJ_SUCCESS) { + + pjsua_perror(THIS_FILE, "SDP negotiation has failed", status); + return; + + } + + /* Destroy existing media session, if any. */ + + if (call && call->session) { + pjmedia_conf_remove_port(pjsua.mconf, call->conf_slot); + pjmedia_session_destroy(call->session); + call->session = NULL; + } + + /* Get local and remote SDP */ + + status = pjmedia_sdp_neg_get_active_local(call->inv->neg, &local_sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to retrieve currently active local SDP", + status); + return; + } + + + status = pjmedia_sdp_neg_get_active_remote(call->inv->neg, &remote_sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to retrieve currently active remote SDP", + status); + return; + } + + /* Create new media session. + * The media session is active immediately. + */ + if (pjsua.null_audio) + return; + + status = pjmedia_session_create( pjsua.med_endpt, 1, + &call->skinfo, + local_sdp, remote_sdp, + call, + &call->session ); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create media session", + status); + return; + } + + + /* Get the port interface of the first stream in the session. + * We need the port interface to add to the conference bridge. + */ + pjmedia_session_get_port(call->session, 0, &media_port); + + + /* + * Add the call to conference bridge. + */ + port_name.ptr = tmp; + port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, + call->inv->dlg->remote.info->uri, + tmp, sizeof(tmp)); + if (port_name.slen < 1) { + port_name = pj_str("call"); + } + status = pjmedia_conf_add_port( pjsua.mconf, call->inv->pool, + media_port, + &port_name, + &call->conf_slot); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create conference slot", + status); + pjmedia_session_destroy(call->session); + call->session = NULL; + return; + } + + /* If auto-play is configured, connect the call to the file player + * port + */ + if (pjsua.auto_play && pjsua.wav_file && + call->inv->role == PJSIP_ROLE_UAS) + { + + pjmedia_conf_connect_port( pjsua.mconf, pjsua.wav_slot, + call->conf_slot); + + } else if (pjsua.auto_loop && call->inv->role == PJSIP_ROLE_UAS) { + + pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, + call->conf_slot); + + } else if (pjsua.auto_conf) { + + int i; + + pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot); + pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0); + + for (i=0; i < pjsua.max_calls; ++i) { + + if (!pjsua.calls[i].session) + continue; + + pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, + pjsua.calls[i].conf_slot); + pjmedia_conf_connect_port( pjsua.mconf, pjsua.calls[i].conf_slot, + call->conf_slot); + } + + } else { + + /* Connect new call to the sound device port (port zero) in the + * main conference bridge. + */ + pjmedia_conf_connect_port( pjsua.mconf, 0, call->conf_slot); + pjmedia_conf_connect_port( pjsua.mconf, call->conf_slot, 0); + } + + + /* Done. */ + { + struct pjmedia_session_info sess_info; + char info[80]; + int info_len = 0; + unsigned i; + + pjmedia_session_get_info(call->session, &sess_info); + for (i=0; i<sess_info.stream_cnt; ++i) { + int len; + const char *dir; + pjmedia_stream_info *strm_info = &sess_info.stream_info[i]; + + switch (strm_info->dir) { + case PJMEDIA_DIR_NONE: + dir = "inactive"; + break; + case PJMEDIA_DIR_ENCODING: + dir = "sendonly"; + break; + case PJMEDIA_DIR_DECODING: + dir = "recvonly"; + break; + case PJMEDIA_DIR_ENCODING_DECODING: + dir = "sendrecv"; + break; + default: + dir = "unknown"; + break; + } + len = pj_ansi_sprintf( info+info_len, + ", stream #%d: %.*s (%s)", i, + (int)strm_info->fmt.encoding_name.slen, + (int)strm_info->fmt.encoding_name.ptr, + dir); + if (len > 0) + info_len += len; + } + PJ_LOG(3,(THIS_FILE,"Media started%s", info)); + } +} + + +/* + * Hangup call. + */ +void pjsua_call_hangup(int call_index, int code) +{ + pjsua_call *call; + pj_status_t status; + pjsip_tx_data *tdata; + + + call = &pjsua.calls[call_index]; + + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + return; + } + + status = pjsip_inv_end_session(call->inv, code, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Failed to create end session message", + status); + return; + } + + /* pjsip_inv_end_session may return PJ_SUCCESS with NULL + * as p_tdata when INVITE transaction has not been answered + * with any provisional responses. + */ + if (tdata == NULL) + return; + + status = pjsip_inv_send_msg(call->inv, tdata, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Failed to send end session message", + status); + return; + } +} + + +/* + * Put call on-Hold. + */ +void pjsua_call_set_hold(int call_index) +{ + pjmedia_sdp_session *sdp; + pjsua_call *call; + pjsip_tx_data *tdata; + pj_status_t status; + + call = &pjsua.calls[call_index]; + + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + return; + } + + if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed")); + return; + } + + status = create_inactive_sdp(call, &sdp); + if (status != PJ_SUCCESS) + return; + + /* Send re-INVITE with new offer */ + status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); + return; + } + + status = pjsip_inv_send_msg( call->inv, tdata, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); + return; + } +} + + +/* + * re-INVITE. + */ +void pjsua_call_reinvite(int call_index) +{ + pjmedia_sdp_session *sdp; + pjsip_tx_data *tdata; + pjsua_call *call; + pj_status_t status; + + call = &pjsua.calls[call_index]; + + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + return; + } + + + if (call->inv->state != PJSIP_INV_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed")); + return; + } + + /* Create SDP */ + status = pjmedia_endpt_create_sdp( pjsua.med_endpt, call->inv->pool, 1, + &call->skinfo, &sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", + status); + return; + } + + /* Send re-INVITE with new offer */ + status = pjsip_inv_reinvite( call->inv, NULL, sdp, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); + return; + } + + status = pjsip_inv_send_msg( call->inv, tdata, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); + return; + } +} + + +/* + * Transfer call. + */ +void pjsua_call_xfer(int call_index, const char *dest) +{ + pjsip_evsub *sub; + pjsip_tx_data *tdata; + pjsua_call *call; + pj_str_t tmp; + pj_status_t status; + + + call = &pjsua.calls[call_index]; + + if (!call->inv) { + PJ_LOG(3,(THIS_FILE,"Call has been disconnected")); + return; + } + + /* Create xfer client subscription. + * We're not interested in knowing the transfer result, so we + * put NULL as the callback. + */ + status = pjsip_xfer_create_uac(call->inv->dlg, NULL, &sub); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create xfer", status); + return; + } + + /* + * Create REFER request. + */ + status = pjsip_xfer_initiate(sub, pj_cstr(&tmp, dest), &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create REFER request", status); + return; + } + + /* Send. */ + status = pjsip_xfer_send_request(sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send REFER request", status); + return; + } + + /* For simplicity (that's what this program is intended to be!), + * leave the original invite session as it is. More advanced application + * may want to hold the INVITE, or terminate the invite, or whatever. + */ +} + + +/* + * Terminate all calls. + */ +void pjsua_inv_shutdown() +{ + int i; + + for (i=0; i<pjsua.max_calls; ++i) { + pjsip_tx_data *tdata; + pjsua_call *call; + + if (pjsua.calls[i].inv == NULL) + continue; + + call = &pjsua.calls[i]; + + if (pjsip_inv_end_session(call->inv, 410, NULL, &tdata)==0) { + if (tdata) + pjsip_inv_send_msg(call->inv, tdata, NULL); + } + } +} + + +pj_status_t pjsua_call_init(void) +{ + /* Initialize invite session callback. */ + pjsip_inv_callback inv_cb; + pj_status_t status; + + pj_memset(&inv_cb, 0, sizeof(inv_cb)); + inv_cb.on_state_changed = &pjsua_call_on_state_changed; + inv_cb.on_new_session = &pjsua_call_on_forked; + inv_cb.on_media_update = &pjsua_call_on_media_update; + inv_cb.on_rx_offer = &pjsua_call_on_rx_offer; + inv_cb.on_tsx_state_changed = &pjsua_call_on_tsx_state_changed; + + + /* Initialize invite session module: */ + status = pjsip_inv_usage_init(pjsua.endpt, &pjsua.mod, &inv_cb); + + return status; +} diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c new file mode 100644 index 00000000..b09a7c59 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -0,0 +1,910 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> + +/* + * pjsua_core.c + * + * Core application functionalities. + */ + +#define THIS_FILE "pjsua_core.c" + + +/* + * Global variable. + */ +struct pjsua pjsua; + + +/* + * Default local URI, if none is specified in cmd-line + */ +#define PJSUA_LOCAL_URI "<sip:user@127.0.0.1>" + + + +/* + * Init default application parameters. + */ +void pjsua_default(void) +{ + unsigned i; + + + /* Normally need another thread for console application, because main + * thread will be blocked in fgets(). + */ + pjsua.thread_cnt = 1; + + + /* Default transport settings: */ + pjsua.sip_port = 5060; + + + /* Default logging settings: */ + pjsua.log_level = 5; + pjsua.app_log_level = 4; + pjsua.log_decor = PJ_LOG_HAS_SENDER | PJ_LOG_HAS_TIME | + PJ_LOG_HAS_MICRO_SEC | PJ_LOG_HAS_NEWLINE; + + + /* Default: do not use STUN: */ + pjsua.stun_port1 = pjsua.stun_port2 = 0; + + /* Init accounts: */ + pjsua.acc_cnt = 1; + for (i=0; i<PJ_ARRAY_SIZE(pjsua.acc); ++i) { + pjsua.acc[i].index = i; + pjsua.acc[i].local_uri = pj_str(PJSUA_LOCAL_URI); + pjsua.acc[i].reg_timeout = 55; + pj_list_init(&pjsua.acc[i].route_set); + pj_list_init(&pjsua.acc[i].pres_srv_list); + } + + /* Init call array: */ + for (i=0; i<PJ_ARRAY_SIZE(pjsua.calls); ++i) + pjsua.calls[i].index = i; + + /* Default max nb of calls. */ + pjsua.max_calls = 4; + + /* Init server presence subscription list: */ + + +} + + + +/* + * Handler for receiving incoming requests. + * + * This handler serves multiple purposes: + * - it receives requests outside dialogs. + * - it receives requests inside dialogs, when the requests are + * unhandled by other dialog usages. Example of these + * requests are: MESSAGE. + */ +static pj_bool_t mod_pjsua_on_rx_request(pjsip_rx_data *rdata) +{ + + if (rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) { + + return pjsua_call_on_incoming(rdata); + } + + return PJ_FALSE; +} + + +/* + * Handler for receiving incoming responses. + * + * This handler serves multiple purposes: + * - it receives strayed responses (i.e. outside any dialog and + * outside any transactions). + * - it receives responses coming to a transaction, when pjsua + * module is set as transaction user for the transaction. + * - it receives responses inside a dialog, when these responses + * are unhandled by other dialog usages. + */ +static pj_bool_t mod_pjsua_on_rx_response(pjsip_rx_data *rdata) +{ + PJ_UNUSED_ARG(rdata); + return PJ_FALSE; +} + + +/* + * Initialize sockets and optionally get the public address via STUN. + */ +static pj_status_t init_sockets(pj_bool_t sip, + pjmedia_sock_info *skinfo) +{ + enum { + RTP_START_PORT = 4000, + RTP_RANDOM_START = 2, + RTP_RETRY = 100 + }; + enum { + SIP_SOCK, + RTP_SOCK, + RTCP_SOCK, + }; + int i; + static pj_uint16_t rtp_port = RTP_START_PORT; + pj_sock_t sock[3]; + pj_sockaddr_in mapped_addr[3]; + pj_status_t status = PJ_SUCCESS; + + for (i=0; i<3; ++i) + sock[i] = PJ_INVALID_SOCKET; + + /* Create and bind SIP UDP socket. */ + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[SIP_SOCK]); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "socket() error", status); + goto on_error; + } + + if (sip) { + status = pj_sock_bind_in(sock[SIP_SOCK], 0, pjsua.sip_port); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "bind() error", status); + goto on_error; + } + } else { + status = pj_sock_bind_in(sock[SIP_SOCK], 0, 0); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "bind() error", status); + goto on_error; + } + } + + + /* Loop retry to bind RTP and RTCP sockets. */ + for (i=0; i<RTP_RETRY; ++i, rtp_port += 2) { + + /* Create and bind RTP socket. */ + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[RTP_SOCK]); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "socket() error", status); + goto on_error; + } + + status = pj_sock_bind_in(sock[RTP_SOCK], 0, rtp_port); + if (status != PJ_SUCCESS) { + pj_sock_close(sock[RTP_SOCK]); + sock[RTP_SOCK] = PJ_INVALID_SOCKET; + continue; + } + + /* Create and bind RTCP socket. */ + status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &sock[RTCP_SOCK]); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "socket() error", status); + goto on_error; + } + + status = pj_sock_bind_in(sock[RTCP_SOCK], 0, (pj_uint16_t)(rtp_port+1)); + if (status != PJ_SUCCESS) { + pj_sock_close(sock[RTP_SOCK]); + sock[RTP_SOCK] = PJ_INVALID_SOCKET; + + pj_sock_close(sock[RTCP_SOCK]); + sock[RTCP_SOCK] = PJ_INVALID_SOCKET; + continue; + } + + /* + * If we're configured to use STUN, then find out the mapped address, + * and make sure that the mapped RTCP port is adjacent with the RTP. + */ + if (pjsua.stun_port1 == 0) { + const pj_str_t *hostname; + pj_sockaddr_in addr; + + /* Get local IP address. */ + hostname = pj_gethostname(); + + pj_memset( &addr, 0, sizeof(addr)); + addr.sin_family = PJ_AF_INET; + status = pj_sockaddr_in_set_str_addr( &addr, hostname); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unresolvable local hostname", + status); + goto on_error; + } + + for (i=0; i<3; ++i) + pj_memcpy(&mapped_addr[i], &addr, sizeof(addr)); + + if (sip) { + mapped_addr[SIP_SOCK].sin_port = + pj_htons((pj_uint16_t)pjsua.sip_port); + } + mapped_addr[RTP_SOCK].sin_port=pj_htons((pj_uint16_t)rtp_port); + mapped_addr[RTCP_SOCK].sin_port=pj_htons((pj_uint16_t)(rtp_port+1)); + break; + + } else { + status=pj_stun_get_mapped_addr(&pjsua.cp.factory, 3, sock, + &pjsua.stun_srv1, pjsua.stun_port1, + &pjsua.stun_srv2, pjsua.stun_port2, + mapped_addr); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "STUN error", status); + goto on_error; + } + + if (pj_ntohs(mapped_addr[2].sin_port) == + pj_ntohs(mapped_addr[1].sin_port)+1) + { + break; + } + + pj_sock_close(sock[RTP_SOCK]); + sock[RTP_SOCK] = PJ_INVALID_SOCKET; + + pj_sock_close(sock[RTCP_SOCK]); + sock[RTCP_SOCK] = PJ_INVALID_SOCKET; + } + } + + if (sock[RTP_SOCK] == PJ_INVALID_SOCKET) { + PJ_LOG(1,(THIS_FILE, + "Unable to find appropriate RTP/RTCP ports combination")); + goto on_error; + } + + if (sip) { + pjsua.sip_sock = sock[SIP_SOCK]; + pj_memcpy(&pjsua.sip_sock_name, + &mapped_addr[SIP_SOCK], + sizeof(pj_sockaddr_in)); + } else { + pj_sock_close(sock[0]); + } + + skinfo->rtp_sock = sock[RTP_SOCK]; + pj_memcpy(&skinfo->rtp_addr_name, + &mapped_addr[RTP_SOCK], sizeof(pj_sockaddr_in)); + + skinfo->rtcp_sock = sock[RTCP_SOCK]; + pj_memcpy(&skinfo->rtcp_addr_name, + &mapped_addr[RTCP_SOCK], sizeof(pj_sockaddr_in)); + + if (sip) { + PJ_LOG(4,(THIS_FILE, "SIP UDP socket reachable at %s:%d", + pj_inet_ntoa(pjsua.sip_sock_name.sin_addr), + pj_ntohs(pjsua.sip_sock_name.sin_port))); + } + PJ_LOG(4,(THIS_FILE, "RTP socket reachable at %s:%d", + pj_inet_ntoa(skinfo->rtp_addr_name.sin_addr), + pj_ntohs(skinfo->rtp_addr_name.sin_port))); + PJ_LOG(4,(THIS_FILE, "RTCP UDP socket reachable at %s:%d", + pj_inet_ntoa(skinfo->rtcp_addr_name.sin_addr), + pj_ntohs(skinfo->rtcp_addr_name.sin_port))); + + rtp_port += 2; + return PJ_SUCCESS; + +on_error: + for (i=0; i<3; ++i) { + if (sip && i==0) + continue; + if (sock[i] != PJ_INVALID_SOCKET) + pj_sock_close(sock[i]); + } + return status; +} + + + +/* + * Initialize stack. + */ +static pj_status_t init_stack(void) +{ + pj_status_t status; + + /* Create global endpoint: */ + + { + const pj_str_t *hostname; + const char *endpt_name; + + /* Endpoint MUST be assigned a globally unique name. + * The name will be used as the hostname in Warning header. + */ + + /* For this implementation, we'll use hostname for simplicity */ + hostname = pj_gethostname(); + endpt_name = hostname->ptr; + + /* Create the endpoint: */ + + status = pjsip_endpt_create(&pjsua.cp.factory, endpt_name, + &pjsua.endpt); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create SIP endpoint", status); + return status; + } + } + + + /* Initialize transaction layer: */ + + status = pjsip_tsx_layer_init_module(pjsua.endpt); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Transaction layer initialization error", + status); + goto on_error; + } + + /* Initialize UA layer module: */ + + status = pjsip_ua_init_module( pjsua.endpt, NULL ); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "UA layer initialization error", status); + goto on_error; + } + + /* Initialize and register pjsua's application module: */ + + { + pjsip_module my_mod = + { + NULL, NULL, /* prev, next. */ + { "mod-pjsua", 9 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &mod_pjsua_on_rx_request, /* on_rx_request() */ + &mod_pjsua_on_rx_response, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + }; + + pjsua.mod = my_mod; + + status = pjsip_endpt_register_module(pjsua.endpt, &pjsua.mod); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to register pjsua module", + status); + goto on_error; + } + } + + /* Initialize invite session module: */ + + status = pjsua_call_init(); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Invite usage initialization error", + status); + goto on_error; + } + + /* Done */ + + return PJ_SUCCESS; + + +on_error: + pjsip_endpt_destroy(pjsua.endpt); + pjsua.endpt = NULL; + return status; +} + + +static int PJ_THREAD_FUNC pjsua_poll(void *arg) +{ + PJ_UNUSED_ARG(arg); + + do { + pj_time_val timeout = { 0, 10 }; + pjsip_endpt_handle_events (pjsua.endpt, &timeout); + } while (!pjsua.quit_flag); + + return 0; +} + +/* + * Initialize pjsua application. + * This will initialize all libraries, create endpoint instance, and register + * pjsip modules. + */ +pj_status_t pjsua_init(void) +{ + pj_status_t status; + + /* Init PJLIB logging: */ + + pj_log_set_level(pjsua.log_level); + pj_log_set_decor(pjsua.log_decor); + + + /* Init PJLIB: */ + + status = pj_init(); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "pj_init() error", status); + return status; + } + + /* Init PJLIB-UTIL: */ + + status = pjlib_util_init(); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "pjlib_util_init() error", status); + return status; + } + + /* Init memory pool: */ + + /* Init caching pool. */ + pj_caching_pool_init(&pjsua.cp, &pj_pool_factory_default_policy, 0); + + /* Create memory pool for application. */ + pjsua.pool = pj_pool_create(&pjsua.cp.factory, "pjsua", 4000, 4000, NULL); + + + /* Init PJSIP : */ + + status = init_stack(); + if (status != PJ_SUCCESS) { + pj_caching_pool_destroy(&pjsua.cp); + pjsua_perror(THIS_FILE, "Stack initialization has returned error", + status); + return status; + } + + + /* Init core SIMPLE module : */ + + pjsip_evsub_init_module(pjsua.endpt); + + /* Init presence module: */ + + pjsip_pres_init_module( pjsua.endpt, pjsip_evsub_instance()); + + /* Init xfer/REFER module */ + + pjsip_xfer_init_module( pjsua.endpt ); + + /* Init pjsua presence handler: */ + + pjsua_pres_init(); + + + /* Init media endpoint: */ + + status = pjmedia_endpt_create(&pjsua.cp.factory, &pjsua.med_endpt); + if (status != PJ_SUCCESS) { + pj_caching_pool_destroy(&pjsua.cp); + pjsua_perror(THIS_FILE, + "Media stack initialization has returned error", + status); + return status; + } + + /* Init conference bridge. */ + + status = pjmedia_conf_create(pjsua.pool, + pjsua.max_calls+PJSUA_CONF_MORE_PORTS, + 8000, 160, 16, &pjsua.mconf); + if (status != PJ_SUCCESS) { + pj_caching_pool_destroy(&pjsua.cp); + pjsua_perror(THIS_FILE, + "Media stack initialization has returned error", + status); + return status; + } + + /* Init pjmedia-codecs: */ + + status = pjmedia_codec_init(pjsua.med_endpt); + if (status != PJ_SUCCESS) { + pj_caching_pool_destroy(&pjsua.cp); + pjsua_perror(THIS_FILE, + "Media codec initialization has returned error", + status); + return status; + } + + + /* Done. */ + return PJ_SUCCESS; +} + + +/* + * Find account for incoming request. + */ +int pjsua_find_account_for_incoming(pjsip_rx_data *rdata) +{ + pjsip_uri *uri; + pjsip_sip_uri *sip_uri; + int acc_index; + + uri = rdata->msg_info.to->uri; + + /* Just return account #0 if To URI is not SIP: */ + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && + !PJSIP_URI_SCHEME_IS_SIPS(uri)) + { + return 0; + } + + + sip_uri = (pjsip_sip_uri*)pjsip_uri_get_uri(uri); + + /* Find account which has matching username and domain. */ + for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) { + + pjsua_acc *acc = &pjsua.acc[acc_index]; + + if (pj_stricmp(&acc->user_part, &sip_uri->user)==0 && + pj_stricmp(&acc->host_part, &sip_uri->host)==0) + { + /* Match ! */ + return acc_index; + } + } + + /* No matching, try match domain part only. */ + for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) { + + pjsua_acc *acc = &pjsua.acc[acc_index]; + + if (pj_stricmp(&acc->host_part, &sip_uri->host)==0) { + /* Match ! */ + return acc_index; + } + } + + /* Still no match, just return account #0 */ + return 0; +} + + +/* + * Find account for outgoing request. + */ +int pjsua_find_account_for_outgoing(const pj_str_t *url) +{ + PJ_UNUSED_ARG(url); + + /* Just use account #0 */ + return 0; +} + + +/* + * Start pjsua stack. + * This will start the registration process, if registration is configured. + */ +pj_status_t pjsua_start(void) +{ + int i; /* Must be signed */ + pjsip_transport *udp_transport; + pj_status_t status = PJ_SUCCESS; + + /* Create WAV file player if required: */ + + if (pjsua.wav_file) { + pjmedia_port *port; + pj_str_t port_name; + + /* Create the file player port. */ + status = pjmedia_file_player_port_create( pjsua.pool, pjsua.wav_file, + 0, -1, NULL, &port); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Error playing media file", + status); + return status; + } + + /* Add port to conference bridge: */ + status = pjmedia_conf_add_port(pjsua.mconf, pjsua.pool, port, + pj_cstr(&port_name, pjsua.wav_file), + &pjsua.wav_slot); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to add file player to conference bridge", + status); + return status; + } + } + + + /* Init sockets (STUN etc): */ + for (i=0; i<(int)pjsua.max_calls; ++i) { + status = init_sockets(i==0, &pjsua.calls[i].skinfo); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "init_sockets() has returned error", + status); + --i; + if (i >= 0) + pj_sock_close(pjsua.sip_sock); + while (i >= 0) { + pj_sock_close(pjsua.calls[i].skinfo.rtp_sock); + pj_sock_close(pjsua.calls[i].skinfo.rtcp_sock); + } + return status; + } + } + + /* Add UDP transport: */ + + { + /* Init the published name for the transport. + * Depending whether STUN is used, this may be the STUN mapped + * address, or socket's bound address. + */ + pjsip_host_port addr_name; + + addr_name.host.ptr = pj_inet_ntoa(pjsua.sip_sock_name.sin_addr); + addr_name.host.slen = pj_ansi_strlen(addr_name.host.ptr); + addr_name.port = pj_ntohs(pjsua.sip_sock_name.sin_port); + + /* Create UDP transport from previously created UDP socket: */ + + status = pjsip_udp_transport_attach( pjsua.endpt, pjsua.sip_sock, + &addr_name, 1, + &udp_transport); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to start UDP transport", + status); + return status; + } + } + + /* Initialize Contact URI, if one is not specified: */ + for (i=0; i<pjsua.acc_cnt; ++i) { + + pjsip_uri *uri; + pjsip_sip_uri *sip_uri; + + /* Need to parse local_uri to get the elements: */ + + uri = pjsip_parse_uri(pjsua.pool, pjsua.acc[i].local_uri.ptr, + pjsua.acc[i].local_uri.slen, 0); + if (uri == NULL) { + pjsua_perror(THIS_FILE, "Invalid local URI", + PJSIP_EINVALIDURI); + return PJSIP_EINVALIDURI; + } + + /* Local URI MUST be a SIP or SIPS: */ + + if (!PJSIP_URI_SCHEME_IS_SIP(uri) && + !PJSIP_URI_SCHEME_IS_SIPS(uri)) + { + pjsua_perror(THIS_FILE, "Invalid local URI", + PJSIP_EINVALIDSCHEME); + return PJSIP_EINVALIDSCHEME; + } + + + /* Get the SIP URI object: */ + + sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri); + + pjsua.acc[i].user_part = sip_uri->user; + pjsua.acc[i].host_part = sip_uri->host; + + if (pjsua.acc[i].contact_uri.slen == 0 && + pjsua.acc[i].local_uri.slen) + { + char contact[128]; + int len; + + /* The local Contact is the username@ip-addr, where + * - username is taken from the local URI, + * - ip-addr in UDP transport's address name (which may have been + * resolved from STUN. + */ + + /* Build temporary contact string. */ + + if (sip_uri->user.slen) { + + /* With the user part. */ + len = pj_snprintf(contact, sizeof(contact), + "<sip:%.*s@%.*s:%d>", + (int)sip_uri->user.slen, + sip_uri->user.ptr, + (int)udp_transport->local_name.host.slen, + udp_transport->local_name.host.ptr, + udp_transport->local_name.port); + } else { + + /* Without user part */ + + len = pj_snprintf(contact, sizeof(contact), + "<sip:%.*s:%d>", + (int)udp_transport->local_name.host.slen, + udp_transport->local_name.host.ptr, + udp_transport->local_name.port); + } + + if (len < 1 || len >= sizeof(contact)) { + pjsua_perror(THIS_FILE, "Invalid Contact", PJSIP_EURITOOLONG); + return PJSIP_EURITOOLONG; + } + + /* Duplicate Contact uri. */ + + pj_strdup2(pjsua.pool, &pjsua.acc[i].contact_uri, contact); + + } + } + + /* If outbound_proxy is specified, put it in the route_set: */ + + if (pjsua.outbound_proxy.slen) { + + pjsip_route_hdr *route; + const pj_str_t hname = { "Route", 5 }; + int parsed_len; + + route = pjsip_parse_hdr( pjsua.pool, &hname, + pjsua.outbound_proxy.ptr, + pjsua.outbound_proxy.slen, + &parsed_len); + if (route == NULL) { + pjsua_perror(THIS_FILE, "Invalid outbound proxy URL", + PJSIP_EINVALIDURI); + return PJSIP_EINVALIDURI; + } + + for (i=0; i<pjsua.acc_cnt; ++i) { + pj_list_push_front(&pjsua.acc[i].route_set, route); + } + } + + + /* Create worker thread(s), if required: */ + + for (i=0; i<pjsua.thread_cnt; ++i) { + status = pj_thread_create( pjsua.pool, "pjsua", &pjsua_poll, + NULL, 0, 0, &pjsua.threads[i]); + if (status != PJ_SUCCESS) { + pjsua.quit_flag = 1; + for (--i; i>=0; --i) { + pj_thread_join(pjsua.threads[i]); + pj_thread_destroy(pjsua.threads[i]); + } + return status; + } + } + + /* Start registration: */ + + /* Create client registration session: */ + for (i=0; i<pjsua.acc_cnt; ++i) { + status = pjsua_regc_init(i); + if (status != PJ_SUCCESS) + return status; + + /* Perform registration, if required. */ + if (pjsua.acc[i].regc) { + pjsua_regc_update(i, 1); + } + } + + + /* Find account for outgoing preence subscription */ + for (i=0; i<pjsua.buddy_cnt; ++i) { + pjsua.buddies[i].acc_index = + pjsua_find_account_for_outgoing(&pjsua.buddies[i].uri); + } + + + PJ_LOG(3,(THIS_FILE, "PJSUA version %s started", PJ_VERSION)); + return PJ_SUCCESS; +} + + +/* Sleep with polling */ +static void busy_sleep(unsigned msec) +{ + pj_time_val timeout, now; + + pj_gettimeofday(&timeout); + timeout.msec += msec; + pj_time_val_normalize(&timeout); + + do { + pjsua_poll(NULL); + pj_gettimeofday(&now); + } while (PJ_TIME_VAL_LT(now, timeout)); +} + +/* + * Destroy pjsua. + */ +pj_status_t pjsua_destroy(void) +{ + int i; + + /* Signal threads to quit: */ + pjsua.quit_flag = 1; + + /* Terminate all calls. */ + pjsua_inv_shutdown(); + + /* Terminate all presence subscriptions. */ + pjsua_pres_shutdown(); + + /* Unregister, if required: */ + for (i=0; i<pjsua.acc_cnt; ++i) { + if (pjsua.acc[i].regc) { + pjsua_regc_update(i, 0); + } + } + + /* Wait worker threads to quit: */ + for (i=0; i<pjsua.thread_cnt; ++i) { + + if (pjsua.threads[i]) { + pj_thread_join(pjsua.threads[i]); + pj_thread_destroy(pjsua.threads[i]); + pjsua.threads[i] = NULL; + } + } + + + /* Wait for some time to allow unregistration to complete: */ + PJ_LOG(4,(THIS_FILE, "Shutting down...")); + busy_sleep(1000); + + /* Destroy conference bridge. */ + if (pjsua.mconf) + pjmedia_conf_destroy(pjsua.mconf); + + /* Shutdown pjmedia-codec: */ + pjmedia_codec_deinit(); + + /* Destroy sound framework: + * (this should be done in pjmedia_shutdown()) + */ + pj_snd_deinit(); + + /* Destroy endpoint. */ + + pjsip_endpt_destroy(pjsua.endpt); + pjsua.endpt = NULL; + + /* Destroy caching pool. */ + + pj_caching_pool_destroy(&pjsua.cp); + + + /* Done. */ + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/pjsua-lib/pjsua_opt.c b/pjsip/src/pjsua-lib/pjsua_opt.c new file mode 100644 index 00000000..bdd1a421 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_opt.c @@ -0,0 +1,865 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> +#include <pjsua-lib/getopt.h> + +#define THIS_FILE "pjsua_opt.c" + + +const char *pjsua_inv_state_names[] = +{ + "NULL ", + "CALLING ", + "INCOMING ", + "EARLY ", + "CONNECTING", + "CONFIRMED ", + "DISCONNCTD", + "TERMINATED", +}; + + + +/* Show usage */ +static void usage(void) +{ + puts("Usage:"); + puts(" pjsua [options]"); + puts(""); + puts("General options:"); + puts(" --help Display this help screen"); + puts(" --version Display version info"); + puts(""); + puts("Logging options:"); + puts(" --config-file=file Read the config/arguments from file."); + puts(" --log-file=fname Log to filename (default stderr)"); + puts(" --log-level=N Set log max level to N (0(none) to 6(trace))"); + puts(" --app-log-level=N Set log max level for stdout display to N"); + puts(""); + puts("SIP Account options:"); + puts(" --id=url Set the URL of local ID (used in From header)"); + puts(" --contact=url Override the Contact information"); + puts(" --proxy=url Set the URL of proxy server"); + puts(""); + puts("SIP Account Registration Options:"); + puts(" --registrar=url Set the URL of registrar server"); + puts(" --reg-timeout=secs Set registration interval to secs (default 3600)"); + puts(""); + puts("SIP Account Control:"); + puts(" --next-account Add more account"); + puts(""); + puts("Authentication options:"); + puts(" --realm=string Set realm"); + puts(" --username=string Set authentication username"); + puts(" --password=string Set authentication password"); + puts(" --next-cred Add more credential"); + puts(""); + puts("Transport Options:"); + puts(" --local-port=port Set TCP/UDP port"); + puts(" --outbound=url Set the URL of outbound proxy server"); + puts(" --use-stun1=host[:port]"); + puts(" --use-stun2=host[:port] Resolve local IP with the specified STUN servers"); + puts(""); + puts("Media Options:"); + puts(" --null-audio Use NULL audio device"); + puts(" --play-file=file Play WAV file in conference bridge"); + puts(" --auto-play Automatically play the file (to incoming calls only)"); + puts(" --auto-loop Automatically loop incoming RTP to outgoing RTP"); + puts(" --auto-conf Automatically put incoming calls to conference"); + puts(""); + puts("Buddy List (can be more than one):"); + puts(" --add-buddy url Add the specified URL to the buddy list."); + puts(""); + puts("User Agent options:"); + puts(" --auto-answer=code Automatically answer incoming calls with code (e.g. 200)"); + puts(" --max-calls=N Maximum number of concurrent calls (default:4, max:255)"); + puts(""); + fflush(stdout); +} + + + +/* + * Verify that valid SIP url is given. + */ +pj_status_t pjsua_verify_sip_url(const char *c_url) +{ + pjsip_uri *p; + pj_pool_t *pool; + char *url; + int len = (c_url ? pj_ansi_strlen(c_url) : 0); + + if (!len) return -1; + + pool = pj_pool_create(&pjsua.cp.factory, "check%p", 1024, 0, NULL); + if (!pool) return -1; + + url = pj_pool_alloc(pool, len+1); + pj_ansi_strcpy(url, c_url); + + p = pjsip_parse_uri(pool, url, len, 0); + if (!p || pj_stricmp2(pjsip_uri_get_scheme(p), "sip") != 0) + p = NULL; + + pj_pool_release(pool); + return p ? 0 : -1; +} + + +/* + * Read command arguments from config file. + */ +static int read_config_file(pj_pool_t *pool, const char *filename, + int *app_argc, char ***app_argv) +{ + int i; + FILE *fhnd; + char line[200]; + int argc = 0; + char **argv; + enum { MAX_ARGS = 64 }; + + /* Allocate MAX_ARGS+1 (argv needs to be terminated with NULL argument) */ + argv = pj_pool_calloc(pool, MAX_ARGS+1, sizeof(char*)); + argv[argc++] = *app_argv[0]; + + /* Open config file. */ + fhnd = fopen(filename, "rt"); + if (!fhnd) { + printf("Unable to open config file %s\n", filename); + fflush(stdout); + return -1; + } + + /* Scan tokens in the file. */ + while (argc < MAX_ARGS && !feof(fhnd)) { + char *token, *p = line; + + if (fgets(line, sizeof(line), fhnd) == NULL) break; + + for (token = strtok(p, " \t\r\n"); argc < MAX_ARGS; + token = strtok(NULL, " \t\r\n")) + { + int token_len; + + if (!token) break; + if (*token == '#') break; + + token_len = strlen(token); + if (!token_len) + continue; + argv[argc] = pj_pool_alloc(pool, token_len+1); + pj_memcpy(argv[argc], token, token_len+1); + ++argc; + } + } + + /* Copy arguments from command line */ + for (i=1; i<*app_argc && argc < MAX_ARGS; ++i) + argv[argc++] = (*app_argv)[i]; + + if (argc == MAX_ARGS && (i!=*app_argc || !feof(fhnd))) { + printf("Too many arguments specified in cmd line/config file\n"); + fflush(stdout); + fclose(fhnd); + return -1; + } + + fclose(fhnd); + + /* Assign the new command line back to the original command line. */ + *app_argc = argc; + *app_argv = argv; + return 0; + +} + +static int my_atoi(const char *cs) +{ + pj_str_t s; + return pj_strtoul(pj_cstr(&s, cs)); +} + + +/* Parse arguments. */ +pj_status_t pjsua_parse_args(int argc, char *argv[]) +{ + int c; + int option_index; + enum { OPT_CONFIG_FILE, OPT_LOG_FILE, OPT_LOG_LEVEL, OPT_APP_LOG_LEVEL, + OPT_HELP, OPT_VERSION, OPT_NULL_AUDIO, + OPT_LOCAL_PORT, OPT_PROXY, OPT_OUTBOUND_PROXY, OPT_REGISTRAR, + OPT_REG_TIMEOUT, OPT_ID, OPT_CONTACT, + OPT_REALM, OPT_USERNAME, OPT_PASSWORD, + OPT_USE_STUN1, OPT_USE_STUN2, + OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE, + OPT_AUTO_ANSWER, OPT_AUTO_HANGUP, OPT_AUTO_PLAY, OPT_AUTO_LOOP, + OPT_AUTO_CONF, + OPT_PLAY_FILE, + OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS, + }; + struct option long_options[] = { + { "config-file",1, 0, OPT_CONFIG_FILE}, + { "log-file", 1, 0, OPT_LOG_FILE}, + { "log-level", 1, 0, OPT_LOG_LEVEL}, + { "app-log-level",1,0,OPT_APP_LOG_LEVEL}, + { "help", 0, 0, OPT_HELP}, + { "version", 0, 0, OPT_VERSION}, + { "null-audio", 0, 0, OPT_NULL_AUDIO}, + { "local-port", 1, 0, OPT_LOCAL_PORT}, + { "proxy", 1, 0, OPT_PROXY}, + { "outbound", 1, 0, OPT_OUTBOUND_PROXY}, + { "registrar", 1, 0, OPT_REGISTRAR}, + { "reg-timeout",1, 0, OPT_REG_TIMEOUT}, + { "id", 1, 0, OPT_ID}, + { "contact", 1, 0, OPT_CONTACT}, + { "realm", 1, 0, OPT_REALM}, + { "username", 1, 0, OPT_USERNAME}, + { "password", 1, 0, OPT_PASSWORD}, + { "use-stun1", 1, 0, OPT_USE_STUN1}, + { "use-stun2", 1, 0, OPT_USE_STUN2}, + { "add-buddy", 1, 0, OPT_ADD_BUDDY}, + { "offer-x-ms-msg",0,0,OPT_OFFER_X_MS_MSG}, + { "no-presence", 0, 0, OPT_NO_PRESENCE}, + { "auto-answer",1, 0, OPT_AUTO_ANSWER}, + { "auto-hangup",1, 0, OPT_AUTO_HANGUP}, + { "auto-play", 0, 0, OPT_AUTO_PLAY}, + { "auto-loop", 0, 0, OPT_AUTO_LOOP}, + { "auto-conf", 0, 0, OPT_AUTO_CONF}, + { "play-file", 1, 0, OPT_PLAY_FILE}, + { "next-account",0,0, OPT_NEXT_ACCOUNT}, + { "next-cred", 0, 0, OPT_NEXT_CRED}, + { "max-calls", 1, 0, OPT_MAX_CALLS}, + { NULL, 0, 0, 0} + }; + pj_status_t status; + pjsua_acc *cur_acc; + pjsip_cred_info *cur_cred; + char *config_file = NULL; + + /* Run getopt once to see if user specifies config file to read. */ + while ((c=getopt_long(argc, argv, "", long_options, &option_index)) != -1) { + switch (c) { + case OPT_CONFIG_FILE: + config_file = optarg; + break; + } + if (config_file) + break; + } + + if (config_file) { + status = read_config_file(pjsua.pool, config_file, &argc, &argv); + if (status != 0) + return status; + } + + + cur_acc = &pjsua.acc[0]; + cur_cred = &pjsua.cred_info[0]; + + + /* Reinitialize and re-run getopt again, possibly with new arguments + * read from config file. + */ + optind = 0; + while ((c=getopt_long(argc, argv, "", long_options, &option_index)) != -1) { + char *p; + pj_str_t tmp; + long lval; + + switch (c) { + + case OPT_LOG_FILE: + pjsua.log_filename = optarg; + break; + + case OPT_LOG_LEVEL: + c = pj_strtoul(pj_cstr(&tmp, optarg)); + if (c < 0 || c > 6) { + printf("Error: expecting integer value 0-6 for --log-level\n"); + return PJ_EINVAL; + } + pj_log_set_level( c ); + break; + + case OPT_APP_LOG_LEVEL: + pjsua.app_log_level = pj_strtoul(pj_cstr(&tmp, optarg)); + if (pjsua.app_log_level < 0 || pjsua.app_log_level > 6) { + printf("Error: expecting integer value 0-6 for --app-log-level\n"); + return PJ_EINVAL; + } + break; + + case OPT_HELP: + usage(); + return PJ_EINVAL; + + case OPT_VERSION: /* version */ + pj_dump_config(); + return PJ_EINVAL; + + case OPT_NULL_AUDIO: + pjsua.null_audio = 1; + break; + + case OPT_LOCAL_PORT: /* local-port */ + lval = pj_strtoul(pj_cstr(&tmp, optarg)); + if (lval < 1 || lval > 65535) { + printf("Error: expecting integer value for --local-port\n"); + return PJ_EINVAL; + } + pjsua.sip_port = (pj_uint16_t)lval; + break; + + case OPT_PROXY: /* proxy */ + if (pjsua_verify_sip_url(optarg) != 0) { + printf("Error: invalid SIP URL '%s' in proxy argument\n", optarg); + return PJ_EINVAL; + } + cur_acc->proxy = pj_str(optarg); + break; + + case OPT_OUTBOUND_PROXY: /* outbound proxy */ + if (pjsua_verify_sip_url(optarg) != 0) { + printf("Error: invalid SIP URL '%s' in outbound proxy argument\n", optarg); + return PJ_EINVAL; + } + pjsua.outbound_proxy = pj_str(optarg); + break; + + case OPT_REGISTRAR: /* registrar */ + if (pjsua_verify_sip_url(optarg) != 0) { + printf("Error: invalid SIP URL '%s' in registrar argument\n", optarg); + return PJ_EINVAL; + } + cur_acc->reg_uri = pj_str(optarg); + break; + + case OPT_REG_TIMEOUT: /* reg-timeout */ + cur_acc->reg_timeout = pj_strtoul(pj_cstr(&tmp,optarg)); + if (cur_acc->reg_timeout < 1 || cur_acc->reg_timeout > 3600) { + printf("Error: invalid value for --reg-timeout (expecting 1-3600)\n"); + return PJ_EINVAL; + } + break; + + case OPT_ID: /* id */ + if (pjsua_verify_sip_url(optarg) != 0) { + printf("Error: invalid SIP URL '%s' in local id argument\n", optarg); + return PJ_EINVAL; + } + cur_acc->local_uri = pj_str(optarg); + break; + + case OPT_CONTACT: /* contact */ + if (pjsua_verify_sip_url(optarg) != 0) { + printf("Error: invalid SIP URL '%s' in contact argument\n", optarg); + return PJ_EINVAL; + } + cur_acc->contact_uri = pj_str(optarg); + break; + + case OPT_NEXT_ACCOUNT: /* Add more account. */ + pjsua.acc_cnt++; + cur_acc = &pjsua.acc[pjsua.acc_cnt - 1]; + break; + + case OPT_USERNAME: /* Default authentication user */ + if (pjsua.cred_count==0) pjsua.cred_count=1; + cur_cred->username = pj_str(optarg); + break; + + case OPT_REALM: /* Default authentication realm. */ + if (pjsua.cred_count==0) pjsua.cred_count=1; + cur_cred->realm = pj_str(optarg); + break; + + case OPT_PASSWORD: /* authentication password */ + if (pjsua.cred_count==0) pjsua.cred_count=1; + cur_cred->data_type = 0; + cur_cred->data = pj_str(optarg); + break; + + case OPT_NEXT_CRED: /* Next credential */ + pjsua.cred_count++; + cur_cred = &pjsua.cred_info[pjsua.cred_count - 1]; + break; + + case OPT_USE_STUN1: /* STUN server 1 */ + p = pj_ansi_strchr(optarg, ':'); + if (p) { + *p = '\0'; + pjsua.stun_srv1 = pj_str(optarg); + pjsua.stun_port1 = pj_strtoul(pj_cstr(&tmp, p+1)); + if (pjsua.stun_port1 < 1 || pjsua.stun_port1 > 65535) { + printf("Error: expecting port number with option --use-stun1\n"); + return PJ_EINVAL; + } + } else { + pjsua.stun_port1 = 3478; + pjsua.stun_srv1 = pj_str(optarg); + } + break; + + case OPT_USE_STUN2: /* STUN server 2 */ + p = pj_ansi_strchr(optarg, ':'); + if (p) { + *p = '\0'; + pjsua.stun_srv2 = pj_str(optarg); + pjsua.stun_port2 = pj_strtoul(pj_cstr(&tmp,p+1)); + if (pjsua.stun_port2 < 1 || pjsua.stun_port2 > 65535) { + printf("Error: expecting port number with option --use-stun2\n"); + return PJ_EINVAL; + } + } else { + pjsua.stun_port2 = 3478; + pjsua.stun_srv2 = pj_str(optarg); + } + break; + + case OPT_ADD_BUDDY: /* Add to buddy list. */ + if (pjsua_verify_sip_url(optarg) != 0) { + printf("Error: invalid URL '%s' in --add-buddy option\n", optarg); + return -1; + } + if (pjsua.buddy_cnt == PJSUA_MAX_BUDDIES) { + printf("Error: too many buddies in buddy list.\n"); + return -1; + } + pjsua.buddies[pjsua.buddy_cnt++].uri = pj_str(optarg); + break; + + case OPT_AUTO_PLAY: + pjsua.auto_play = 1; + break; + + case OPT_AUTO_LOOP: + pjsua.auto_loop = 1; + break; + + case OPT_AUTO_CONF: + pjsua.auto_conf = 1; + break; + + case OPT_PLAY_FILE: + pjsua.wav_file = optarg; + break; + + case OPT_AUTO_ANSWER: + pjsua.auto_answer = my_atoi(optarg); + if (pjsua.auto_answer < 100 || pjsua.auto_answer > 699) { + puts("Error: invalid code in --auto-answer (expecting 100-699"); + return -1; + } + break; + + case OPT_MAX_CALLS: + pjsua.max_calls = my_atoi(optarg); + if (pjsua.max_calls < 1 || pjsua.max_calls > 255) { + puts("Too many calls for max-calls (1-255)"); + return -1; + } + break; + } + } + + if (optind != argc) { + printf("Error: unknown options %s\n", argv[optind]); + return PJ_EINVAL; + } + + return PJ_SUCCESS; +} + + + +static void print_call(const char *title, + int call_index, + char *buf, pj_size_t size) +{ + int len; + pjsip_inv_session *inv = pjsua.calls[call_index].inv; + pjsip_dialog *dlg = inv->dlg; + char userinfo[128]; + + /* Dump invite sesion info. */ + + len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo)); + if (len < 1) + pj_ansi_strcpy(userinfo, "<--uri too long-->"); + else + userinfo[len] = '\0'; + + len = pj_snprintf(buf, size, "%s[%s] %s", + title, + pjsua_inv_state_names[inv->state], + userinfo); + if (len < 1 || len >= (int)size) { + pj_ansi_strcpy(buf, "<--uri too long-->"); + len = 18; + } else + buf[len] = '\0'; +} + +static void dump_media_session(pjmedia_session *session) +{ + unsigned i; + pjmedia_session_info info; + + pjmedia_session_get_info(session, &info); + + for (i=0; i<info.stream_cnt; ++i) { + pjmedia_stream_stat strm_stat; + const char *rem_addr; + int rem_port; + const char *dir; + + pjmedia_session_get_stream_stat(session, i, &strm_stat); + rem_addr = pj_inet_ntoa(info.stream_info[i].rem_addr.sin_addr); + rem_port = pj_ntohs(info.stream_info[i].rem_addr.sin_port); + + if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING) + dir = "sendonly"; + else if (info.stream_info[i].dir == PJMEDIA_DIR_DECODING) + dir = "recvonly"; + else if (info.stream_info[i].dir == PJMEDIA_DIR_ENCODING_DECODING) + dir = "sendrecv"; + else + dir = "inactive"; + + + PJ_LOG(3,(THIS_FILE, + "%s[Media strm#%d] %.*s, %s, peer=%s:%d", + " ", + i, + info.stream_info[i].fmt.encoding_name.slen, + info.stream_info[i].fmt.encoding_name.ptr, + dir, + rem_addr, rem_port)); + PJ_LOG(3,(THIS_FILE, + "%s tx {pkt=%u, bytes=%u} rx {pkt=%u, bytes=%u}", + " ", + strm_stat.enc.pkt, strm_stat.enc.bytes, + strm_stat.dec.pkt, strm_stat.dec.bytes)); + + } +} + +/* + * Dump application states. + */ +void pjsua_dump(void) +{ + char buf[128]; + unsigned old_decor; + + PJ_LOG(3,(THIS_FILE, "Start dumping application states:")); + + old_decor = pj_log_get_decor(); + pj_log_set_decor(old_decor & (PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR)); + + pjsip_endpt_dump(pjsua.endpt, 1); + pjmedia_endpt_dump(pjsua.med_endpt); + pjsip_tsx_layer_dump(); + pjsip_ua_dump(); + + + /* Dump all invite sessions: */ + PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:")); + + if (pjsua.call_cnt == 0) { + + PJ_LOG(3,(THIS_FILE, " - no sessions -")); + + } else { + int i; + + for (i=0; i<pjsua.max_calls; ++i) { + + if (pjsua.calls[i].inv == NULL) + continue; + + print_call(" ", i, buf, sizeof(buf)); + PJ_LOG(3,(THIS_FILE, "%s", buf)); + + if (pjsua.calls[i].session) + dump_media_session(pjsua.calls[i].session); + } + } + + /* Dump presence status */ + pjsua_pres_dump(); + + pj_log_set_decor(old_decor); + PJ_LOG(3,(THIS_FILE, "Dump complete")); +} + + +/* + * Load settings. + */ +pj_status_t pjsua_load_settings(const char *filename) +{ + int argc = 3; + char *argv[4] = { "pjsua", "--config-file", NULL, NULL}; + + argv[3] = (char*)filename; + return pjsua_parse_args(argc, argv); +} + + +/* + * Save account settings + */ +static void save_account_settings(int acc_index, pj_str_t *result) +{ + char line[128]; + pjsua_acc *acc = &pjsua.acc[acc_index]; + + + pj_ansi_sprintf(line, "#\n# Account %d:\n#\n", acc_index); + pj_strcat2(result, line); + + + /* Identity */ + if (acc->local_uri.slen) { + pj_ansi_sprintf(line, "--id %.*s\n", + (int)acc->local_uri.slen, + acc->local_uri.ptr); + pj_strcat2(result, line); + } + + /* Registrar server */ + if (acc->reg_uri.slen) { + pj_ansi_sprintf(line, "--registrar %.*s\n", + (int)acc->reg_uri.slen, + acc->reg_uri.ptr); + pj_strcat2(result, line); + + pj_ansi_sprintf(line, "--reg-timeout %u\n", + acc->reg_timeout); + pj_strcat2(result, line); + } + + + /* Proxy */ + if (acc->proxy.slen) { + pj_ansi_sprintf(line, "--proxy %.*s\n", + (int)acc->proxy.slen, + acc->proxy.ptr); + pj_strcat2(result, line); + } +} + + + +/* + * Dump settings. + */ +int pjsua_dump_settings(char *buf, pj_size_t max) +{ + int acc_index; + int i; + pj_str_t cfg; + char line[128]; + + cfg.ptr = buf; + cfg.slen = 0; + + + /* Logging. */ + pj_strcat2(&cfg, "#\n# Logging options:\n#\n"); + pj_ansi_sprintf(line, "--log-level %d\n", + pjsua.log_level); + pj_strcat2(&cfg, line); + + pj_ansi_sprintf(line, "--app-log-level %d\n", + pjsua.app_log_level); + pj_strcat2(&cfg, line); + + if (pjsua.log_filename) { + pj_ansi_sprintf(line, "--log-file %s\n", + pjsua.log_filename); + pj_strcat2(&cfg, line); + } + + + /* Save account settings. */ + for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) { + + save_account_settings(acc_index, &cfg); + + if (acc_index < pjsua.acc_cnt-1) + pj_strcat2(&cfg, "--next-account\n"); + } + + /* Credentials. */ + for (i=0; i<pjsua.cred_count; ++i) { + + pj_ansi_sprintf(line, "#\n# Credential %d:\n#\n", i); + pj_strcat2(&cfg, line); + + if (pjsua.cred_info[i].realm.slen) { + pj_ansi_sprintf(line, "--realm %.*s\n", + (int)pjsua.cred_info[i].realm.slen, + pjsua.cred_info[i].realm.ptr); + pj_strcat2(&cfg, line); + } + + pj_ansi_sprintf(line, "--username %.*s\n", + (int)pjsua.cred_info[i].username.slen, + pjsua.cred_info[i].username.ptr); + pj_strcat2(&cfg, line); + + pj_ansi_sprintf(line, "--password %.*s\n", + (int)pjsua.cred_info[i].data.slen, + pjsua.cred_info[i].data.ptr); + pj_strcat2(&cfg, line); + + if (i < pjsua.cred_count-1) + pj_strcat2(&cfg, "--next-cred\n"); + } + + + pj_strcat2(&cfg, "#\n# Network settings:\n#\n"); + + /* Outbound proxy */ + if (pjsua.outbound_proxy.slen) { + pj_ansi_sprintf(line, "--outbound %.*s\n", + (int)pjsua.outbound_proxy.slen, + pjsua.outbound_proxy.ptr); + pj_strcat2(&cfg, line); + } + + + /* Transport. */ + pj_ansi_sprintf(line, "--local-port %d\n", pjsua.sip_port); + pj_strcat2(&cfg, line); + + + /* STUN */ + if (pjsua.stun_port1) { + pj_ansi_sprintf(line, "--use-stun1 %.*s:%d\n", + (int)pjsua.stun_srv1.slen, + pjsua.stun_srv1.ptr, + pjsua.stun_port1); + pj_strcat2(&cfg, line); + } + + if (pjsua.stun_port2) { + pj_ansi_sprintf(line, "--use-stun2 %.*s:%d\n", + (int)pjsua.stun_srv2.slen, + pjsua.stun_srv2.ptr, + pjsua.stun_port2); + pj_strcat2(&cfg, line); + } + + + pj_strcat2(&cfg, "#\n# Media settings:\n#\n"); + + + /* Media */ + if (pjsua.null_audio) + pj_strcat2(&cfg, "--null-audio\n"); + if (pjsua.auto_play) + pj_strcat2(&cfg, "--auto-play\n"); + if (pjsua.auto_loop) + pj_strcat2(&cfg, "--auto-loop\n"); + if (pjsua.auto_conf) + pj_strcat2(&cfg, "--auto-conf\n"); + if (pjsua.wav_file) { + pj_ansi_sprintf(line, "--play-file %s\n", + pjsua.wav_file); + pj_strcat2(&cfg, line); + } + + + pj_strcat2(&cfg, "#\n# User agent:\n#\n"); + + /* Auto-answer. */ + if (pjsua.auto_answer != 0) { + pj_ansi_sprintf(line, "--auto-answer %d\n", + pjsua.auto_answer); + pj_strcat2(&cfg, line); + } + + /* Max calls. */ + pj_ansi_sprintf(line, "--max-calls %d\n", + pjsua.max_calls); + pj_strcat2(&cfg, line); + + + pj_strcat2(&cfg, "#\n# Buddies:\n#\n"); + + /* Add buddies. */ + for (i=0; i<pjsua.buddy_cnt; ++i) { + pj_ansi_sprintf(line, "--add-buddy %.*s\n", + (int)pjsua.buddies[i].uri.slen, + pjsua.buddies[i].uri.ptr); + pj_strcat2(&cfg, line); + } + + + *(cfg.ptr + cfg.slen) = '\0'; + return cfg.slen; +} + +/* + * Save settings. + */ +pj_status_t pjsua_save_settings(const char *filename) +{ + pj_str_t cfg; + pj_pool_t *pool; + FILE *fhnd; + + /* Create pool for temporary buffer. */ + pool = pj_pool_create(&pjsua.cp.factory, "settings", 4000, 0, NULL); + if (!pool) + return PJ_ENOMEM; + + + cfg.ptr = pj_pool_alloc(pool, 3800); + if (!cfg.ptr) { + pj_pool_release(pool); + return PJ_EBUG; + } + + + cfg.slen = pjsua_dump_settings(cfg.ptr, 3800); + if (cfg.slen < 1) { + pj_pool_release(pool); + return PJ_ENOMEM; + } + + + /* Write to file. */ + fhnd = fopen(filename, "wt"); + if (!fhnd) { + pj_pool_release(pool); + return pj_get_os_error(); + } + + fwrite(cfg.ptr, cfg.slen, 1, fhnd); + fclose(fhnd); + + pj_pool_release(pool); + return PJ_SUCCESS; +} diff --git a/pjsip/src/pjsua-lib/pjsua_pres.c b/pjsip/src/pjsua-lib/pjsua_pres.c new file mode 100644 index 00000000..9f307c6c --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_pres.c @@ -0,0 +1,504 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> + +/* + * pjsua_pres.c + * + * Presence related stuffs. + */ + +#define THIS_FILE "pjsua_pres.c" + + + +/* ************************************************************************** + * THE FOLLOWING PART HANDLES SERVER SUBSCRIPTION + * ************************************************************************** + */ + +/* Proto */ +static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata); + +/* The module instance. */ +static pjsip_module mod_pjsua_pres = +{ + NULL, NULL, /* prev, next. */ + { "mod-pjsua-pres", 14 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &pres_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + +}; + + +/* Callback called when *server* subscription state has changed. */ +static void pres_evsub_on_srv_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsua_srv_pres *uapres = pjsip_evsub_get_mod_data(sub, pjsua.mod.id); + + PJ_UNUSED_ARG(event); + + if (uapres) { + PJ_LOG(3,(THIS_FILE, "Server subscription to %s is %s", + uapres->remote, pjsip_evsub_get_state_name(sub))); + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL); + pj_list_erase(uapres); + } + } +} + +/* This is called when request is received. + * We need to check for incoming SUBSCRIBE request. + */ +static pj_bool_t pres_on_rx_request(pjsip_rx_data *rdata) +{ + int acc_index; + pjsip_method *req_method = &rdata->msg_info.msg->line.req.method; + pjsua_srv_pres *uapres; + pjsip_evsub *sub; + pjsip_evsub_user pres_cb; + pjsip_tx_data *tdata; + pjsip_pres_status pres_status; + pjsip_dialog *dlg; + pj_status_t status; + + if (pjsip_method_cmp(req_method, &pjsip_subscribe_method) != 0) + return PJ_FALSE; + + /* Incoming SUBSCRIBE: */ + + /* Find which account for the incoming request. */ + acc_index = pjsua_find_account_for_incoming(rdata); + + /* Create UAS dialog: */ + status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, + &pjsua.acc[acc_index].contact_uri, + &dlg); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Unable to create UAS dialog for subscription", + status); + return PJ_FALSE; + } + + /* Init callback: */ + pj_memset(&pres_cb, 0, sizeof(pres_cb)); + pres_cb.on_evsub_state = &pres_evsub_on_srv_state; + + /* Create server presence subscription: */ + status = pjsip_pres_create_uas( dlg, &pres_cb, rdata, &sub); + if (status != PJ_SUCCESS) { + PJ_TODO(DESTROY_DIALOG); + pjsua_perror(THIS_FILE, "Unable to create server subscription", + status); + return PJ_FALSE; + } + + /* Attach our data to the subscription: */ + uapres = pj_pool_alloc(dlg->pool, sizeof(pjsua_srv_pres)); + uapres->sub = sub; + uapres->remote = pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE); + status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri, + uapres->remote, PJSIP_MAX_URL_SIZE); + if (status < 1) + pj_ansi_strcpy(uapres->remote, "<-- url is too long-->"); + else + uapres->remote[status] = '\0'; + + pjsip_evsub_set_mod_data(sub, pjsua.mod.id, uapres); + + /* Add server subscription to the list: */ + pj_list_push_back(&pjsua.acc[acc_index].pres_srv_list, uapres); + + + /* Create and send 200 (OK) to the SUBSCRIBE request: */ + status = pjsip_pres_accept(sub, rdata, 200, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to accept presence subscription", + status); + pj_list_erase(uapres); + return PJ_FALSE; + } + + + /* Set our online status: */ + pj_memset(&pres_status, 0, sizeof(pres_status)); + pres_status.info_cnt = 1; + pres_status.info[0].basic_open = pjsua.acc[acc_index].online_status; + //Both pjsua.local_uri and pjsua.contact_uri are enclosed in "<" and ">" + //causing XML parsing to fail. + //pres_status.info[0].contact = pjsua.local_uri; + + pjsip_pres_set_status(sub, &pres_status); + + /* Create and send the first NOTIFY to active subscription: */ + status = pjsip_pres_notify( sub, PJSIP_EVSUB_STATE_ACTIVE, NULL, + NULL, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_pres_send_request( sub, tdata); + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create/send NOTIFY", + status); + pj_list_erase(uapres); + return PJ_FALSE; + } + + + /* Done: */ + + return PJ_TRUE; +} + + +/* Refresh subscription (e.g. when our online status has changed) */ +static void refresh_server_subscription(int acc_index) +{ + pjsua_srv_pres *uapres; + + uapres = pjsua.acc[acc_index].pres_srv_list.next; + + while (uapres != &pjsua.acc[acc_index].pres_srv_list) { + + pjsip_pres_status pres_status; + pjsip_tx_data *tdata; + + pjsip_pres_get_status(uapres->sub, &pres_status); + if (pres_status.info[0].basic_open != pjsua.acc[acc_index].online_status) { + pres_status.info[0].basic_open = pjsua.acc[acc_index].online_status; + pjsip_pres_set_status(uapres->sub, &pres_status); + + if (pjsua.quit_flag) { + pj_str_t reason = { "noresource", 10 }; + if (pjsip_pres_notify(uapres->sub, + PJSIP_EVSUB_STATE_TERMINATED, NULL, + &reason, &tdata)==PJ_SUCCESS) + { + pjsip_pres_send_request(uapres->sub, tdata); + } + } else { + if (pjsip_pres_current_notify(uapres->sub, &tdata)==PJ_SUCCESS) + pjsip_pres_send_request(uapres->sub, tdata); + } + } + + uapres = uapres->next; + } +} + + + +/* ************************************************************************** + * THE FOLLOWING PART HANDLES CLIENT SUBSCRIPTION + * ************************************************************************** + */ + +/* Callback called when *client* subscription state has changed. */ +static void pjsua_evsub_on_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsua_buddy *buddy; + + PJ_UNUSED_ARG(event); + + buddy = pjsip_evsub_get_mod_data(sub, pjsua.mod.id); + if (buddy) { + PJ_LOG(3,(THIS_FILE, + "Presence subscription to %s is %s", + buddy->uri.ptr, + pjsip_evsub_get_state_name(sub))); + + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + buddy->sub = NULL; + buddy->status.info_cnt = 0; + pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL); + } + } +} + +/* Callback called when we receive NOTIFY */ +static void pjsua_evsub_on_rx_notify(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body) +{ + pjsua_buddy *buddy; + + buddy = pjsip_evsub_get_mod_data(sub, pjsua.mod.id); + if (buddy) { + /* Update our info. */ + pjsip_pres_get_status(sub, &buddy->status); + + if (buddy->status.info_cnt) { + PJ_LOG(3,(THIS_FILE, "%s is %s", + buddy->uri.ptr, + (buddy->status.info[0].basic_open?"online":"offline"))); + } else { + PJ_LOG(3,(THIS_FILE, "No presence info for %s", + buddy->uri.ptr)); + } + } + + /* The default is to send 200 response to NOTIFY. + * Just leave it there.. + */ + PJ_UNUSED_ARG(rdata); + PJ_UNUSED_ARG(p_st_code); + PJ_UNUSED_ARG(p_st_text); + PJ_UNUSED_ARG(res_hdr); + PJ_UNUSED_ARG(p_body); +} + + +/* Event subscription callback. */ +static pjsip_evsub_user pres_callback = +{ + &pjsua_evsub_on_state, + + NULL, /* on_tsx_state: don't care about transaction state. */ + + NULL, /* on_rx_refresh: don't care about SUBSCRIBE refresh, unless + * we want to authenticate + */ + + &pjsua_evsub_on_rx_notify, + + NULL, /* on_client_refresh: Use default behaviour, which is to + * refresh client subscription. */ + + NULL, /* on_server_timeout: Use default behaviour, which is to send + * NOTIFY to terminate. + */ +}; + + +/* It does what it says.. */ +static void subscribe_buddy_presence(unsigned index) +{ + int acc_index; + pjsip_dialog *dlg; + pjsip_tx_data *tdata; + pj_status_t status; + + acc_index = pjsua.buddies[index].acc_index; + + status = pjsip_dlg_create_uac( pjsip_ua_instance(), + &pjsua.acc[acc_index].local_uri, + &pjsua.acc[acc_index].contact_uri, + &pjsua.buddies[index].uri, + NULL, &dlg); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create dialog", + status); + return; + } + + status = pjsip_pres_create_uac( dlg, &pres_callback, + &pjsua.buddies[index].sub); + if (status != PJ_SUCCESS) { + pjsua.buddies[index].sub = NULL; + pjsua_perror(THIS_FILE, "Unable to create presence client", + status); + return; + } + + pjsip_evsub_set_mod_data(pjsua.buddies[index].sub, pjsua.mod.id, + &pjsua.buddies[index]); + + status = pjsip_pres_initiate(pjsua.buddies[index].sub, -1, &tdata); + if (status != PJ_SUCCESS) { + pjsua.buddies[index].sub = NULL; + pjsua_perror(THIS_FILE, "Unable to create initial SUBSCRIBE", + status); + return; + } + + status = pjsip_pres_send_request(pjsua.buddies[index].sub, tdata); + if (status != PJ_SUCCESS) { + pjsua.buddies[index].sub = NULL; + pjsua_perror(THIS_FILE, "Unable to send initial SUBSCRIBE", + status); + return; + } + + PJ_TODO(DESTROY_DIALOG_ON_ERROR); +} + + +/* It does what it says... */ +static void unsubscribe_buddy_presence(unsigned index) +{ + pjsip_tx_data *tdata; + pj_status_t status; + + if (pjsua.buddies[index].sub == NULL) + return; + + if (pjsip_evsub_get_state(pjsua.buddies[index].sub) == + PJSIP_EVSUB_STATE_TERMINATED) + { + pjsua.buddies[index].sub = NULL; + return; + } + + status = pjsip_pres_initiate( pjsua.buddies[index].sub, 0, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_pres_send_request( pjsua.buddies[index].sub, tdata ); + + if (status == PJ_SUCCESS) { + + //pjsip_evsub_set_mod_data(pjsua.buddies[index].sub, pjsua.mod.id, + // NULL); + //pjsua.buddies[index].sub = NULL; + + } else { + pjsua_perror(THIS_FILE, "Unable to unsubscribe presence", + status); + } +} + + +/* It does what it says.. */ +static void refresh_client_subscription(void) +{ + int i; + + for (i=0; i<pjsua.buddy_cnt; ++i) { + + if (pjsua.buddies[i].monitor && !pjsua.buddies[i].sub) { + subscribe_buddy_presence(i); + + } else if (!pjsua.buddies[i].monitor && pjsua.buddies[i].sub) { + unsubscribe_buddy_presence(i); + + } + } +} + + +/* + * Init presence + */ +pj_status_t pjsua_pres_init() +{ + pj_status_t status; + + status = pjsip_endpt_register_module( pjsua.endpt, &mod_pjsua_pres); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to register pjsua presence module", + status); + } + + return status; +} + +/* + * Refresh presence + */ +void pjsua_pres_refresh(int acc_index) +{ + refresh_client_subscription(); + refresh_server_subscription(acc_index); +} + + +/* + * Shutdown presence. + */ +void pjsua_pres_shutdown(void) +{ + int acc_index; + int i; + + for (acc_index=0; acc_index<pjsua.acc_cnt; ++acc_index) { + pjsua.acc[acc_index].online_status = 0; + } + + for (i=0; i<pjsua.buddy_cnt; ++i) { + pjsua.buddies[i].monitor = 0; + } + + for (acc_index=0; acc_index<pjsua.acc_cnt; ++acc_index) { + pjsua_pres_refresh(acc_index); + } +} + +/* + * Dump presence status. + */ +void pjsua_pres_dump(void) +{ + int acc_index; + int i; + + PJ_LOG(3,(THIS_FILE, "Dumping pjsua server subscriptions:")); + for (acc_index=0; acc_index < pjsua.acc_cnt; ++acc_index) { + + PJ_LOG(3,(THIS_FILE, " %.*s", + (int)pjsua.acc[acc_index].local_uri.slen, + pjsua.acc[acc_index].local_uri.ptr)); + + if (pj_list_empty(&pjsua.acc[acc_index].pres_srv_list)) { + PJ_LOG(3,(THIS_FILE, " - none - ")); + } else { + struct pjsua_srv_pres *uapres; + + uapres = pjsua.acc[acc_index].pres_srv_list.next; + while (uapres != &pjsua.acc[acc_index].pres_srv_list) { + + PJ_LOG(3,(THIS_FILE, " %10s %s", + pjsip_evsub_get_state_name(uapres->sub), + uapres->remote)); + + uapres = uapres->next; + } + } + } + + PJ_LOG(3,(THIS_FILE, "Dumping pjsua client subscriptions:")); + if (pjsua.buddy_cnt == 0) { + PJ_LOG(3,(THIS_FILE, " - no buddy list - ")); + } else { + for (i=0; i<pjsua.buddy_cnt; ++i) { + + if (pjsua.buddies[i].sub) { + PJ_LOG(3,(THIS_FILE, " %10s %s", + pjsip_evsub_get_state_name(pjsua.buddies[i].sub), + pjsua.buddies[i].uri.ptr)); + } else { + PJ_LOG(3,(THIS_FILE, " %10s %s", + "(null)", + pjsua.buddies[i].uri.ptr)); + } + } + } +} + diff --git a/pjsip/src/pjsua-lib/pjsua_reg.c b/pjsip/src/pjsua-lib/pjsua_reg.c new file mode 100644 index 00000000..e18f7cc8 --- /dev/null +++ b/pjsip/src/pjsua-lib/pjsua_reg.c @@ -0,0 +1,167 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsua-lib/pjsua.h> + + +/* + * pjsua_reg.c + * + * Client registration handler. + */ + +#define THIS_FILE "pjsua_reg.c" + + +/* + * This callback is called by pjsip_regc when outgoing register + * request has completed. + */ +static void regc_cb(struct pjsip_regc_cbparam *param) +{ + + pjsua_acc *acc = param->token; + + /* + * Print registration status. + */ + if (param->status!=PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "SIP registration error", + param->status); + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; + + } else if (param->code < 0 || param->code >= 300) { + PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%s)", + param->code, + pjsip_get_status_text(param->code)->ptr)); + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; + + } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) { + + if (param->expiration < 1) { + pjsip_regc_destroy(acc->regc); + acc->regc = NULL; + PJ_LOG(3,(THIS_FILE, "%s: unregistration success", + acc->local_uri.ptr)); + } else { + PJ_LOG(3, (THIS_FILE, + "%s: registration success, status=%d (%s), " + "will re-register in %d seconds", + acc->local_uri.ptr, + param->code, + pjsip_get_status_text(param->code)->ptr, + param->expiration)); + } + + } else { + PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code)); + } + + acc->reg_last_err = param->status; + acc->reg_last_code = param->code; + + pjsua_ui_regc_on_state_changed(acc->index); +} + + +/* + * Update registration. If renew is false, then unregistration will be performed. + */ +void pjsua_regc_update(int acc_index, pj_bool_t renew) +{ + pj_status_t status; + pjsip_tx_data *tdata; + + if (renew) { + if (pjsua.acc[acc_index].regc == NULL) { + status = pjsua_regc_init(acc_index); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create registration", + status); + return; + } + } + status = pjsip_regc_register(pjsua.acc[acc_index].regc, 1, &tdata); + } else { + if (pjsua.acc[acc_index].regc == NULL) { + PJ_LOG(3,(THIS_FILE, "Currently not registered")); + return; + } + status = pjsip_regc_unregister(pjsua.acc[acc_index].regc, &tdata); + } + + if (status == PJ_SUCCESS) + status = pjsip_regc_send( pjsua.acc[acc_index].regc, tdata ); + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create/send REGISTER", + status); + } else { + PJ_LOG(3,(THIS_FILE, "%s sent", + (renew? "Registration" : "Unregistration"))); + } +} + +/* + * Initialize client registration. + */ +pj_status_t pjsua_regc_init(int acc_index) +{ + pj_status_t status; + + /* initialize SIP registration if registrar is configured */ + if (pjsua.acc[acc_index].reg_uri.slen) { + + status = pjsip_regc_create( pjsua.endpt, + &pjsua.acc[acc_index], + ®c_cb, + &pjsua.acc[acc_index].regc); + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create client registration", + status); + return status; + } + + + status = pjsip_regc_init( pjsua.acc[acc_index].regc, + &pjsua.acc[acc_index].reg_uri, + &pjsua.acc[acc_index].local_uri, + &pjsua.acc[acc_index].local_uri, + 1, &pjsua.acc[acc_index].contact_uri, + pjsua.acc[acc_index].reg_timeout); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Client registration initialization error", + status); + return status; + } + + pjsip_regc_set_credentials( pjsua.acc[acc_index].regc, + pjsua.cred_count, + pjsua.cred_info ); + + pjsip_regc_set_route_set( pjsua.acc[acc_index].regc, + &pjsua.acc[acc_index].route_set ); + } + + return PJ_SUCCESS; +} + |