From 5f1de1bbb341ea1dc1d27d9bf35764b643ef904a Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Mon, 21 Nov 2005 01:55:47 +0000 Subject: Set svn:eol-style property git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@65 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/src/pjsua/getopt.c | 2122 +++++++++++++-------------- pjsip/src/pjsua/getopt.h | 282 ++-- pjsip/src/pjsua/main.c | 3654 +++++++++++++++++++++++----------------------- pjsip/src/pjsua/misc.c | 970 ++++++------ 4 files changed, 3514 insertions(+), 3514 deletions(-) (limited to 'pjsip/src/pjsua') diff --git a/pjsip/src/pjsua/getopt.c b/pjsip/src/pjsua/getopt.c index d2812f2b..b522c579 100644 --- a/pjsip/src/pjsua/getopt.c +++ b/pjsip/src/pjsua/getopt.c @@ -1,1061 +1,1061 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono - * - * 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 - */ - -#ifdef _MSC_VER -/* in VC this file will generate a lot of warning about old style function - * declarations. - */ -# pragma warning(push, 3) -#endif - -/* - * getopt entry points - * - * modified by Mike Borella - * - * $Id: getopt.c,v 1.4 2000/10/30 22:06:03 mborella Exp $ - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifndef HAVE_GETOPT_LONG - -/* 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. */ - -#include "getopt.h" - - -#include - -/* 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 -#if !defined (_LIBC) && defined (__GLIBC__) && __GLIBC__ >= 2 -#include -#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION -#define ELIDE_CODE -#endif -#endif - -#ifndef ELIDE_CODE - - -/* This needs to come after some library #include - to get __GNU_LIBRARY__ defined. */ -#ifdef __GNU_LIBRARY__ -#include -#endif - -#ifndef NULL -#define NULL 0 -#endif - -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_long_only (argc, argv, options, long_options, opt_index) - 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, 1); -} - -int -getopt (int argc, char * const * argv, const char * optstring) -{ - return _getopt_internal (argc, argv, optstring, - (const struct option *) 0, - (int *) 0, - 0); -} - -#endif /* Not ELIDE_CODE. */ - - -#ifndef _NO_PROTO -#define _NO_PROTO -#endif - - -//#include - -/* 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 -#if !defined (_LIBC) && defined (__GLIBC__) && __GLIBC__ >= 2 -#include -#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION -#define ELIDE_CODE -#endif -#endif - -#ifndef ELIDE_CODE - - -/* This needs to come after some library #include - to get __GNU_LIBRARY__ defined. */ -#ifdef __GNU_LIBRARY__ -/* Don't include stdlib.h for non-GNU C libraries because some of them - contain conflicting prototypes for getopt. */ -#include -#include -#endif /* GNU C library. */ - -#ifdef VMS -#include -#if HAVE_STRING_H - 0 -#include -#endif -#endif - -#if defined (WIN32) && !defined (__CYGWIN32__) -/* It's not Unix, really. See? Capital letters. */ -#include -#define getpid() GetCurrentProcessId() -#endif - -#ifndef _ -/* This is for other GNU distributions with internationalized messages. - When compiling libc, the _ macro is predefined. */ -#ifdef HAVE_LIBINTL_H -# include -# define _(msgid) gettext (msgid) -#else -# define _(msgid) (msgid) -#endif -#endif - -/* 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. */ - -#include "getopt.h" - -/* 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; - -/* Callers store zero here to inhibit the error message - for unrecognized options. */ - -int opterr = 1; - -/* 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; - -#ifdef __GNU_LIBRARY__ -/* We want to avoid inclusion of string.h with non-GNU libraries - because there are many ways it can cause trouble. - On some systems, it contains special magic macros that don't work - in GCC. */ -#include -#define my_index pj_native_strchr -#else - -static char * -my_index (const char *str, int chr) -{ - while (*str) - { - if (*str == chr) - return (char *) str; - str++; - } - return 0; -} - -/* If using GCC, we can safely declare strlen this way. - If not using GCC, it is ok not to declare it. */ -#ifdef __GNUC__ -/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. - That was relevant to code that was here before. */ -#if !defined (__STDC__) || !__STDC__ -/* gcc with -traditional declares the built-in strlen to return int, - and has done so at least since version 2.4.5. -- rms. */ -extern int strlen (const char *); -#endif /* not __STDC__ */ -#endif /* __GNUC__ */ - -#endif /* not __GNU_LIBRARY__ */ - -/* 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; - -#ifdef _LIBC -/* Bash 2.0 gives us an environment variable containing flags - indicating ARGV elements that should not be considered arguments. */ - -/* Defined in getopt_init.c */ -extern char *__getopt_nonoption_flags; - -static int nonoption_flags_max_len; -static int nonoption_flags_len; - -static int original_argc; -static char *const *original_argv; - -extern pid_t __libc_pid; - -/* Make sure the environment variable bash 2.0 puts in the environment - is valid for the getopt call we must make sure that the ARGV passed - to getopt is that one passed to the process. */ -static void -__attribute__ ((unused)) -store_args_and_env (int argc, char *const *argv) -{ - /* XXX This is no good solution. We should rather copy the args so - that we can compare them later. But we must not use malloc(3). */ - original_argc = argc; - original_argv = argv; -} -text_set_element (__libc_subinit, store_args_and_env); - -# define SWAP_FLAGS(ch1, ch2) \ - if (nonoption_flags_len > 0) \ - { \ - char __tmp = __getopt_nonoption_flags[ch1]; \ - __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ - __getopt_nonoption_flags[ch2] = __tmp; \ - } -#else /* !_LIBC */ -# define SWAP_FLAGS(ch1, ch2) -#endif /* _LIBC */ - -/* 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. */ - -#if defined (__STDC__) && __STDC__ -static void exchange (char **); -#endif - -static void -exchange (argv) - 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. */ - -#ifdef _LIBC - /* First make sure the handling of the `__getopt_nonoption_flags' - string can work normally. Our top argument must be in the range - of the string. */ - if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) - { - /* We must extend the array. The user plays games with us and - presents new arguments. */ - char *new_str = malloc (top + 1); - if (new_str == NULL) - nonoption_flags_len = nonoption_flags_max_len = 0; - else - { - memcpy (new_str, __getopt_nonoption_flags, nonoption_flags_max_len); - memset (&new_str[nonoption_flags_max_len], '\0', - top + 1 - nonoption_flags_max_len); - nonoption_flags_max_len = top + 1; - __getopt_nonoption_flags = new_str; - } - } -#endif - - 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. */ - -#if defined (__STDC__) && __STDC__ -static const char *_getopt_initialize (int, char *const *, const char *); -#endif -static const char * -_getopt_initialize (argc, argv, optstring) - int argc; - char *const *argv; - const char *optstring; -{ - /* 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"); - - /* 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; - -#ifdef _LIBC - if (posixly_correct == NULL - && argc == original_argc && argv == original_argv) - { - if (nonoption_flags_max_len == 0) - { - if (__getopt_nonoption_flags == NULL - || __getopt_nonoption_flags[0] == '\0') - nonoption_flags_max_len = -1; - else - { - const char *orig_str = __getopt_nonoption_flags; - int len = nonoption_flags_max_len = strlen (orig_str); - if (nonoption_flags_max_len < argc) - nonoption_flags_max_len = argc; - __getopt_nonoption_flags = - (char *) malloc (nonoption_flags_max_len); - if (__getopt_nonoption_flags == NULL) - nonoption_flags_max_len = -1; - else - { - memcpy (__getopt_nonoption_flags, orig_str, len); - memset (&__getopt_nonoption_flags[len], '\0', - nonoption_flags_max_len - len); - } - } - } - nonoption_flags_len = nonoption_flags_max_len; - } - else - nonoption_flags_len = 0; -#endif - - 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. */ - -int -_getopt_internal (argc, argv, optstring, longopts, longind, long_only) - 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. */ -#ifdef _LIBC -#define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ - || (optind < nonoption_flags_len \ - && __getopt_nonoption_flags[optind] == '1')) -#else -#define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') -#endif - - 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_native_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) - { - if (opterr) - fprintf (stderr, _("%s: option `%s' is ambiguous\n"), - argv[0], argv[optind]); - 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 - { - if (opterr) - { - if (argv[optind - 1][1] == '-') - /* --option */ - fprintf (stderr, - _("%s: option `--%s' doesn't allow an argument\n"), - argv[0], pfound->name); - else - { - /* +option or -option */ - fprintf (stderr, - _("%s: option `%c%s' doesn't allow an argument\n"), - argv[0], argv[optind - 1][0], pfound->name); - } - } - nextchar += strlen (nextchar); - - optopt = pfound->val; - return '?'; - } - } - else if (pfound->has_arg == 1) - { - if (optind < argc) - optarg = argv[optind++]; - else - { - if (opterr) - fprintf (stderr, - _("%s: option `%s' requires an argument\n"), - argv[0], argv[optind - 1]); - 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) - { - if (opterr) - { - if (argv[optind][1] == '-') - /* --option */ - fprintf (stderr, _("%s: unrecognized option `--%s'\n"), - argv[0], nextchar); - else - /* +option or -option */ - fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), - argv[0], argv[optind][0], nextchar); - } - 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 == ':') - { - if (opterr) - { - if (posixly_correct) - /* 1003.2 specifies the format of this message. */ - fprintf (stderr, _("%s: illegal option -- %c\n"), - argv[0], c); - else - fprintf (stderr, _("%s: invalid option -- %c\n"), - argv[0], 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) - { - if (opterr) - { - /* 1003.2 specifies the format of this message. */ - fprintf (stderr, _("%s: option requires an argument -- %c\n"), - argv[0], c); - } - 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) - { - if (opterr) - fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), - argv[0], argv[optind]); - 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 - { - if (opterr) - fprintf (stderr, _("\ -%s: option `-W %s' doesn't allow an argument\n"), - argv[0], pfound->name); - - nextchar += strlen (nextchar); - return '?'; - } - } - else if (pfound->has_arg == 1) - { - if (optind < argc) - optarg = argv[optind++]; - else - { - if (opterr) - fprintf (stderr, - _("%s: option `%s' requires an argument\n"), - argv[0], argv[optind - 1]); - 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) - { - if (opterr) - { - /* 1003.2 specifies the format of this message. */ - fprintf (stderr, - _("%s: option requires an argument -- %c\n"), - argv[0], c); - } - 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; - } -} - -#endif /* Not ELIDE_CODE. */ - -#endif - -#ifdef _MSC_VER -# pragma warning(pop) -#endif - +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * 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 + */ + +#ifdef _MSC_VER +/* in VC this file will generate a lot of warning about old style function + * declarations. + */ +# pragma warning(push, 3) +#endif + +/* + * getopt entry points + * + * modified by Mike Borella + * + * $Id: getopt.c,v 1.4 2000/10/30 22:06:03 mborella Exp $ + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef HAVE_GETOPT_LONG + +/* 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. */ + +#include "getopt.h" + + +#include + +/* 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 +#if !defined (_LIBC) && defined (__GLIBC__) && __GLIBC__ >= 2 +#include +#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION +#define ELIDE_CODE +#endif +#endif + +#ifndef ELIDE_CODE + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#include +#endif + +#ifndef NULL +#define NULL 0 +#endif + +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_long_only (argc, argv, options, long_options, opt_index) + 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, 1); +} + +int +getopt (int argc, char * const * argv, const char * optstring) +{ + return _getopt_internal (argc, argv, optstring, + (const struct option *) 0, + (int *) 0, + 0); +} + +#endif /* Not ELIDE_CODE. */ + + +#ifndef _NO_PROTO +#define _NO_PROTO +#endif + + +//#include + +/* 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 +#if !defined (_LIBC) && defined (__GLIBC__) && __GLIBC__ >= 2 +#include +#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION +#define ELIDE_CODE +#endif +#endif + +#ifndef ELIDE_CODE + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +/* Don't include stdlib.h for non-GNU C libraries because some of them + contain conflicting prototypes for getopt. */ +#include +#include +#endif /* GNU C library. */ + +#ifdef VMS +#include +#if HAVE_STRING_H - 0 +#include +#endif +#endif + +#if defined (WIN32) && !defined (__CYGWIN32__) +/* It's not Unix, really. See? Capital letters. */ +#include +#define getpid() GetCurrentProcessId() +#endif + +#ifndef _ +/* This is for other GNU distributions with internationalized messages. + When compiling libc, the _ macro is predefined. */ +#ifdef HAVE_LIBINTL_H +# include +# define _(msgid) gettext (msgid) +#else +# define _(msgid) (msgid) +#endif +#endif + +/* 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. */ + +#include "getopt.h" + +/* 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; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* 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; + +#ifdef __GNU_LIBRARY__ +/* We want to avoid inclusion of string.h with non-GNU libraries + because there are many ways it can cause trouble. + On some systems, it contains special magic macros that don't work + in GCC. */ +#include +#define my_index pj_native_strchr +#else + +static char * +my_index (const char *str, int chr) +{ + while (*str) + { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + +/* If using GCC, we can safely declare strlen this way. + If not using GCC, it is ok not to declare it. */ +#ifdef __GNUC__ +/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. + That was relevant to code that was here before. */ +#if !defined (__STDC__) || !__STDC__ +/* gcc with -traditional declares the built-in strlen to return int, + and has done so at least since version 2.4.5. -- rms. */ +extern int strlen (const char *); +#endif /* not __STDC__ */ +#endif /* __GNUC__ */ + +#endif /* not __GNU_LIBRARY__ */ + +/* 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; + +#ifdef _LIBC +/* Bash 2.0 gives us an environment variable containing flags + indicating ARGV elements that should not be considered arguments. */ + +/* Defined in getopt_init.c */ +extern char *__getopt_nonoption_flags; + +static int nonoption_flags_max_len; +static int nonoption_flags_len; + +static int original_argc; +static char *const *original_argv; + +extern pid_t __libc_pid; + +/* Make sure the environment variable bash 2.0 puts in the environment + is valid for the getopt call we must make sure that the ARGV passed + to getopt is that one passed to the process. */ +static void +__attribute__ ((unused)) +store_args_and_env (int argc, char *const *argv) +{ + /* XXX This is no good solution. We should rather copy the args so + that we can compare them later. But we must not use malloc(3). */ + original_argc = argc; + original_argv = argv; +} +text_set_element (__libc_subinit, store_args_and_env); + +# define SWAP_FLAGS(ch1, ch2) \ + if (nonoption_flags_len > 0) \ + { \ + char __tmp = __getopt_nonoption_flags[ch1]; \ + __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ + __getopt_nonoption_flags[ch2] = __tmp; \ + } +#else /* !_LIBC */ +# define SWAP_FLAGS(ch1, ch2) +#endif /* _LIBC */ + +/* 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. */ + +#if defined (__STDC__) && __STDC__ +static void exchange (char **); +#endif + +static void +exchange (argv) + 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. */ + +#ifdef _LIBC + /* First make sure the handling of the `__getopt_nonoption_flags' + string can work normally. Our top argument must be in the range + of the string. */ + if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) + { + /* We must extend the array. The user plays games with us and + presents new arguments. */ + char *new_str = malloc (top + 1); + if (new_str == NULL) + nonoption_flags_len = nonoption_flags_max_len = 0; + else + { + memcpy (new_str, __getopt_nonoption_flags, nonoption_flags_max_len); + memset (&new_str[nonoption_flags_max_len], '\0', + top + 1 - nonoption_flags_max_len); + nonoption_flags_max_len = top + 1; + __getopt_nonoption_flags = new_str; + } + } +#endif + + 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. */ + +#if defined (__STDC__) && __STDC__ +static const char *_getopt_initialize (int, char *const *, const char *); +#endif +static const char * +_getopt_initialize (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + /* 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"); + + /* 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; + +#ifdef _LIBC + if (posixly_correct == NULL + && argc == original_argc && argv == original_argv) + { + if (nonoption_flags_max_len == 0) + { + if (__getopt_nonoption_flags == NULL + || __getopt_nonoption_flags[0] == '\0') + nonoption_flags_max_len = -1; + else + { + const char *orig_str = __getopt_nonoption_flags; + int len = nonoption_flags_max_len = strlen (orig_str); + if (nonoption_flags_max_len < argc) + nonoption_flags_max_len = argc; + __getopt_nonoption_flags = + (char *) malloc (nonoption_flags_max_len); + if (__getopt_nonoption_flags == NULL) + nonoption_flags_max_len = -1; + else + { + memcpy (__getopt_nonoption_flags, orig_str, len); + memset (&__getopt_nonoption_flags[len], '\0', + nonoption_flags_max_len - len); + } + } + } + nonoption_flags_len = nonoption_flags_max_len; + } + else + nonoption_flags_len = 0; +#endif + + 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. */ + +int +_getopt_internal (argc, argv, optstring, longopts, longind, long_only) + 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. */ +#ifdef _LIBC +#define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ + || (optind < nonoption_flags_len \ + && __getopt_nonoption_flags[optind] == '1')) +#else +#define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') +#endif + + 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_native_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) + { + if (opterr) + fprintf (stderr, _("%s: option `%s' is ambiguous\n"), + argv[0], argv[optind]); + 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 + { + if (opterr) + { + if (argv[optind - 1][1] == '-') + /* --option */ + fprintf (stderr, + _("%s: option `--%s' doesn't allow an argument\n"), + argv[0], pfound->name); + else + { + /* +option or -option */ + fprintf (stderr, + _("%s: option `%c%s' doesn't allow an argument\n"), + argv[0], argv[optind - 1][0], pfound->name); + } + } + nextchar += strlen (nextchar); + + optopt = pfound->val; + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + 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) + { + if (opterr) + { + if (argv[optind][1] == '-') + /* --option */ + fprintf (stderr, _("%s: unrecognized option `--%s'\n"), + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), + argv[0], argv[optind][0], nextchar); + } + 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 == ':') + { + if (opterr) + { + if (posixly_correct) + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: illegal option -- %c\n"), + argv[0], c); + else + fprintf (stderr, _("%s: invalid option -- %c\n"), + argv[0], 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) + { + if (opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + 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) + { + if (opterr) + fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), + argv[0], argv[optind]); + 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 + { + if (opterr) + fprintf (stderr, _("\ +%s: option `-W %s' doesn't allow an argument\n"), + argv[0], pfound->name); + + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + 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) + { + if (opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, + _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + 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; + } +} + +#endif /* Not ELIDE_CODE. */ + +#endif + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + diff --git a/pjsip/src/pjsua/getopt.h b/pjsip/src/pjsua/getopt.h index fbd8a3dd..3acdd5fc 100644 --- a/pjsip/src/pjsua/getopt.h +++ b/pjsip/src/pjsua/getopt.h @@ -1,141 +1,141 @@ -/* $Id$ */ -/* This file has now become GPL. */ -/* Declarations for getopt. - Copyright (C) 1989,90,91,92,93,94,96,97,98 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. */ - -#ifndef _GETOPT_H -#define _GETOPT_H 1 - -#ifdef __cplusplus -extern "C" { -#endif - -/* 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. */ - -extern char *optarg; - -/* 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. */ - -extern int optind; - -/* Callers store zero here to inhibit the error message `getopt' prints - for unrecognized options. */ - -extern int opterr; - -/* Set to an option character which was unrecognized. */ - -extern int optopt; - -/* Describe the long-named options requested by the application. - The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector - of `struct option' terminated by an element containing a name which is - zero. - - The field `has_arg' is: - no_argument (or 0) if the option does not take an argument, - required_argument (or 1) if the option requires an argument, - optional_argument (or 2) if the option takes an optional argument. - - If the field `flag' is not NULL, it points to a variable that is set - to the value given in the field `val' when the option is found, but - left unchanged if the option is not found. - - To have a long-named option do something other than set an `int' to - a compiled-in constant, such as set a value from `optarg', set the - option's `flag' field to zero and its `val' field to a nonzero - value (the equivalent single-letter option character, if there is - one). For long options that have a zero `flag' field, `getopt' - returns the contents of the `val' field. */ - -struct option -{ - const char *name; - /* has_arg can't be an enum because some compilers complain about - type mismatches in all the code that assumes it is an int. */ - int has_arg; - int *flag; - int val; -}; - -/* Names for the values of the `has_arg' field of `struct option'. */ - -# define no_argument 0 -# define required_argument 1 -# define optional_argument 2 - - -/* Get definitions and prototypes for functions to process the - arguments in ARGV (ARGC of them, minus the program name) for - options given in OPTS. - - Return the option character from OPTS just read. Return -1 when - there are no more options. For unrecognized options, or options - missing arguments, `optopt' is set to the option letter, and '?' is - returned. - - The OPTS string is a list of characters which are recognized option - letters, optionally followed by colons, specifying that that letter - takes an argument, to be placed in `optarg'. - - If a letter in OPTS is followed by two colons, its argument is - optional. This behavior is specific to the GNU `getopt'. - - The argument `--' causes premature termination of argument - scanning, explicitly telling `getopt' that there are no more - options. - - If OPTS begins with `--', then non-option arguments are treated as - arguments to the option '\0'. This behavior is specific to the GNU - `getopt'. */ - -int getopt (int argc, char *const *argv, const char *shortopts); - -int getopt_long (int argc, char *const *argv, const char *options, - const struct option *longopts, int *longind); -int getopt_long_only (int argc, char *const *argv, - const char *shortopts, - const struct option *longopts, int *longind); - -/* Internal only. Users should not call this directly. */ -int _getopt_internal (int argc, char *const *argv, - const char *shortopts, - const struct option *longopts, int *longind, - int long_only); - -#ifdef __cplusplus -} -#endif - -#endif /* getopt.h */ - +/* $Id$ */ +/* This file has now become GPL. */ +/* Declarations for getopt. + Copyright (C) 1989,90,91,92,93,94,96,97,98 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. */ + +#ifndef _GETOPT_H +#define _GETOPT_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* 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. */ + +extern char *optarg; + +/* 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. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ + const char *name; + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +# define no_argument 0 +# define required_argument 1 +# define optional_argument 2 + + +/* Get definitions and prototypes for functions to process the + arguments in ARGV (ARGC of them, minus the program name) for + options given in OPTS. + + Return the option character from OPTS just read. Return -1 when + there are no more options. For unrecognized options, or options + missing arguments, `optopt' is set to the option letter, and '?' is + returned. + + The OPTS string is a list of characters which are recognized option + letters, optionally followed by colons, specifying that that letter + takes an argument, to be placed in `optarg'. + + If a letter in OPTS is followed by two colons, its argument is + optional. This behavior is specific to the GNU `getopt'. + + The argument `--' causes premature termination of argument + scanning, explicitly telling `getopt' that there are no more + options. + + If OPTS begins with `--', then non-option arguments are treated as + arguments to the option '\0'. This behavior is specific to the GNU + `getopt'. */ + +int getopt (int argc, char *const *argv, const char *shortopts); + +int getopt_long (int argc, char *const *argv, const char *options, + const struct option *longopts, int *longind); +int getopt_long_only (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind); + +/* Internal only. Users should not call this directly. */ +int _getopt_internal (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind, + int long_only); + +#ifdef __cplusplus +} +#endif + +#endif /* getopt.h */ + diff --git a/pjsip/src/pjsua/main.c b/pjsip/src/pjsua/main.c index e4d1e1c8..09f9f8f3 100644 --- a/pjsip/src/pjsua/main.c +++ b/pjsip/src/pjsua/main.c @@ -1,1827 +1,1827 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono - * - * 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 -#include -#include -#include -#include -#include -#include -#include - -#define START_PORT 5060 -#define MAX_BUDDIES 32 -#define THIS_FILE "main.c" -#define MAX_PRESENTITY 32 -#define PRESENCE_TIMEOUT 60 - -/* By default we'll have one worker thread, except when threading - * is disabled. - */ -#if PJ_HAS_THREADS -# define WORKER_COUNT 1 -#else -# define WORKER_COUNT 0 -#endif - -/* Global variable. */ -static struct -{ - /* Control. */ - pj_pool_factory *pf; - pjsip_endpoint *endpt; - pj_pool_t *pool; - pjsip_user_agent *user_agent; - int worker_cnt; - int worker_quit_flag; - - /* User info. */ - char user_id[64]; - pj_str_t local_uri; - pj_str_t contact; - pj_str_t real_contact; - - /* Dialog. */ - pjsip_dlg *cur_dlg; - - /* Authentication. */ - int cred_count; - pjsip_cred_info cred_info[4]; - - /* Media stack. */ - pj_bool_t null_audio; - pj_med_mgr_t *mmgr; - - /* Misc. */ - int app_log_level; - char *log_filename; - FILE *log_file; - - /* Proxy URLs */ - pj_str_t proxy; - pj_str_t outbound_proxy; - - /* UA auto options. */ - int auto_answer; /* -1 to disable. */ - int auto_hangup; /* -1 to disable */ - - /* Registration. */ - pj_str_t registrar_uri; - pjsip_regc *regc; - pj_int32_t reg_timeout; - pj_timer_entry regc_timer; - - /* STUN */ - pj_str_t stun_srv1; - int stun_port1; - pj_str_t stun_srv2; - int stun_port2; - - /* UDP sockets and their public address. */ - int sip_port; - pj_sock_t sip_sock; - pj_sockaddr_in sip_sock_name; - pj_sock_t rtp_sock; - pj_sockaddr_in rtp_sock_name; - pj_sock_t rtcp_sock; - pj_sockaddr_in rtcp_sock_name; - - /* SIMPLE */ - pj_bool_t hide_status; - pj_bool_t offer_x_ms_msg; - int im_counter; - int buddy_cnt; - pj_str_t buddy[MAX_BUDDIES]; - pj_bool_t buddy_status[MAX_BUDDIES]; - pj_bool_t no_presence; - pjsip_presentity *buddy_pres[MAX_BUDDIES]; - - int pres_cnt; - pjsip_presentity *pres[MAX_PRESENTITY]; - -} global; - -enum { AUTO_ANSWER, AUTO_HANGUP }; - -/* This is the data that will be 'attached' on per dialog basis. */ -struct dialog_data -{ - /* Media session. */ - pj_media_session_t *msession; - - /* x-ms-chat session. */ - pj_bool_t x_ms_msg_session; - - /* Cached SDP body, updated when media session changed. */ - pjsip_msg_body *body; - - /* Timer. */ - pj_bool_t has_auto_timer; - pj_timer_entry auto_timer; -}; - -/* - * These are the callbacks to be registered to dialog to receive notifications - * about various events in the dialog. - */ -static void dlg_on_all_events (pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt, - pjsip_event *event ); -static void dlg_on_before_tx (pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_tx_data *tdata, int retransmission); -static void dlg_on_tx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_tx_data *tdata); -static void dlg_on_rx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_rx_data *rdata); -static void dlg_on_incoming (pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_rx_data *rdata); -static void dlg_on_calling (pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_tx_data *tdata); -static void dlg_on_provisional (pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_event *event); -static void dlg_on_connecting (pjsip_dlg *dlg, pjsip_event *event); -static void dlg_on_established (pjsip_dlg *dlg, pjsip_event *event); -static void dlg_on_disconnected (pjsip_dlg *dlg, pjsip_event *event); -static void dlg_on_terminated (pjsip_dlg *dlg); -static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event); - -/* The callback structure that will be registered to UA layer. */ -struct pjsip_dlg_callback dlg_callback = { - &dlg_on_all_events, - &dlg_on_before_tx, - &dlg_on_tx_msg, - &dlg_on_rx_msg, - &dlg_on_incoming, - &dlg_on_calling, - &dlg_on_provisional, - &dlg_on_connecting, - &dlg_on_established, - &dlg_on_disconnected, - &dlg_on_terminated, - &dlg_on_mid_call_evt -}; - - -/* - * Auxiliary things are put in misc.c, so that this main.c file is more - * readable. - */ -#include "misc.c" - -static void dlg_auto_timer_callback( pj_timer_heap_t *timer_heap, - struct pj_timer_entry *entry) -{ - pjsip_dlg *dlg = entry->user_data; - struct dialog_data *dlg_data = dlg->user_data; - - PJ_UNUSED_ARG(timer_heap) - - dlg_data->has_auto_timer = 0; - - if (entry->id == AUTO_ANSWER) { - pjsip_tx_data *tdata = pjsip_dlg_answer(dlg, 200); - if (tdata) { - struct dialog_data *dlg_data = global.cur_dlg->user_data; - tdata->msg->body = dlg_data->body; - pjsip_dlg_send_msg(dlg, tdata); - } - } else { - pjsip_tx_data *tdata = pjsip_dlg_disconnect(dlg, 500); - if (tdata) - pjsip_dlg_send_msg(dlg, tdata); - } -} - -static void update_registration(pjsip_regc *regc, int renew) -{ - pjsip_tx_data *tdata; - - PJ_LOG(3,(THIS_FILE, "Performing SIP registration...")); - - if (renew) { - tdata = pjsip_regc_register(regc, 1); - } else { - tdata = pjsip_regc_unregister(regc); - } - - pjsip_regc_send( regc, tdata ); -} - -static void regc_cb(struct pjsip_regc_cbparam *param) -{ - /* - * Print registration status. - */ - 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)); - global.regc = NULL; - - } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) { - PJ_LOG(3, (THIS_FILE, "SIP registration success, status=%d (%s), " - "will re-register in %d seconds", - param->code, - pjsip_get_status_text(param->code)->ptr, - param->expiration)); - - } else { - PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code)); - } -} - -static void pres_on_received_request(pjsip_presentity *pres, pjsip_rx_data *rdata, - int *timeout) -{ - int state; - int i; - char url[PJSIP_MAX_URL_SIZE]; - int urllen; - - PJ_UNUSED_ARG(rdata) - - if (*timeout > 0) { - state = PJSIP_EVENT_SUB_STATE_ACTIVE; - if (*timeout > 300) - *timeout = 300; - } else { - state = PJSIP_EVENT_SUB_STATE_TERMINATED; - } - - urllen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, rdata->from->uri, url, sizeof(url)-1); - if (urllen < 1) { - pj_native_strcpy(url, ""); - } else { - url[urllen] = '\0'; - } - PJ_LOG(3,(THIS_FILE, "Received presence request from %s, sub_state=%s", - url, - (state==PJSIP_EVENT_SUB_STATE_ACTIVE?"active":"terminated"))); - - for (i=0; isub->default_interval); -} - -/* This is called by presence framework when we receives presence update - * of a resource (buddy). - */ -static void pres_on_received_update(pjsip_presentity *pres, pj_bool_t is_open) -{ - int buddy_index = (int)pres->user_data; - - global.buddy_status[buddy_index] = is_open; - PJ_LOG(3,(THIS_FILE, "Presence update: %s is %s", - global.buddy[buddy_index].ptr, - (is_open ? "Online" : "Offline"))); -} - -/* This is called when the subscription is terminated. */ -static void pres_on_terminated(pjsip_presentity *pres, const pj_str_t *reason) -{ - if (pres->sub->role == PJSIP_ROLE_UAC) { - int buddy_index = (int)pres->user_data; - PJ_LOG(3,(THIS_FILE, "Presence subscription for %s is terminated (reason=%.*s)", - global.buddy[buddy_index].ptr, - reason->slen, reason->ptr)); - global.buddy_pres[buddy_index] = NULL; - global.buddy_status[buddy_index] = 0; - } else { - int i; - PJ_LOG(3,(THIS_FILE, "Notifier terminated (reason=%.*s)", - reason->slen, reason->ptr)); - pjsip_presence_notify(pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 1); - for (i=0; idata, buf, size); -} - -/* When media session has changed, call this function to update the cached body - * information in the dialog. - */ -static pjsip_msg_body *create_msg_body (pjsip_dlg *dlg, pj_bool_t is_ack_msg) -{ - struct dialog_data *dlg_data = dlg->user_data; - pjsdp_session_desc *sdp; - - sdp = pj_media_session_create_sdp (dlg_data->msession, dlg->pool, is_ack_msg); - if (!sdp) { - dlg_data->body = NULL; - return NULL; - } - - /* For outgoing INVITE, if we offer "x-ms-message" line, then add a new - * "m=" line in the SDP. - */ - if (dlg_data->x_ms_msg_session >= 0 && - dlg_data->x_ms_msg_session >= (int)sdp->media_count) - { - pjsdp_media_desc *m = pj_pool_calloc(dlg->pool, 1, sizeof(*m)); - sdp->media[sdp->media_count] = m; - dlg_data->x_ms_msg_session = sdp->media_count++; - } - - /* - * For "x-ms-message" line, remove all attributes and connection line etc. - */ - if (dlg_data->x_ms_msg_session >= 0) { - pjsdp_media_desc *m = sdp->media[dlg_data->x_ms_msg_session]; - if (m) { - m->desc.media = pj_str("x-ms-message"); - m->desc.port = 5060; - m->desc.transport = pj_str("sip"); - m->desc.fmt_count = 1; - m->desc.fmt[0] = pj_str("null"); - m->attr_count = 0; - m->conn = NULL; - } - } - - dlg_data->body = pj_pool_calloc(dlg->pool, 1, sizeof(*dlg_data->body)); - dlg_data->body->content_type.type = pj_str("application"); - dlg_data->body->content_type.subtype = pj_str("sdp"); - dlg_data->body->len = 0; /* ignored */ - dlg_data->body->print_body = &print_msg_body; - - dlg_data->body->data = sdp; - return dlg_data->body; -} - -/* This callback will be called on every occurence of events in dialogs */ -static void dlg_on_all_events(pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt, - pjsip_event *event ) -{ - PJ_UNUSED_ARG(dlg_evt) - PJ_UNUSED_ARG(event) - - PJ_LOG(4, (THIS_FILE, "dlg_on_all_events %p", dlg)); -} - -/* This callback is called before each outgoing msg is sent (including - * retransmission). Application can override this notification if it wants - * to modify the message before transmission or if it wants to do something - * else for each transmission. - */ -static void dlg_on_before_tx(pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_tx_data *tdata, int ret_cnt) -{ - PJ_UNUSED_ARG(tsx) - PJ_UNUSED_ARG(tdata) - - if (ret_cnt > 0) { - PJ_LOG(3, (THIS_FILE, "Dialog %s: retransmitting message (cnt=%d)", - dlg->obj_name, ret_cnt)); - } -} - -/* This callback is called after a message is sent. */ -static void dlg_on_tx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_tx_data *tdata) -{ - PJ_UNUSED_ARG(tsx) - PJ_UNUSED_ARG(tdata) - - PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg)); -} - -/* This callback is called on receipt of incoming message. */ -static void dlg_on_rx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_rx_data *rdata) -{ - PJ_UNUSED_ARG(tsx) - PJ_UNUSED_ARG(rdata) - PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg)); -} - -/* This callback is called after dialog has sent INVITE */ -static void dlg_on_calling(pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_tx_data *tdata) -{ - PJ_UNUSED_ARG(tsx) - PJ_UNUSED_ARG(tdata) - - pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG && - tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD && - tsx->method.id == PJSIP_INVITE_METHOD); - - PJ_LOG(3, (THIS_FILE, "Dialog %s: start calling...", dlg->obj_name)); -} - -static void create_session_from_sdp( pjsip_dlg *dlg, pjsdp_session_desc *sdp) -{ - struct dialog_data *dlg_data = dlg->user_data; - pj_bool_t sdp_x_ms_msg_index = -1; - int i; - int mcnt; - const pj_media_stream_info *mi[PJSDP_MAX_MEDIA]; - int has_active; - pj_media_sock_info sock_info; - - /* Find "m=x-ms-message" line in the SDP. */ - for (i=0; i<(int)sdp->media_count; ++i) { - if (pj_stricmp2(&sdp->media[i]->desc.media, "x-ms-message")==0) - sdp_x_ms_msg_index = i; - } - - /* - * Create media session. - */ - pj_memset(&sock_info, 0, sizeof(sock_info)); - sock_info.rtp_sock = global.rtp_sock; - sock_info.rtcp_sock = global.rtcp_sock; - pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in)); - - dlg_data->msession = pj_media_session_create_from_sdp (global.mmgr, sdp, &sock_info); - - /* A session will always be created, unless there is memory - * alloc problem. - */ - pj_assert(dlg_data->msession); - - /* See if we can take the offer by checking that we have at least - * one media stream active. - */ - mcnt = pj_media_session_enum_streams(dlg_data->msession, PJSDP_MAX_MEDIA, mi); - for (i=0, has_active=0; ifmt_cnt>0 && mi[i]->dir!=PJ_MEDIA_DIR_NONE) { - has_active = 1; - break; - } - } - - if (!has_active && sdp_x_ms_msg_index==-1) { - pjsip_tx_data *tdata; - - /* Unable to accept remote's SDP. - * Answer with 488 (Not Acceptable Here) - */ - /* Create 488 response. */ - tdata = pjsip_dlg_answer(dlg, PJSIP_SC_NOT_ACCEPTABLE_HERE); - - /* Send response. */ - if (tdata) - pjsip_dlg_send_msg(dlg, tdata); - return; - } - - dlg_data->x_ms_msg_session = sdp_x_ms_msg_index; - - /* Create msg body to be used later in 2xx/response */ - create_msg_body(dlg, 0); - -} - -/* This callback is called after an INVITE is received. */ -static void dlg_on_incoming(pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_rx_data *rdata) -{ - struct dialog_data *dlg_data; - pjsip_msg *msg; - pjsip_tx_data *tdata; - char buf[128]; - int len; - - PJ_UNUSED_ARG(tsx) - - pj_assert(rdata->msg->type == PJSIP_REQUEST_MSG && - rdata->msg->line.req.method.id == PJSIP_INVITE_METHOD && - tsx->method.id == PJSIP_INVITE_METHOD); - - /* - * Notify user! - */ - PJ_LOG(3, (THIS_FILE, "")); - PJ_LOG(3, (THIS_FILE, "INCOMING CALL ON DIALOG %s!!", dlg->obj_name)); - PJ_LOG(3, (THIS_FILE, "")); - len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, - (pjsip_name_addr*)dlg->remote.info->uri, - buf, sizeof(buf)-1); - if (len > 0) { - buf[len] = '\0'; - PJ_LOG(3,(THIS_FILE, "From:\t%s", buf)); - } - len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, - (pjsip_name_addr*)dlg->local.info->uri, - buf, sizeof(buf)-1); - if (len > 0) { - buf[len] = '\0'; - PJ_LOG(3,(THIS_FILE, "To:\t%s", buf)); - } - PJ_LOG(3, (THIS_FILE, "Press 'a' to answer, or 'h' to hangup!!", dlg->obj_name)); - PJ_LOG(3, (THIS_FILE, "")); - - /* - * Process incoming dialog. - */ - - dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data)); - dlg->user_data = dlg_data; - - /* Update contact. */ - pjsip_dlg_set_contact(dlg, &global.contact); - - /* Initialize credentials. */ - pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info); - - /* Create media session if the request has "application/sdp" body. */ - msg = rdata->msg; - if (msg->body && - pj_stricmp2(&msg->body->content_type.type, "application")==0 && - pj_stricmp2(&msg->body->content_type.subtype, "sdp")==0) - { - pjsdp_session_desc *sdp; - - /* Parse SDP body, and instantiate media session based on remote's SDP. - * Then create our SDP body from the session. - */ - sdp = pjsdp_parse (msg->body->data, msg->body->len, rdata->pool); - if (!sdp) - goto send_answer; - - create_session_from_sdp(dlg, sdp); - - } else if (msg->body) { - /* The request has a message body other than "application/sdp" */ - pjsip_accept_hdr *accept; - - /* Create response. */ - tdata = pjsip_dlg_answer(dlg, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); - - /* Add "Accept" header. */ - accept = pjsip_accept_hdr_create(tdata->pool); - accept->values[0] = pj_str("application/sdp"); - accept->count = 1; - pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)accept); - - /* Send response. */ - pjsip_dlg_send_msg(dlg, tdata); - return; - - } else { - /* The request has no message body. We can take this request, but - * no media session will be activated. - */ - /* Nothing to do here. */ - } - -send_answer: - /* Immediately answer with 100 (or 180? */ - tdata = pjsip_dlg_answer( dlg, PJSIP_SC_RINGING ); - pjsip_dlg_send_msg(dlg, tdata); - - /* Set current dialog to this dialog if we don't currently have - * current dialog. - */ - if (global.cur_dlg == NULL) { - global.cur_dlg = dlg; - } - - /* Auto-answer if option is specified. */ - if (global.auto_answer >= 0) { - pj_time_val delay = { 0, 0}; - struct dialog_data *dlg_data = dlg->user_data; - - PJ_LOG(4, (THIS_FILE, "Scheduling auto-answer in %d seconds", - global.auto_answer)); - - delay.sec = global.auto_answer; - dlg_data->auto_timer.user_data = dlg; - dlg_data->auto_timer.id = AUTO_ANSWER; - dlg_data->auto_timer.cb = &dlg_auto_timer_callback; - dlg_data->has_auto_timer = 1; - pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay); - } -} - -/* This callback is called when dialog has sent/received a provisional response - * to INVITE. - */ -static void dlg_on_provisional(pjsip_dlg *dlg, pjsip_transaction *tsx, - pjsip_event *event) -{ - const char *action; - - pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && - event->src.tdata->msg->type == PJSIP_RESPONSE_MSG && - event->src.tdata->msg->line.status.code/100 == 1 && - tsx->method.id == PJSIP_INVITE_METHOD) - || - (event->src_type == PJSIP_EVENT_RX_MSG && - event->src.rdata->msg->type == PJSIP_RESPONSE_MSG && - event->src.rdata->msg->line.status.code/100 == 1 && - tsx->method.id == PJSIP_INVITE_METHOD)); - - if (event->src_type == PJSIP_EVENT_TX_MSG) - action = "Sending"; - else - action = "Received"; - - PJ_LOG(3, (THIS_FILE, "Dialog %s: %s %d (%s)", - dlg->obj_name, action, tsx->status_code, - pjsip_get_status_text(tsx->status_code)->ptr)); -} - -/* This callback is called when 200 response to INVITE is sent/received. */ -static void dlg_on_connecting(pjsip_dlg *dlg, pjsip_event *event) -{ - struct dialog_data *dlg_data = dlg->user_data; - const char *action; - - pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && - event->src.tdata->msg->type == PJSIP_RESPONSE_MSG && - event->src.tdata->msg->line.status.code/100 == 2) - || - (event->src_type == PJSIP_EVENT_RX_MSG && - event->src.rdata->msg->type == PJSIP_RESPONSE_MSG && - event->src.rdata->msg->line.status.code/100 == 2)); - - if (event->src_type == PJSIP_EVENT_RX_MSG) - action = "Received"; - else - action = "Sending"; - - PJ_LOG(3, (THIS_FILE, "Dialog %s: %s 200 (OK)", dlg->obj_name, action)); - - if (event->src_type == PJSIP_EVENT_RX_MSG) { - /* On receipt of 2xx response, negotiate our media capability - * and start media. - */ - pjsip_msg *msg = event->src.rdata->msg; - pjsip_msg_body *body; - pjsdp_session_desc *sdp; - - /* Get SDP from message. */ - - /* Ignore if no SDP body is present. */ - body = msg->body; - if (!body) { - PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no body!", - dlg->obj_name)); - return; - } - - if (pj_stricmp2(&body->content_type.type, "application") != 0 && - pj_stricmp2(&body->content_type.subtype, "sdp") != 0) - { - PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no SDP body!", - dlg->obj_name)); - return; - } - - /* Got what seems to be a SDP content. Parse it. */ - sdp = pjsdp_parse (body->data, body->len, event->src.rdata->pool); - if (!sdp) { - PJ_LOG(3, (THIS_FILE, "Dialog %s: SDP syntax error!", - dlg->obj_name)); - return; - } - - /* Negotiate media session with remote's media capability. */ - if (pj_media_session_update (dlg_data->msession, sdp) != 0) { - PJ_LOG(3, (THIS_FILE, "Dialog %s: media session update error!", - dlg->obj_name)); - return; - } - - /* Update the saved SDP body because media session has changed. - * Also set ack flag to '1', because we only want to send one format/ - * codec for each media streams. - */ - create_msg_body(dlg, 1); - - /* Activate media. */ - pj_media_session_activate (dlg_data->msession); - - } else { - pjsip_msg *msg = event->src.tdata->msg; - - if (msg->body) { - /* On transmission of 2xx response, start media session. */ - pj_media_session_activate (dlg_data->msession); - } - } - -} - -/* This callback is called when ACK to initial INVITE is sent/received. */ -static void dlg_on_established(pjsip_dlg *dlg, pjsip_event *event) -{ - const char *action; - - pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && - event->src.tdata->msg->type == PJSIP_REQUEST_MSG && - event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) - || - (event->src_type == PJSIP_EVENT_RX_MSG && - event->src.rdata->msg->type == PJSIP_REQUEST_MSG && - event->src.rdata->msg->line.req.method.id == PJSIP_ACK_METHOD)); - - if (event->src_type == PJSIP_EVENT_RX_MSG) - action = "Received"; - else - action = "Sending"; - - PJ_LOG(3, (THIS_FILE, "Dialog %s: %s ACK, dialog is ESTABLISHED", - dlg->obj_name, action)); - - /* Attach SDP body for outgoing ACK. */ - if (event->src_type == PJSIP_EVENT_TX_MSG && - event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) - { - struct dialog_data *dlg_data = dlg->user_data; - event->src.tdata->msg->body = dlg_data->body; - } - - /* Auto-hangup if option is specified. */ - if (global.auto_hangup >= 0) { - pj_time_val delay = { 0, 0}; - struct dialog_data *dlg_data = dlg->user_data; - - PJ_LOG(4, (THIS_FILE, "Scheduling auto-hangup in %d seconds", - global.auto_hangup)); - - delay.sec = global.auto_hangup; - dlg_data->auto_timer.user_data = dlg; - dlg_data->auto_timer.id = AUTO_HANGUP; - dlg_data->auto_timer.cb = &dlg_auto_timer_callback; - dlg_data->has_auto_timer = 1; - pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay); - } -} - - -/* This callback is called when dialog is disconnected (because of final - * response, BYE, or timer). - */ -static void dlg_on_disconnected(pjsip_dlg *dlg, pjsip_event *event) -{ - struct dialog_data *dlg_data = dlg->user_data; - int status_code; - const pj_str_t *reason; - - PJ_UNUSED_ARG(event) - - /* Cancel auto-answer/auto-hangup timer. */ - if (dlg_data->has_auto_timer) { - pjsip_endpt_cancel_timer(dlg->ua->endpt, &dlg_data->auto_timer); - dlg_data->has_auto_timer = 0; - } - - if (dlg->invite_tsx) - status_code = dlg->invite_tsx->status_code; - else - status_code = 200; - - if (event->obj.tsx->method.id == PJSIP_INVITE_METHOD) { - if (event->src_type == PJSIP_EVENT_RX_MSG) - reason = &event->src.rdata->msg->line.status.reason; - else if (event->src_type == PJSIP_EVENT_TX_MSG) - reason = &event->src.tdata->msg->line.status.reason; - else - reason = pjsip_get_status_text(event->obj.tsx->status_code); - } else { - reason = &event->obj.tsx->method.name; - } - - PJ_LOG(3, (THIS_FILE, "Dialog %s: DISCONNECTED! Reason=%d (%.*s)", - dlg->obj_name, status_code, - reason->slen, reason->ptr)); - - if (dlg_data->msession) { - pj_media_session_destroy (dlg_data->msession); - dlg_data->msession = NULL; - } -} - -/* This callback is called when dialog is about to be destroyed. */ -static void dlg_on_terminated(pjsip_dlg *dlg) -{ - PJ_LOG(3, (THIS_FILE, "Dialog %s: terminated!", dlg->obj_name)); - - /* If current dialog is equal to this dialog, update it. */ - if (global.cur_dlg == dlg) { - global.cur_dlg = global.cur_dlg->next; - if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { - global.cur_dlg = NULL; - } - } -} - -/* This callback is called for any requests when dialog is established. */ -static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event) -{ - pjsip_transaction *tsx = event->obj.tsx; - - if (event->src_type == PJSIP_EVENT_RX_MSG && - event->src.rdata->msg->type == PJSIP_REQUEST_MSG) - { - if (event->src.rdata->cseq->method.id == PJSIP_INVITE_METHOD) { - /* Re-invitation. */ - pjsip_tx_data *tdata; - - PJ_LOG(3,(THIS_FILE, "Dialog %s: accepting re-invitation (dummy)", - dlg->obj_name)); - tdata = pjsip_dlg_answer(dlg, 200); - if (tdata) { - struct dialog_data *dlg_data = dlg->user_data; - tdata->msg->body = dlg_data->body; - pjsip_dlg_send_msg(dlg, tdata); - } - } else { - /* Don't worry, endpoint will answer with 500 or whetever. */ - } - - } else if (tsx->status_code/100 == 2) { - PJ_LOG(3,(THIS_FILE, "Dialog %s: outgoing %.*s success: %d (%s)", - dlg->obj_name, - tsx->method.name.slen, tsx->method.name.ptr, - tsx->status_code, - pjsip_get_status_text(tsx->status_code)->ptr)); - - - } else if (tsx->status_code >= 300) { - pj_bool_t report_failure = PJ_TRUE; - - /* Check for authentication failures. */ - if (tsx->status_code==401 || tsx->status_code==407) { - pjsip_tx_data *tdata; - tdata = pjsip_auth_reinit_req( global.endpt, - dlg->pool, &dlg->auth_sess, - dlg->cred_count, dlg->cred_info, - tsx->last_tx, event->src.rdata ); - if (tdata) { - int rc; - rc = pjsip_dlg_send_msg( dlg, tdata); - report_failure = (rc != 0); - } - } - if (report_failure) { - const pj_str_t *reason; - if (event->src_type == PJSIP_EVENT_RX_MSG) { - reason = &event->src.rdata->msg->line.status.reason; - } else { - reason = pjsip_get_status_text(tsx->status_code); - } - PJ_LOG(2,(THIS_FILE, "Dialog %s: outgoing request failed: %d (%.*s)", - dlg->obj_name, tsx->status_code, - reason->slen, reason->ptr)); - } - } -} - -/* Initialize sockets and optionally get the public address via STUN. */ -static pj_status_t init_sockets() -{ - enum { - RTP_START_PORT = 4000, - RTP_RANDOM_START = 2, - RTP_RETRY = 10 - }; - enum { - SIP_SOCK, - RTP_SOCK, - RTCP_SOCK, - }; - int i; - int rtp_port; - pj_sock_t sock[3]; - pj_sockaddr_in mapped_addr[3]; - - for (i=0; i<3; ++i) - sock[i] = PJ_INVALID_SOCKET; - - /* Create and bind SIP UDP socket. */ - sock[SIP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0); - if (sock[SIP_SOCK] == PJ_INVALID_SOCKET) { - PJ_LOG(2,(THIS_FILE, "Unable to create socket")); - goto on_error; - } - if (pj_sock_bind_in(sock[SIP_SOCK], 0, (pj_uint16_t)global.sip_port) != 0) { - PJ_LOG(2,(THIS_FILE, "Unable to bind to SIP port")); - goto on_error; - } - - /* Initialize start of RTP port to try. */ - rtp_port = RTP_START_PORT + (pj_rand() % RTP_RANDOM_START) / 2; - - /* Loop retry to bind RTP and RTCP sockets. */ - for (i=0; iuser.slen) - strncpy(global.user_id, url->user.ptr, url->user.slen); - } - } - pj_pool_release(pool); - } - - if (global.user_id[0]=='\0') { - pj_native_strcpy(global.user_id, "user"); - } - - /* build contact */ - global.real_contact.ptr = local_uri; - global.real_contact.slen = - sprintf(local_uri, "", global.user_id, local_addr, global.sip_port); - - if (global.contact.slen == 0) - global.contact = global.real_contact; - - /* initialize local_uri with contact if it's not specified in cmdline */ - if (global.local_uri.slen == 0) - global.local_uri = global.contact; - - /* Init proxy. */ - if (global.proxy.slen || global.outbound_proxy.slen) { - int count = 0; - pj_str_t proxy_url[2]; - - if (global.outbound_proxy.slen) { - proxy_url[count++] = global.outbound_proxy; - } - if (global.proxy.slen) { - proxy_url[count++] = global.proxy; - } - - if (pjsip_endpt_set_proxies(global.endpt, count, proxy_url) != 0) { - PJ_LOG(2,(THIS_FILE, "Error setting proxy address!")); - return -1; - } - } - - /* initialize SIP registration if registrar is configured */ - if (global.registrar_uri.slen) { - global.regc = pjsip_regc_create( global.endpt, NULL, ®c_cb); - pjsip_regc_init( global.regc, &global.registrar_uri, - &global.local_uri, - &global.local_uri, - 1, &global.contact, - global.reg_timeout); - pjsip_regc_set_credentials( global.regc, global.cred_count, global.cred_info ); - } - - return PJ_SUCCESS; -} - -/* Worker thread function, only used when threading is enabled. */ -static void *PJ_THREAD_FUNC worker_thread(void *unused) -{ - PJ_UNUSED_ARG(unused) - - while (!global.worker_quit_flag) { - pj_time_val timeout = { 0, 10 }; - pjsip_endpt_handle_events (global.endpt, &timeout); - } - return NULL; -} - - -/* Make call to the specified URI. */ -static pjsip_dlg *make_call(pj_str_t *remote_uri) -{ - pjsip_dlg *dlg; - pj_str_t local = global.contact; - pj_str_t remote = *remote_uri; - struct dialog_data *dlg_data; - pjsip_tx_data *tdata; - pj_media_sock_info sock_info; - - /* Create new dialog instance. */ - dlg = pjsip_ua_create_dialog(global.user_agent, PJSIP_ROLE_UAC); - - /* Attach our own user data. */ - dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data)); - dlg->user_data = dlg_data; - - /* Create media session. */ - pj_memset(&sock_info, 0, sizeof(sock_info)); - sock_info.rtp_sock = global.rtp_sock; - sock_info.rtcp_sock = global.rtcp_sock; - pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in)); - - dlg_data->msession = pj_media_session_create (global.mmgr, &sock_info); - dlg_data->x_ms_msg_session = -1; - - if (global.offer_x_ms_msg) { - const pj_media_stream_info *minfo[32]; - unsigned cnt; - - cnt = pj_media_session_enum_streams(dlg_data->msession, 32, minfo); - if (cnt > 0) - dlg_data->x_ms_msg_session = cnt; - } - - /* Initialize dialog with local and remote URI. */ - if (pjsip_dlg_init(dlg, &local, &remote, NULL) != PJ_SUCCESS) { - pjsip_ua_destroy_dialog(dlg); - return NULL; - } - - /* Initialize credentials. */ - pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info); - - /* Send INVITE! */ - tdata = pjsip_dlg_invite(dlg); - tdata->msg->body = create_msg_body (dlg, 0); - - if (pjsip_dlg_send_msg(dlg, tdata) != PJ_SUCCESS) { - pjsip_ua_destroy_dialog(dlg); - return NULL; - } - - return dlg; -} - -/* - * Callback to receive incoming IM message. - */ -static int on_incoming_im_msg(pjsip_rx_data *rdata) -{ - pjsip_msg *msg = rdata->msg; - pjsip_msg_body *body = msg->body; - int len; - char to[128], from[128]; - - - len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, - rdata->from->uri, from, sizeof(from)); - if (len > 0) from[len] = '\0'; - else pj_native_strcpy(from, ""); - - len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, - rdata->to->uri, to, sizeof(to)); - if (len > 0) to[len] = '\0'; - else pj_native_strcpy(to, ""); - - PJ_LOG(3,(THIS_FILE, "Incoming instant message:")); - - printf("----- BEGIN INSTANT MESSAGE ----->\n"); - printf("From:\t%s\n", from); - printf("To:\t%s\n", to); - printf("Body:\n%.*s\n", (body ? body->len : 0), (body ? (char*)body->data : "")); - printf("<------ END INSTANT MESSAGE ------\n"); - - fflush(stdout); - - /* Must answer with final response. */ - return 200; -} - -/* - * Input URL. - */ -static pj_str_t *ui_input_url(pj_str_t *out, char *buf, int len, int *selection) -{ - int i; - - *selection = -1; - - printf("\nBuddy list:\n"); - printf("---------------------------------------\n"); - for (i=0; i\n", i+1, global.buddy[i].ptr, - (global.buddy_status[i]?"Online":"Offline")); - } - printf("-------------------------------------\n"); - - printf("Choices\n" - "\t0 For current dialog.\n" - "\t[1-%02d] Select from buddy list\n" - "\tURL An URL\n" - , global.buddy_cnt); - printf("Input: "); - - fflush(stdout); - fgets(buf, len, stdin); - buf[strlen(buf)-1] = '\0'; /* remove trailing newline. */ - - while (isspace(*buf)) ++buf; - - if (!*buf || *buf=='\n' || *buf=='\r') - return NULL; - - i = atoi(buf); - - if (i == 0) { - if (isdigit(*buf)) { - *selection = 0; - *out = pj_str("0"); - return out; - } else { - if (verify_sip_url(buf) != 0) { - puts("Invalid URL specified!"); - return NULL; - } - *out = pj_str(buf); - return out; - } - } else if (i > global.buddy_cnt || i < 0) { - printf("Error: invalid selection!\n"); - return NULL; - } else { - *out = global.buddy[i-1]; - *selection = i; - return out; - } -} - - -static void generic_request_callback( void *token, pjsip_event *event ) -{ - pjsip_transaction *tsx = event->obj.tsx; - - PJ_UNUSED_ARG(token) - - if (tsx->status_code/100 == 2) { - PJ_LOG(3,(THIS_FILE, "Outgoing %.*s %d (%s)", - event->obj.tsx->method.name.slen, - event->obj.tsx->method.name.ptr, - tsx->status_code, - pjsip_get_status_text(tsx->status_code)->ptr)); - } else if (tsx->status_code==401 || tsx->status_code==407) { - pjsip_tx_data *tdata; - tdata = pjsip_auth_reinit_req( global.endpt, - global.pool, NULL, global.cred_count, global.cred_info, - tsx->last_tx, event->src.rdata); - if (tdata) { - int rc; - pjsip_cseq_hdr *cseq; - cseq = (pjsip_cseq_hdr*)pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); - cseq->cseq++; - rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL, - &generic_request_callback); - if (rc == 0) - return; - } - PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%s)", - event->obj.tsx->method.name.slen, - event->obj.tsx->method.name.ptr, - event->obj.tsx->status_code, - pjsip_get_status_text(event->obj.tsx->status_code)->ptr)); - } else { - const pj_str_t *reason; - if (event->src_type == PJSIP_EVENT_RX_MSG) - reason = &event->src.rdata->msg->line.status.reason; - else - reason = pjsip_get_status_text(tsx->status_code); - PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%.*s)", - event->obj.tsx->method.name.slen, - event->obj.tsx->method.name.ptr, - event->obj.tsx->status_code, - reason->slen, reason->ptr)); - } -} - - -static void ui_send_im_message() -{ - char line[100]; - char text_buf[100]; - pj_str_t str; - pj_str_t text_msg; - int selection, rc; - pjsip_tx_data *tdata; - - if (ui_input_url(&str, line, sizeof(line), &selection) == NULL) - return; - - - printf("Enter text to send (empty to cancel): "); fflush(stdout); - fgets(text_buf, sizeof(text_buf), stdin); - text_buf[strlen(text_buf)-1] = '\0'; - if (!*text_buf) - return; - - text_msg = pj_str(text_buf); - - if (selection==0) { - pjsip_method message_method; - pj_str_t str_MESSAGE = { "MESSAGE", 7 }; - - /* Send IM to current dialog. */ - if (global.cur_dlg == NULL || global.cur_dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) { - printf("No current dialog or dialog state is not ESTABLISHED!\n"); - return; - } - - pjsip_method_init( &message_method, global.cur_dlg->pool, &str_MESSAGE); - tdata = pjsip_dlg_create_request( global.cur_dlg, &message_method, -1 ); - - if (tdata) { - /* Create message body for the text. */ - pjsip_msg_body *body = pj_pool_calloc(tdata->pool, 1, sizeof(*body)); - body->content_type.type = pj_str("text"); - body->content_type.subtype = pj_str("plain"); - body->data = pj_pool_alloc(tdata->pool, text_msg.slen); - pj_memcpy(body->data, text_msg.ptr, text_msg.slen); - body->len = text_msg.slen; - body->print_body = &pjsip_print_text_body; - - /* Assign body to message, and send the message! */ - tdata->msg->body = body; - pjsip_dlg_send_msg( global.cur_dlg, tdata ); - } - - } else { - /* Send IM to buddy list. */ - pjsip_method message; - static pj_str_t MESSAGE = { "MESSAGE", 7 }; - pjsip_method_init_np(&message, &MESSAGE); - tdata = pjsip_endpt_create_request(global.endpt, &message, - &str, - &global.real_contact, - &str, &global.real_contact, NULL, -1, - &text_msg); - if (!tdata) { - puts("Error creating request"); - return; - } - rc = pjsip_endpt_send_request(global.endpt, tdata, -1, NULL, &generic_request_callback); - if (rc == 0) { - printf("Sending IM message %d\n", global.im_counter); - ++global.im_counter; - } else { - printf("Error: unable to send IM message!\n"); - } - } -} - -static void ui_send_options() -{ - char line[100]; - pj_str_t str; - int selection, rc; - pjsip_tx_data *tdata; - pjsip_method options; - - if (ui_input_url(&str, line, sizeof(line), &selection) == NULL) - return; - - pjsip_method_set( &options, PJSIP_OPTIONS_METHOD ); - - if (selection == 0) { - /* Send OPTIONS to current dialog. */ - tdata = pjsip_dlg_create_request(global.cur_dlg, &options, -1); - if (tdata) - pjsip_dlg_send_msg( global.cur_dlg, tdata ); - } else { - /* Send OPTIONS to arbitrary party. */ - tdata = pjsip_endpt_create_request( global.endpt, &options, - &str, - &global.local_uri, &str, - &global.real_contact, - NULL, -1, NULL); - if (tdata) { - rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL, - &generic_request_callback); - if (rc != 0) - PJ_LOG(2,(THIS_FILE, "Error sending OPTIONS!")); - } - } -} - -static void init_presence() -{ - const pjsip_presence_cb pres_cb = { - NULL, - &pres_on_received_request, - &pres_on_received_refresh, - &pres_on_received_update, - &pres_on_terminated - }; - - pjsip_presence_init(&pres_cb); -} - -/* Subscribe presence information for all buddies. */ -static void subscribe_buddies_presence() -{ - int i; - for (i=0; i=0; --i) { - pj_thread_join(thread[i]); - pj_thread_destroy(thread[i]); - } - pj_caching_pool_destroy(&cp); - return 1; - } - } - - printf("Worker thread count: %d\n", global.worker_cnt); - - /* Perform registration, if required. */ - if (global.regc) { - update_registration(global.regc, 1); - } - - /* Initialize media manager. */ - global.mmgr = pj_med_mgr_create(global.pf); - - /* Init presence. */ - init_presence(); - - /* Subscribe presence information of all buddies. */ - if (!global.no_presence) - subscribe_buddies_presence(); - - /* Initializatio completes, loop waiting for commands. */ - for (;!global.worker_quit_flag;) { - pj_str_t str; - char line[128]; - -#if WORKER_COUNT==0 - /* If worker thread does not exist, main thread must poll for evetns. - * But this won't work very well since main thread is blocked by - * fgets(). So keep pressing the ENTER key to get the events! - */ - pj_time_val timeout = { 0, 100 }; - pjsip_endpt_handle_events(global.endpt, &timeout); - puts("Keep pressing ENTER key to get the events!"); -#endif - - printf("\nCurrent dialog: "); - print_dialog(global.cur_dlg); - puts(""); - - keystroke_help(); - - fgets(line, sizeof(line), stdin); - - switch (*line) { - case 'm': - puts("Make outgoing call"); - if (ui_input_url(&str, line, sizeof(line), &i) != NULL) { - pjsip_dlg *dlg = make_call(&str); - if (global.cur_dlg == NULL) { - global.cur_dlg = dlg; - } - } - break; - case 'i': - puts("Send Instant Messaging"); - ui_send_im_message(); - break; - case 'o': - puts("Send OPTIONS"); - ui_send_options(); - break; - case 'a': - if (global.cur_dlg) { - unsigned code; - pjsip_tx_data *tdata; - struct dialog_data *dlg_data = global.cur_dlg->user_data; - - printf("Answer with status code (1xx-6xx): "); - fflush(stdout); - fgets(line, sizeof(line), stdin); - str = pj_str(line); - str.slen -= 1; - - code = pj_strtoul(&str); - tdata = pjsip_dlg_answer(global.cur_dlg, code); - if (tdata) { - if (code/100 == 2) { - tdata->msg->body = dlg_data->body; - } - pjsip_dlg_send_msg(global.cur_dlg, tdata); - - } - } else { - puts("No current dialog"); - } - break; - case 'h': - if (global.cur_dlg) { - pjsip_tx_data *tdata; - tdata = pjsip_dlg_disconnect(global.cur_dlg, PJSIP_SC_DECLINE); - if (tdata) { - pjsip_dlg_send_msg(global.cur_dlg, tdata); - } - } else { - puts("No current dialog"); - } - break; - case ']': - if (global.cur_dlg) { - global.cur_dlg = global.cur_dlg->next; - if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { - global.cur_dlg = global.cur_dlg->next; - } - } else { - puts("No current dialog"); - } - break; - case '[': - if (global.cur_dlg) { - global.cur_dlg = global.cur_dlg->prev; - if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { - global.cur_dlg = global.cur_dlg->prev; - } - } else { - puts("No current dialog"); - } - break; - case 'd': - pjsip_endpt_dump(global.endpt, *(line+1)=='1'); - pjsip_ua_dump(global.user_agent); - break; - case 's': - if (*(line+1) == 'u') - subscribe_buddies_presence(); - break; - case 'u': - if (*(line+1) == 's') - unsubscribe_presence(); - break; - case 't': - global.hide_status = !global.hide_status; - update_im_status(); - break; - case 'q': - goto on_exit; - case 'l': - print_all_dialogs(); - break; - } - } - -on_exit: - /* Unregister, if required. */ - if (global.regc) { - update_registration(global.regc, 0); - } - - /* Unsubscribe presence. */ - unsubscribe_presence(); - - /* Allow one second to get all events. */ - if (1) { - pj_time_val end_time; - - pj_gettimeofday(&end_time); - end_time.sec++; - - PJ_LOG(3,(THIS_FILE, "Shutting down..")); - for (;;) { - pj_time_val timeout = { 0, 20 }, now; - pjsip_endpt_handle_events (global.endpt, &timeout); - pj_gettimeofday(&now); - PJ_TIME_VAL_SUB(now, end_time); - if (now.sec >= 1) - break; - } - } - - global.worker_quit_flag = 1; - - pj_med_mgr_destroy(global.mmgr); - - /* Wait all threads to quit. */ - for (i=0; imod_data; - modules[(*count)++] = pjsip_messaging_get_module(); - modules[(*count)++] = pjsip_event_sub_get_module(); - - return PJ_SUCCESS; -} +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#define START_PORT 5060 +#define MAX_BUDDIES 32 +#define THIS_FILE "main.c" +#define MAX_PRESENTITY 32 +#define PRESENCE_TIMEOUT 60 + +/* By default we'll have one worker thread, except when threading + * is disabled. + */ +#if PJ_HAS_THREADS +# define WORKER_COUNT 1 +#else +# define WORKER_COUNT 0 +#endif + +/* Global variable. */ +static struct +{ + /* Control. */ + pj_pool_factory *pf; + pjsip_endpoint *endpt; + pj_pool_t *pool; + pjsip_user_agent *user_agent; + int worker_cnt; + int worker_quit_flag; + + /* User info. */ + char user_id[64]; + pj_str_t local_uri; + pj_str_t contact; + pj_str_t real_contact; + + /* Dialog. */ + pjsip_dlg *cur_dlg; + + /* Authentication. */ + int cred_count; + pjsip_cred_info cred_info[4]; + + /* Media stack. */ + pj_bool_t null_audio; + pj_med_mgr_t *mmgr; + + /* Misc. */ + int app_log_level; + char *log_filename; + FILE *log_file; + + /* Proxy URLs */ + pj_str_t proxy; + pj_str_t outbound_proxy; + + /* UA auto options. */ + int auto_answer; /* -1 to disable. */ + int auto_hangup; /* -1 to disable */ + + /* Registration. */ + pj_str_t registrar_uri; + pjsip_regc *regc; + pj_int32_t reg_timeout; + pj_timer_entry regc_timer; + + /* STUN */ + pj_str_t stun_srv1; + int stun_port1; + pj_str_t stun_srv2; + int stun_port2; + + /* UDP sockets and their public address. */ + int sip_port; + pj_sock_t sip_sock; + pj_sockaddr_in sip_sock_name; + pj_sock_t rtp_sock; + pj_sockaddr_in rtp_sock_name; + pj_sock_t rtcp_sock; + pj_sockaddr_in rtcp_sock_name; + + /* SIMPLE */ + pj_bool_t hide_status; + pj_bool_t offer_x_ms_msg; + int im_counter; + int buddy_cnt; + pj_str_t buddy[MAX_BUDDIES]; + pj_bool_t buddy_status[MAX_BUDDIES]; + pj_bool_t no_presence; + pjsip_presentity *buddy_pres[MAX_BUDDIES]; + + int pres_cnt; + pjsip_presentity *pres[MAX_PRESENTITY]; + +} global; + +enum { AUTO_ANSWER, AUTO_HANGUP }; + +/* This is the data that will be 'attached' on per dialog basis. */ +struct dialog_data +{ + /* Media session. */ + pj_media_session_t *msession; + + /* x-ms-chat session. */ + pj_bool_t x_ms_msg_session; + + /* Cached SDP body, updated when media session changed. */ + pjsip_msg_body *body; + + /* Timer. */ + pj_bool_t has_auto_timer; + pj_timer_entry auto_timer; +}; + +/* + * These are the callbacks to be registered to dialog to receive notifications + * about various events in the dialog. + */ +static void dlg_on_all_events (pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt, + pjsip_event *event ); +static void dlg_on_before_tx (pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_tx_data *tdata, int retransmission); +static void dlg_on_tx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_tx_data *tdata); +static void dlg_on_rx_msg (pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_rx_data *rdata); +static void dlg_on_incoming (pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_rx_data *rdata); +static void dlg_on_calling (pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_tx_data *tdata); +static void dlg_on_provisional (pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_event *event); +static void dlg_on_connecting (pjsip_dlg *dlg, pjsip_event *event); +static void dlg_on_established (pjsip_dlg *dlg, pjsip_event *event); +static void dlg_on_disconnected (pjsip_dlg *dlg, pjsip_event *event); +static void dlg_on_terminated (pjsip_dlg *dlg); +static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event); + +/* The callback structure that will be registered to UA layer. */ +struct pjsip_dlg_callback dlg_callback = { + &dlg_on_all_events, + &dlg_on_before_tx, + &dlg_on_tx_msg, + &dlg_on_rx_msg, + &dlg_on_incoming, + &dlg_on_calling, + &dlg_on_provisional, + &dlg_on_connecting, + &dlg_on_established, + &dlg_on_disconnected, + &dlg_on_terminated, + &dlg_on_mid_call_evt +}; + + +/* + * Auxiliary things are put in misc.c, so that this main.c file is more + * readable. + */ +#include "misc.c" + +static void dlg_auto_timer_callback( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + pjsip_dlg *dlg = entry->user_data; + struct dialog_data *dlg_data = dlg->user_data; + + PJ_UNUSED_ARG(timer_heap) + + dlg_data->has_auto_timer = 0; + + if (entry->id == AUTO_ANSWER) { + pjsip_tx_data *tdata = pjsip_dlg_answer(dlg, 200); + if (tdata) { + struct dialog_data *dlg_data = global.cur_dlg->user_data; + tdata->msg->body = dlg_data->body; + pjsip_dlg_send_msg(dlg, tdata); + } + } else { + pjsip_tx_data *tdata = pjsip_dlg_disconnect(dlg, 500); + if (tdata) + pjsip_dlg_send_msg(dlg, tdata); + } +} + +static void update_registration(pjsip_regc *regc, int renew) +{ + pjsip_tx_data *tdata; + + PJ_LOG(3,(THIS_FILE, "Performing SIP registration...")); + + if (renew) { + tdata = pjsip_regc_register(regc, 1); + } else { + tdata = pjsip_regc_unregister(regc); + } + + pjsip_regc_send( regc, tdata ); +} + +static void regc_cb(struct pjsip_regc_cbparam *param) +{ + /* + * Print registration status. + */ + 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)); + global.regc = NULL; + + } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) { + PJ_LOG(3, (THIS_FILE, "SIP registration success, status=%d (%s), " + "will re-register in %d seconds", + param->code, + pjsip_get_status_text(param->code)->ptr, + param->expiration)); + + } else { + PJ_LOG(4, (THIS_FILE, "SIP registration updated status=%d", param->code)); + } +} + +static void pres_on_received_request(pjsip_presentity *pres, pjsip_rx_data *rdata, + int *timeout) +{ + int state; + int i; + char url[PJSIP_MAX_URL_SIZE]; + int urllen; + + PJ_UNUSED_ARG(rdata) + + if (*timeout > 0) { + state = PJSIP_EVENT_SUB_STATE_ACTIVE; + if (*timeout > 300) + *timeout = 300; + } else { + state = PJSIP_EVENT_SUB_STATE_TERMINATED; + } + + urllen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, rdata->from->uri, url, sizeof(url)-1); + if (urllen < 1) { + pj_native_strcpy(url, ""); + } else { + url[urllen] = '\0'; + } + PJ_LOG(3,(THIS_FILE, "Received presence request from %s, sub_state=%s", + url, + (state==PJSIP_EVENT_SUB_STATE_ACTIVE?"active":"terminated"))); + + for (i=0; isub->default_interval); +} + +/* This is called by presence framework when we receives presence update + * of a resource (buddy). + */ +static void pres_on_received_update(pjsip_presentity *pres, pj_bool_t is_open) +{ + int buddy_index = (int)pres->user_data; + + global.buddy_status[buddy_index] = is_open; + PJ_LOG(3,(THIS_FILE, "Presence update: %s is %s", + global.buddy[buddy_index].ptr, + (is_open ? "Online" : "Offline"))); +} + +/* This is called when the subscription is terminated. */ +static void pres_on_terminated(pjsip_presentity *pres, const pj_str_t *reason) +{ + if (pres->sub->role == PJSIP_ROLE_UAC) { + int buddy_index = (int)pres->user_data; + PJ_LOG(3,(THIS_FILE, "Presence subscription for %s is terminated (reason=%.*s)", + global.buddy[buddy_index].ptr, + reason->slen, reason->ptr)); + global.buddy_pres[buddy_index] = NULL; + global.buddy_status[buddy_index] = 0; + } else { + int i; + PJ_LOG(3,(THIS_FILE, "Notifier terminated (reason=%.*s)", + reason->slen, reason->ptr)); + pjsip_presence_notify(pres, PJSIP_EVENT_SUB_STATE_TERMINATED, 1); + for (i=0; idata, buf, size); +} + +/* When media session has changed, call this function to update the cached body + * information in the dialog. + */ +static pjsip_msg_body *create_msg_body (pjsip_dlg *dlg, pj_bool_t is_ack_msg) +{ + struct dialog_data *dlg_data = dlg->user_data; + pjsdp_session_desc *sdp; + + sdp = pj_media_session_create_sdp (dlg_data->msession, dlg->pool, is_ack_msg); + if (!sdp) { + dlg_data->body = NULL; + return NULL; + } + + /* For outgoing INVITE, if we offer "x-ms-message" line, then add a new + * "m=" line in the SDP. + */ + if (dlg_data->x_ms_msg_session >= 0 && + dlg_data->x_ms_msg_session >= (int)sdp->media_count) + { + pjsdp_media_desc *m = pj_pool_calloc(dlg->pool, 1, sizeof(*m)); + sdp->media[sdp->media_count] = m; + dlg_data->x_ms_msg_session = sdp->media_count++; + } + + /* + * For "x-ms-message" line, remove all attributes and connection line etc. + */ + if (dlg_data->x_ms_msg_session >= 0) { + pjsdp_media_desc *m = sdp->media[dlg_data->x_ms_msg_session]; + if (m) { + m->desc.media = pj_str("x-ms-message"); + m->desc.port = 5060; + m->desc.transport = pj_str("sip"); + m->desc.fmt_count = 1; + m->desc.fmt[0] = pj_str("null"); + m->attr_count = 0; + m->conn = NULL; + } + } + + dlg_data->body = pj_pool_calloc(dlg->pool, 1, sizeof(*dlg_data->body)); + dlg_data->body->content_type.type = pj_str("application"); + dlg_data->body->content_type.subtype = pj_str("sdp"); + dlg_data->body->len = 0; /* ignored */ + dlg_data->body->print_body = &print_msg_body; + + dlg_data->body->data = sdp; + return dlg_data->body; +} + +/* This callback will be called on every occurence of events in dialogs */ +static void dlg_on_all_events(pjsip_dlg *dlg, pjsip_dlg_event_e dlg_evt, + pjsip_event *event ) +{ + PJ_UNUSED_ARG(dlg_evt) + PJ_UNUSED_ARG(event) + + PJ_LOG(4, (THIS_FILE, "dlg_on_all_events %p", dlg)); +} + +/* This callback is called before each outgoing msg is sent (including + * retransmission). Application can override this notification if it wants + * to modify the message before transmission or if it wants to do something + * else for each transmission. + */ +static void dlg_on_before_tx(pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_tx_data *tdata, int ret_cnt) +{ + PJ_UNUSED_ARG(tsx) + PJ_UNUSED_ARG(tdata) + + if (ret_cnt > 0) { + PJ_LOG(3, (THIS_FILE, "Dialog %s: retransmitting message (cnt=%d)", + dlg->obj_name, ret_cnt)); + } +} + +/* This callback is called after a message is sent. */ +static void dlg_on_tx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_tx_data *tdata) +{ + PJ_UNUSED_ARG(tsx) + PJ_UNUSED_ARG(tdata) + + PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg)); +} + +/* This callback is called on receipt of incoming message. */ +static void dlg_on_rx_msg(pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_rx_data *rdata) +{ + PJ_UNUSED_ARG(tsx) + PJ_UNUSED_ARG(rdata) + PJ_LOG(4, (THIS_FILE, "dlg_on_tx_msg %p", dlg)); +} + +/* This callback is called after dialog has sent INVITE */ +static void dlg_on_calling(pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_tx_data *tdata) +{ + PJ_UNUSED_ARG(tsx) + PJ_UNUSED_ARG(tdata) + + pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG && + tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD && + tsx->method.id == PJSIP_INVITE_METHOD); + + PJ_LOG(3, (THIS_FILE, "Dialog %s: start calling...", dlg->obj_name)); +} + +static void create_session_from_sdp( pjsip_dlg *dlg, pjsdp_session_desc *sdp) +{ + struct dialog_data *dlg_data = dlg->user_data; + pj_bool_t sdp_x_ms_msg_index = -1; + int i; + int mcnt; + const pj_media_stream_info *mi[PJSDP_MAX_MEDIA]; + int has_active; + pj_media_sock_info sock_info; + + /* Find "m=x-ms-message" line in the SDP. */ + for (i=0; i<(int)sdp->media_count; ++i) { + if (pj_stricmp2(&sdp->media[i]->desc.media, "x-ms-message")==0) + sdp_x_ms_msg_index = i; + } + + /* + * Create media session. + */ + pj_memset(&sock_info, 0, sizeof(sock_info)); + sock_info.rtp_sock = global.rtp_sock; + sock_info.rtcp_sock = global.rtcp_sock; + pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in)); + + dlg_data->msession = pj_media_session_create_from_sdp (global.mmgr, sdp, &sock_info); + + /* A session will always be created, unless there is memory + * alloc problem. + */ + pj_assert(dlg_data->msession); + + /* See if we can take the offer by checking that we have at least + * one media stream active. + */ + mcnt = pj_media_session_enum_streams(dlg_data->msession, PJSDP_MAX_MEDIA, mi); + for (i=0, has_active=0; ifmt_cnt>0 && mi[i]->dir!=PJ_MEDIA_DIR_NONE) { + has_active = 1; + break; + } + } + + if (!has_active && sdp_x_ms_msg_index==-1) { + pjsip_tx_data *tdata; + + /* Unable to accept remote's SDP. + * Answer with 488 (Not Acceptable Here) + */ + /* Create 488 response. */ + tdata = pjsip_dlg_answer(dlg, PJSIP_SC_NOT_ACCEPTABLE_HERE); + + /* Send response. */ + if (tdata) + pjsip_dlg_send_msg(dlg, tdata); + return; + } + + dlg_data->x_ms_msg_session = sdp_x_ms_msg_index; + + /* Create msg body to be used later in 2xx/response */ + create_msg_body(dlg, 0); + +} + +/* This callback is called after an INVITE is received. */ +static void dlg_on_incoming(pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_rx_data *rdata) +{ + struct dialog_data *dlg_data; + pjsip_msg *msg; + pjsip_tx_data *tdata; + char buf[128]; + int len; + + PJ_UNUSED_ARG(tsx) + + pj_assert(rdata->msg->type == PJSIP_REQUEST_MSG && + rdata->msg->line.req.method.id == PJSIP_INVITE_METHOD && + tsx->method.id == PJSIP_INVITE_METHOD); + + /* + * Notify user! + */ + PJ_LOG(3, (THIS_FILE, "")); + PJ_LOG(3, (THIS_FILE, "INCOMING CALL ON DIALOG %s!!", dlg->obj_name)); + PJ_LOG(3, (THIS_FILE, "")); + len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, + (pjsip_name_addr*)dlg->remote.info->uri, + buf, sizeof(buf)-1); + if (len > 0) { + buf[len] = '\0'; + PJ_LOG(3,(THIS_FILE, "From:\t%s", buf)); + } + len = pjsip_uri_print( PJSIP_URI_IN_FROMTO_HDR, + (pjsip_name_addr*)dlg->local.info->uri, + buf, sizeof(buf)-1); + if (len > 0) { + buf[len] = '\0'; + PJ_LOG(3,(THIS_FILE, "To:\t%s", buf)); + } + PJ_LOG(3, (THIS_FILE, "Press 'a' to answer, or 'h' to hangup!!", dlg->obj_name)); + PJ_LOG(3, (THIS_FILE, "")); + + /* + * Process incoming dialog. + */ + + dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data)); + dlg->user_data = dlg_data; + + /* Update contact. */ + pjsip_dlg_set_contact(dlg, &global.contact); + + /* Initialize credentials. */ + pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info); + + /* Create media session if the request has "application/sdp" body. */ + msg = rdata->msg; + if (msg->body && + pj_stricmp2(&msg->body->content_type.type, "application")==0 && + pj_stricmp2(&msg->body->content_type.subtype, "sdp")==0) + { + pjsdp_session_desc *sdp; + + /* Parse SDP body, and instantiate media session based on remote's SDP. + * Then create our SDP body from the session. + */ + sdp = pjsdp_parse (msg->body->data, msg->body->len, rdata->pool); + if (!sdp) + goto send_answer; + + create_session_from_sdp(dlg, sdp); + + } else if (msg->body) { + /* The request has a message body other than "application/sdp" */ + pjsip_accept_hdr *accept; + + /* Create response. */ + tdata = pjsip_dlg_answer(dlg, PJSIP_SC_UNSUPPORTED_MEDIA_TYPE); + + /* Add "Accept" header. */ + accept = pjsip_accept_hdr_create(tdata->pool); + accept->values[0] = pj_str("application/sdp"); + accept->count = 1; + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)accept); + + /* Send response. */ + pjsip_dlg_send_msg(dlg, tdata); + return; + + } else { + /* The request has no message body. We can take this request, but + * no media session will be activated. + */ + /* Nothing to do here. */ + } + +send_answer: + /* Immediately answer with 100 (or 180? */ + tdata = pjsip_dlg_answer( dlg, PJSIP_SC_RINGING ); + pjsip_dlg_send_msg(dlg, tdata); + + /* Set current dialog to this dialog if we don't currently have + * current dialog. + */ + if (global.cur_dlg == NULL) { + global.cur_dlg = dlg; + } + + /* Auto-answer if option is specified. */ + if (global.auto_answer >= 0) { + pj_time_val delay = { 0, 0}; + struct dialog_data *dlg_data = dlg->user_data; + + PJ_LOG(4, (THIS_FILE, "Scheduling auto-answer in %d seconds", + global.auto_answer)); + + delay.sec = global.auto_answer; + dlg_data->auto_timer.user_data = dlg; + dlg_data->auto_timer.id = AUTO_ANSWER; + dlg_data->auto_timer.cb = &dlg_auto_timer_callback; + dlg_data->has_auto_timer = 1; + pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay); + } +} + +/* This callback is called when dialog has sent/received a provisional response + * to INVITE. + */ +static void dlg_on_provisional(pjsip_dlg *dlg, pjsip_transaction *tsx, + pjsip_event *event) +{ + const char *action; + + pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && + event->src.tdata->msg->type == PJSIP_RESPONSE_MSG && + event->src.tdata->msg->line.status.code/100 == 1 && + tsx->method.id == PJSIP_INVITE_METHOD) + || + (event->src_type == PJSIP_EVENT_RX_MSG && + event->src.rdata->msg->type == PJSIP_RESPONSE_MSG && + event->src.rdata->msg->line.status.code/100 == 1 && + tsx->method.id == PJSIP_INVITE_METHOD)); + + if (event->src_type == PJSIP_EVENT_TX_MSG) + action = "Sending"; + else + action = "Received"; + + PJ_LOG(3, (THIS_FILE, "Dialog %s: %s %d (%s)", + dlg->obj_name, action, tsx->status_code, + pjsip_get_status_text(tsx->status_code)->ptr)); +} + +/* This callback is called when 200 response to INVITE is sent/received. */ +static void dlg_on_connecting(pjsip_dlg *dlg, pjsip_event *event) +{ + struct dialog_data *dlg_data = dlg->user_data; + const char *action; + + pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && + event->src.tdata->msg->type == PJSIP_RESPONSE_MSG && + event->src.tdata->msg->line.status.code/100 == 2) + || + (event->src_type == PJSIP_EVENT_RX_MSG && + event->src.rdata->msg->type == PJSIP_RESPONSE_MSG && + event->src.rdata->msg->line.status.code/100 == 2)); + + if (event->src_type == PJSIP_EVENT_RX_MSG) + action = "Received"; + else + action = "Sending"; + + PJ_LOG(3, (THIS_FILE, "Dialog %s: %s 200 (OK)", dlg->obj_name, action)); + + if (event->src_type == PJSIP_EVENT_RX_MSG) { + /* On receipt of 2xx response, negotiate our media capability + * and start media. + */ + pjsip_msg *msg = event->src.rdata->msg; + pjsip_msg_body *body; + pjsdp_session_desc *sdp; + + /* Get SDP from message. */ + + /* Ignore if no SDP body is present. */ + body = msg->body; + if (!body) { + PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no body!", + dlg->obj_name)); + return; + } + + if (pj_stricmp2(&body->content_type.type, "application") != 0 && + pj_stricmp2(&body->content_type.subtype, "sdp") != 0) + { + PJ_LOG(3, (THIS_FILE, "Dialog %s: the 200/OK response has no SDP body!", + dlg->obj_name)); + return; + } + + /* Got what seems to be a SDP content. Parse it. */ + sdp = pjsdp_parse (body->data, body->len, event->src.rdata->pool); + if (!sdp) { + PJ_LOG(3, (THIS_FILE, "Dialog %s: SDP syntax error!", + dlg->obj_name)); + return; + } + + /* Negotiate media session with remote's media capability. */ + if (pj_media_session_update (dlg_data->msession, sdp) != 0) { + PJ_LOG(3, (THIS_FILE, "Dialog %s: media session update error!", + dlg->obj_name)); + return; + } + + /* Update the saved SDP body because media session has changed. + * Also set ack flag to '1', because we only want to send one format/ + * codec for each media streams. + */ + create_msg_body(dlg, 1); + + /* Activate media. */ + pj_media_session_activate (dlg_data->msession); + + } else { + pjsip_msg *msg = event->src.tdata->msg; + + if (msg->body) { + /* On transmission of 2xx response, start media session. */ + pj_media_session_activate (dlg_data->msession); + } + } + +} + +/* This callback is called when ACK to initial INVITE is sent/received. */ +static void dlg_on_established(pjsip_dlg *dlg, pjsip_event *event) +{ + const char *action; + + pj_assert((event->src_type == PJSIP_EVENT_TX_MSG && + event->src.tdata->msg->type == PJSIP_REQUEST_MSG && + event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) + || + (event->src_type == PJSIP_EVENT_RX_MSG && + event->src.rdata->msg->type == PJSIP_REQUEST_MSG && + event->src.rdata->msg->line.req.method.id == PJSIP_ACK_METHOD)); + + if (event->src_type == PJSIP_EVENT_RX_MSG) + action = "Received"; + else + action = "Sending"; + + PJ_LOG(3, (THIS_FILE, "Dialog %s: %s ACK, dialog is ESTABLISHED", + dlg->obj_name, action)); + + /* Attach SDP body for outgoing ACK. */ + if (event->src_type == PJSIP_EVENT_TX_MSG && + event->src.tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) + { + struct dialog_data *dlg_data = dlg->user_data; + event->src.tdata->msg->body = dlg_data->body; + } + + /* Auto-hangup if option is specified. */ + if (global.auto_hangup >= 0) { + pj_time_val delay = { 0, 0}; + struct dialog_data *dlg_data = dlg->user_data; + + PJ_LOG(4, (THIS_FILE, "Scheduling auto-hangup in %d seconds", + global.auto_hangup)); + + delay.sec = global.auto_hangup; + dlg_data->auto_timer.user_data = dlg; + dlg_data->auto_timer.id = AUTO_HANGUP; + dlg_data->auto_timer.cb = &dlg_auto_timer_callback; + dlg_data->has_auto_timer = 1; + pjsip_endpt_schedule_timer(dlg->ua->endpt, &dlg_data->auto_timer, &delay); + } +} + + +/* This callback is called when dialog is disconnected (because of final + * response, BYE, or timer). + */ +static void dlg_on_disconnected(pjsip_dlg *dlg, pjsip_event *event) +{ + struct dialog_data *dlg_data = dlg->user_data; + int status_code; + const pj_str_t *reason; + + PJ_UNUSED_ARG(event) + + /* Cancel auto-answer/auto-hangup timer. */ + if (dlg_data->has_auto_timer) { + pjsip_endpt_cancel_timer(dlg->ua->endpt, &dlg_data->auto_timer); + dlg_data->has_auto_timer = 0; + } + + if (dlg->invite_tsx) + status_code = dlg->invite_tsx->status_code; + else + status_code = 200; + + if (event->obj.tsx->method.id == PJSIP_INVITE_METHOD) { + if (event->src_type == PJSIP_EVENT_RX_MSG) + reason = &event->src.rdata->msg->line.status.reason; + else if (event->src_type == PJSIP_EVENT_TX_MSG) + reason = &event->src.tdata->msg->line.status.reason; + else + reason = pjsip_get_status_text(event->obj.tsx->status_code); + } else { + reason = &event->obj.tsx->method.name; + } + + PJ_LOG(3, (THIS_FILE, "Dialog %s: DISCONNECTED! Reason=%d (%.*s)", + dlg->obj_name, status_code, + reason->slen, reason->ptr)); + + if (dlg_data->msession) { + pj_media_session_destroy (dlg_data->msession); + dlg_data->msession = NULL; + } +} + +/* This callback is called when dialog is about to be destroyed. */ +static void dlg_on_terminated(pjsip_dlg *dlg) +{ + PJ_LOG(3, (THIS_FILE, "Dialog %s: terminated!", dlg->obj_name)); + + /* If current dialog is equal to this dialog, update it. */ + if (global.cur_dlg == dlg) { + global.cur_dlg = global.cur_dlg->next; + if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { + global.cur_dlg = NULL; + } + } +} + +/* This callback is called for any requests when dialog is established. */ +static void dlg_on_mid_call_evt (pjsip_dlg *dlg, pjsip_event *event) +{ + pjsip_transaction *tsx = event->obj.tsx; + + if (event->src_type == PJSIP_EVENT_RX_MSG && + event->src.rdata->msg->type == PJSIP_REQUEST_MSG) + { + if (event->src.rdata->cseq->method.id == PJSIP_INVITE_METHOD) { + /* Re-invitation. */ + pjsip_tx_data *tdata; + + PJ_LOG(3,(THIS_FILE, "Dialog %s: accepting re-invitation (dummy)", + dlg->obj_name)); + tdata = pjsip_dlg_answer(dlg, 200); + if (tdata) { + struct dialog_data *dlg_data = dlg->user_data; + tdata->msg->body = dlg_data->body; + pjsip_dlg_send_msg(dlg, tdata); + } + } else { + /* Don't worry, endpoint will answer with 500 or whetever. */ + } + + } else if (tsx->status_code/100 == 2) { + PJ_LOG(3,(THIS_FILE, "Dialog %s: outgoing %.*s success: %d (%s)", + dlg->obj_name, + tsx->method.name.slen, tsx->method.name.ptr, + tsx->status_code, + pjsip_get_status_text(tsx->status_code)->ptr)); + + + } else if (tsx->status_code >= 300) { + pj_bool_t report_failure = PJ_TRUE; + + /* Check for authentication failures. */ + if (tsx->status_code==401 || tsx->status_code==407) { + pjsip_tx_data *tdata; + tdata = pjsip_auth_reinit_req( global.endpt, + dlg->pool, &dlg->auth_sess, + dlg->cred_count, dlg->cred_info, + tsx->last_tx, event->src.rdata ); + if (tdata) { + int rc; + rc = pjsip_dlg_send_msg( dlg, tdata); + report_failure = (rc != 0); + } + } + if (report_failure) { + const pj_str_t *reason; + if (event->src_type == PJSIP_EVENT_RX_MSG) { + reason = &event->src.rdata->msg->line.status.reason; + } else { + reason = pjsip_get_status_text(tsx->status_code); + } + PJ_LOG(2,(THIS_FILE, "Dialog %s: outgoing request failed: %d (%.*s)", + dlg->obj_name, tsx->status_code, + reason->slen, reason->ptr)); + } + } +} + +/* Initialize sockets and optionally get the public address via STUN. */ +static pj_status_t init_sockets() +{ + enum { + RTP_START_PORT = 4000, + RTP_RANDOM_START = 2, + RTP_RETRY = 10 + }; + enum { + SIP_SOCK, + RTP_SOCK, + RTCP_SOCK, + }; + int i; + int rtp_port; + pj_sock_t sock[3]; + pj_sockaddr_in mapped_addr[3]; + + for (i=0; i<3; ++i) + sock[i] = PJ_INVALID_SOCKET; + + /* Create and bind SIP UDP socket. */ + sock[SIP_SOCK] = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0); + if (sock[SIP_SOCK] == PJ_INVALID_SOCKET) { + PJ_LOG(2,(THIS_FILE, "Unable to create socket")); + goto on_error; + } + if (pj_sock_bind_in(sock[SIP_SOCK], 0, (pj_uint16_t)global.sip_port) != 0) { + PJ_LOG(2,(THIS_FILE, "Unable to bind to SIP port")); + goto on_error; + } + + /* Initialize start of RTP port to try. */ + rtp_port = RTP_START_PORT + (pj_rand() % RTP_RANDOM_START) / 2; + + /* Loop retry to bind RTP and RTCP sockets. */ + for (i=0; iuser.slen) + strncpy(global.user_id, url->user.ptr, url->user.slen); + } + } + pj_pool_release(pool); + } + + if (global.user_id[0]=='\0') { + pj_native_strcpy(global.user_id, "user"); + } + + /* build contact */ + global.real_contact.ptr = local_uri; + global.real_contact.slen = + sprintf(local_uri, "", global.user_id, local_addr, global.sip_port); + + if (global.contact.slen == 0) + global.contact = global.real_contact; + + /* initialize local_uri with contact if it's not specified in cmdline */ + if (global.local_uri.slen == 0) + global.local_uri = global.contact; + + /* Init proxy. */ + if (global.proxy.slen || global.outbound_proxy.slen) { + int count = 0; + pj_str_t proxy_url[2]; + + if (global.outbound_proxy.slen) { + proxy_url[count++] = global.outbound_proxy; + } + if (global.proxy.slen) { + proxy_url[count++] = global.proxy; + } + + if (pjsip_endpt_set_proxies(global.endpt, count, proxy_url) != 0) { + PJ_LOG(2,(THIS_FILE, "Error setting proxy address!")); + return -1; + } + } + + /* initialize SIP registration if registrar is configured */ + if (global.registrar_uri.slen) { + global.regc = pjsip_regc_create( global.endpt, NULL, ®c_cb); + pjsip_regc_init( global.regc, &global.registrar_uri, + &global.local_uri, + &global.local_uri, + 1, &global.contact, + global.reg_timeout); + pjsip_regc_set_credentials( global.regc, global.cred_count, global.cred_info ); + } + + return PJ_SUCCESS; +} + +/* Worker thread function, only used when threading is enabled. */ +static void *PJ_THREAD_FUNC worker_thread(void *unused) +{ + PJ_UNUSED_ARG(unused) + + while (!global.worker_quit_flag) { + pj_time_val timeout = { 0, 10 }; + pjsip_endpt_handle_events (global.endpt, &timeout); + } + return NULL; +} + + +/* Make call to the specified URI. */ +static pjsip_dlg *make_call(pj_str_t *remote_uri) +{ + pjsip_dlg *dlg; + pj_str_t local = global.contact; + pj_str_t remote = *remote_uri; + struct dialog_data *dlg_data; + pjsip_tx_data *tdata; + pj_media_sock_info sock_info; + + /* Create new dialog instance. */ + dlg = pjsip_ua_create_dialog(global.user_agent, PJSIP_ROLE_UAC); + + /* Attach our own user data. */ + dlg_data = pj_pool_calloc(dlg->pool, 1, sizeof(struct dialog_data)); + dlg->user_data = dlg_data; + + /* Create media session. */ + pj_memset(&sock_info, 0, sizeof(sock_info)); + sock_info.rtp_sock = global.rtp_sock; + sock_info.rtcp_sock = global.rtcp_sock; + pj_memcpy(&sock_info.rtp_addr_name, &global.rtp_sock_name, sizeof(pj_sockaddr_in)); + + dlg_data->msession = pj_media_session_create (global.mmgr, &sock_info); + dlg_data->x_ms_msg_session = -1; + + if (global.offer_x_ms_msg) { + const pj_media_stream_info *minfo[32]; + unsigned cnt; + + cnt = pj_media_session_enum_streams(dlg_data->msession, 32, minfo); + if (cnt > 0) + dlg_data->x_ms_msg_session = cnt; + } + + /* Initialize dialog with local and remote URI. */ + if (pjsip_dlg_init(dlg, &local, &remote, NULL) != PJ_SUCCESS) { + pjsip_ua_destroy_dialog(dlg); + return NULL; + } + + /* Initialize credentials. */ + pjsip_dlg_set_credentials(dlg, global.cred_count, global.cred_info); + + /* Send INVITE! */ + tdata = pjsip_dlg_invite(dlg); + tdata->msg->body = create_msg_body (dlg, 0); + + if (pjsip_dlg_send_msg(dlg, tdata) != PJ_SUCCESS) { + pjsip_ua_destroy_dialog(dlg); + return NULL; + } + + return dlg; +} + +/* + * Callback to receive incoming IM message. + */ +static int on_incoming_im_msg(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg; + pjsip_msg_body *body = msg->body; + int len; + char to[128], from[128]; + + + len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, + rdata->from->uri, from, sizeof(from)); + if (len > 0) from[len] = '\0'; + else pj_native_strcpy(from, ""); + + len = pjsip_uri_print( PJSIP_URI_IN_CONTACT_HDR, + rdata->to->uri, to, sizeof(to)); + if (len > 0) to[len] = '\0'; + else pj_native_strcpy(to, ""); + + PJ_LOG(3,(THIS_FILE, "Incoming instant message:")); + + printf("----- BEGIN INSTANT MESSAGE ----->\n"); + printf("From:\t%s\n", from); + printf("To:\t%s\n", to); + printf("Body:\n%.*s\n", (body ? body->len : 0), (body ? (char*)body->data : "")); + printf("<------ END INSTANT MESSAGE ------\n"); + + fflush(stdout); + + /* Must answer with final response. */ + return 200; +} + +/* + * Input URL. + */ +static pj_str_t *ui_input_url(pj_str_t *out, char *buf, int len, int *selection) +{ + int i; + + *selection = -1; + + printf("\nBuddy list:\n"); + printf("---------------------------------------\n"); + for (i=0; i\n", i+1, global.buddy[i].ptr, + (global.buddy_status[i]?"Online":"Offline")); + } + printf("-------------------------------------\n"); + + printf("Choices\n" + "\t0 For current dialog.\n" + "\t[1-%02d] Select from buddy list\n" + "\tURL An URL\n" + , global.buddy_cnt); + printf("Input: "); + + fflush(stdout); + fgets(buf, len, stdin); + buf[strlen(buf)-1] = '\0'; /* remove trailing newline. */ + + while (isspace(*buf)) ++buf; + + if (!*buf || *buf=='\n' || *buf=='\r') + return NULL; + + i = atoi(buf); + + if (i == 0) { + if (isdigit(*buf)) { + *selection = 0; + *out = pj_str("0"); + return out; + } else { + if (verify_sip_url(buf) != 0) { + puts("Invalid URL specified!"); + return NULL; + } + *out = pj_str(buf); + return out; + } + } else if (i > global.buddy_cnt || i < 0) { + printf("Error: invalid selection!\n"); + return NULL; + } else { + *out = global.buddy[i-1]; + *selection = i; + return out; + } +} + + +static void generic_request_callback( void *token, pjsip_event *event ) +{ + pjsip_transaction *tsx = event->obj.tsx; + + PJ_UNUSED_ARG(token) + + if (tsx->status_code/100 == 2) { + PJ_LOG(3,(THIS_FILE, "Outgoing %.*s %d (%s)", + event->obj.tsx->method.name.slen, + event->obj.tsx->method.name.ptr, + tsx->status_code, + pjsip_get_status_text(tsx->status_code)->ptr)); + } else if (tsx->status_code==401 || tsx->status_code==407) { + pjsip_tx_data *tdata; + tdata = pjsip_auth_reinit_req( global.endpt, + global.pool, NULL, global.cred_count, global.cred_info, + tsx->last_tx, event->src.rdata); + if (tdata) { + int rc; + pjsip_cseq_hdr *cseq; + cseq = (pjsip_cseq_hdr*)pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); + cseq->cseq++; + rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL, + &generic_request_callback); + if (rc == 0) + return; + } + PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%s)", + event->obj.tsx->method.name.slen, + event->obj.tsx->method.name.ptr, + event->obj.tsx->status_code, + pjsip_get_status_text(event->obj.tsx->status_code)->ptr)); + } else { + const pj_str_t *reason; + if (event->src_type == PJSIP_EVENT_RX_MSG) + reason = &event->src.rdata->msg->line.status.reason; + else + reason = pjsip_get_status_text(tsx->status_code); + PJ_LOG(2,(THIS_FILE, "Outgoing %.*s failed, status=%d (%.*s)", + event->obj.tsx->method.name.slen, + event->obj.tsx->method.name.ptr, + event->obj.tsx->status_code, + reason->slen, reason->ptr)); + } +} + + +static void ui_send_im_message() +{ + char line[100]; + char text_buf[100]; + pj_str_t str; + pj_str_t text_msg; + int selection, rc; + pjsip_tx_data *tdata; + + if (ui_input_url(&str, line, sizeof(line), &selection) == NULL) + return; + + + printf("Enter text to send (empty to cancel): "); fflush(stdout); + fgets(text_buf, sizeof(text_buf), stdin); + text_buf[strlen(text_buf)-1] = '\0'; + if (!*text_buf) + return; + + text_msg = pj_str(text_buf); + + if (selection==0) { + pjsip_method message_method; + pj_str_t str_MESSAGE = { "MESSAGE", 7 }; + + /* Send IM to current dialog. */ + if (global.cur_dlg == NULL || global.cur_dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) { + printf("No current dialog or dialog state is not ESTABLISHED!\n"); + return; + } + + pjsip_method_init( &message_method, global.cur_dlg->pool, &str_MESSAGE); + tdata = pjsip_dlg_create_request( global.cur_dlg, &message_method, -1 ); + + if (tdata) { + /* Create message body for the text. */ + pjsip_msg_body *body = pj_pool_calloc(tdata->pool, 1, sizeof(*body)); + body->content_type.type = pj_str("text"); + body->content_type.subtype = pj_str("plain"); + body->data = pj_pool_alloc(tdata->pool, text_msg.slen); + pj_memcpy(body->data, text_msg.ptr, text_msg.slen); + body->len = text_msg.slen; + body->print_body = &pjsip_print_text_body; + + /* Assign body to message, and send the message! */ + tdata->msg->body = body; + pjsip_dlg_send_msg( global.cur_dlg, tdata ); + } + + } else { + /* Send IM to buddy list. */ + pjsip_method message; + static pj_str_t MESSAGE = { "MESSAGE", 7 }; + pjsip_method_init_np(&message, &MESSAGE); + tdata = pjsip_endpt_create_request(global.endpt, &message, + &str, + &global.real_contact, + &str, &global.real_contact, NULL, -1, + &text_msg); + if (!tdata) { + puts("Error creating request"); + return; + } + rc = pjsip_endpt_send_request(global.endpt, tdata, -1, NULL, &generic_request_callback); + if (rc == 0) { + printf("Sending IM message %d\n", global.im_counter); + ++global.im_counter; + } else { + printf("Error: unable to send IM message!\n"); + } + } +} + +static void ui_send_options() +{ + char line[100]; + pj_str_t str; + int selection, rc; + pjsip_tx_data *tdata; + pjsip_method options; + + if (ui_input_url(&str, line, sizeof(line), &selection) == NULL) + return; + + pjsip_method_set( &options, PJSIP_OPTIONS_METHOD ); + + if (selection == 0) { + /* Send OPTIONS to current dialog. */ + tdata = pjsip_dlg_create_request(global.cur_dlg, &options, -1); + if (tdata) + pjsip_dlg_send_msg( global.cur_dlg, tdata ); + } else { + /* Send OPTIONS to arbitrary party. */ + tdata = pjsip_endpt_create_request( global.endpt, &options, + &str, + &global.local_uri, &str, + &global.real_contact, + NULL, -1, NULL); + if (tdata) { + rc = pjsip_endpt_send_request( global.endpt, tdata, -1, NULL, + &generic_request_callback); + if (rc != 0) + PJ_LOG(2,(THIS_FILE, "Error sending OPTIONS!")); + } + } +} + +static void init_presence() +{ + const pjsip_presence_cb pres_cb = { + NULL, + &pres_on_received_request, + &pres_on_received_refresh, + &pres_on_received_update, + &pres_on_terminated + }; + + pjsip_presence_init(&pres_cb); +} + +/* Subscribe presence information for all buddies. */ +static void subscribe_buddies_presence() +{ + int i; + for (i=0; i=0; --i) { + pj_thread_join(thread[i]); + pj_thread_destroy(thread[i]); + } + pj_caching_pool_destroy(&cp); + return 1; + } + } + + printf("Worker thread count: %d\n", global.worker_cnt); + + /* Perform registration, if required. */ + if (global.regc) { + update_registration(global.regc, 1); + } + + /* Initialize media manager. */ + global.mmgr = pj_med_mgr_create(global.pf); + + /* Init presence. */ + init_presence(); + + /* Subscribe presence information of all buddies. */ + if (!global.no_presence) + subscribe_buddies_presence(); + + /* Initializatio completes, loop waiting for commands. */ + for (;!global.worker_quit_flag;) { + pj_str_t str; + char line[128]; + +#if WORKER_COUNT==0 + /* If worker thread does not exist, main thread must poll for evetns. + * But this won't work very well since main thread is blocked by + * fgets(). So keep pressing the ENTER key to get the events! + */ + pj_time_val timeout = { 0, 100 }; + pjsip_endpt_handle_events(global.endpt, &timeout); + puts("Keep pressing ENTER key to get the events!"); +#endif + + printf("\nCurrent dialog: "); + print_dialog(global.cur_dlg); + puts(""); + + keystroke_help(); + + fgets(line, sizeof(line), stdin); + + switch (*line) { + case 'm': + puts("Make outgoing call"); + if (ui_input_url(&str, line, sizeof(line), &i) != NULL) { + pjsip_dlg *dlg = make_call(&str); + if (global.cur_dlg == NULL) { + global.cur_dlg = dlg; + } + } + break; + case 'i': + puts("Send Instant Messaging"); + ui_send_im_message(); + break; + case 'o': + puts("Send OPTIONS"); + ui_send_options(); + break; + case 'a': + if (global.cur_dlg) { + unsigned code; + pjsip_tx_data *tdata; + struct dialog_data *dlg_data = global.cur_dlg->user_data; + + printf("Answer with status code (1xx-6xx): "); + fflush(stdout); + fgets(line, sizeof(line), stdin); + str = pj_str(line); + str.slen -= 1; + + code = pj_strtoul(&str); + tdata = pjsip_dlg_answer(global.cur_dlg, code); + if (tdata) { + if (code/100 == 2) { + tdata->msg->body = dlg_data->body; + } + pjsip_dlg_send_msg(global.cur_dlg, tdata); + + } + } else { + puts("No current dialog"); + } + break; + case 'h': + if (global.cur_dlg) { + pjsip_tx_data *tdata; + tdata = pjsip_dlg_disconnect(global.cur_dlg, PJSIP_SC_DECLINE); + if (tdata) { + pjsip_dlg_send_msg(global.cur_dlg, tdata); + } + } else { + puts("No current dialog"); + } + break; + case ']': + if (global.cur_dlg) { + global.cur_dlg = global.cur_dlg->next; + if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { + global.cur_dlg = global.cur_dlg->next; + } + } else { + puts("No current dialog"); + } + break; + case '[': + if (global.cur_dlg) { + global.cur_dlg = global.cur_dlg->prev; + if (global.cur_dlg == (void*)&global.user_agent->dlg_list) { + global.cur_dlg = global.cur_dlg->prev; + } + } else { + puts("No current dialog"); + } + break; + case 'd': + pjsip_endpt_dump(global.endpt, *(line+1)=='1'); + pjsip_ua_dump(global.user_agent); + break; + case 's': + if (*(line+1) == 'u') + subscribe_buddies_presence(); + break; + case 'u': + if (*(line+1) == 's') + unsubscribe_presence(); + break; + case 't': + global.hide_status = !global.hide_status; + update_im_status(); + break; + case 'q': + goto on_exit; + case 'l': + print_all_dialogs(); + break; + } + } + +on_exit: + /* Unregister, if required. */ + if (global.regc) { + update_registration(global.regc, 0); + } + + /* Unsubscribe presence. */ + unsubscribe_presence(); + + /* Allow one second to get all events. */ + if (1) { + pj_time_val end_time; + + pj_gettimeofday(&end_time); + end_time.sec++; + + PJ_LOG(3,(THIS_FILE, "Shutting down..")); + for (;;) { + pj_time_val timeout = { 0, 20 }, now; + pjsip_endpt_handle_events (global.endpt, &timeout); + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, end_time); + if (now.sec >= 1) + break; + } + } + + global.worker_quit_flag = 1; + + pj_med_mgr_destroy(global.mmgr); + + /* Wait all threads to quit. */ + for (i=0; imod_data; + modules[(*count)++] = pjsip_messaging_get_module(); + modules[(*count)++] = pjsip_event_sub_get_module(); + + return PJ_SUCCESS; +} diff --git a/pjsip/src/pjsua/misc.c b/pjsip/src/pjsua/misc.c index a9b3510b..5c063fc7 100644 --- a/pjsip/src/pjsua/misc.c +++ b/pjsip/src/pjsua/misc.c @@ -1,485 +1,485 @@ -/* $Id$ */ -/* - * Copyright (C) 2003-2006 Benny Prijono - * - * 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 - */ - -/* - * THIS FILE IS INCLUDED BY main.c. - * IT WON'T COMPILE BY ITSELF. - */ - -#include "getopt.h" -#include - - -/* - * Display program usage - */ -static void usage() -{ - puts("Usage:"); - puts(" pjsua [options] [sip-url]"); - puts(""); - puts(" [sip-url] Default URL to invite."); - puts(""); - puts("General 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(" --help Display this help screen"); - puts(" --version Display version info"); - puts(""); - puts("Media options:"); - puts(" --null-audio Use NULL audio device"); - puts(""); - puts("User Agent options:"); - puts(" --auto-answer=sec Auto-answer all incoming calls after sec seconds."); - puts(" --auto-hangup=sec Auto-hangup all calls after sec seconds."); - puts(""); - puts("SIP options:"); - puts(" --local-port=port Set TCP/UDP port"); - 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(" --outbound=url Set the URL of outbound proxy server"); - puts(" --registrar=url Set the URL of registrar server"); - puts(" --reg-timeout=secs Set registration interval to secs (default 3600)"); - puts(""); - puts("Authentication options:"); - puts(" --realm=string Set realm"); - puts(" --username=string Set authentication username"); - puts(" --password=string Set authentication password"); - puts(""); - puts("STUN options (all must be specified):"); - puts(" --use-stun1=host[:port]"); - puts(" --use-stun2=host[:port] Use STUN and set host name and port of STUN servers"); - puts(""); - puts("SIMPLE options (may be specified more than once):"); - puts(" --add-buddy url Add the specified URL to the buddy list."); - puts(" --offer-x-ms-msg Offer \"x-ms-message\" in outgoing INVITE"); - puts(" --no-presence Do not subscribe presence of buddies"); - puts(""); - fflush(stdout); -} - -/* Display keystroke help. */ -static void keystroke_help() -{ - int i; - - printf("Advertise status as: %s\n", (global.hide_status ? "Offline" : "Online")); - puts(""); - puts("Buddy list:"); - puts("-------------------------------------------------------------------------------"); - for (i=0; i\n", i+1, global.buddy[i].ptr, - (global.buddy_status[i]?"Online":"Offline")); - } - //printf("-------------------------------------\n"); - puts(""); - //puts("Commands:"); - puts("+=============================================================================+"); - puts("| Call Commands: | IM & Presence: | Misc: |"); - puts("| | | |"); - puts("| m Make new call | i Send IM | o Send OPTIONS |"); - puts("| a Answer call | su Subscribe presence | d Dump status |"); - puts("| h Hangup call | us Unsubscribe presence | d1 Dump detailed |"); - puts("| ] Select next dialog | t Toggle Online status | |"); - puts("| [ Select previous dialog | | |"); - puts("+-----------------------------------------------------------------------------+"); - puts("| q QUIT |"); - puts("+=============================================================================+"); - puts(""); - - - fflush(stdout); -} - -/* - * Verify that valid SIP url is given. - */ -static pj_status_t verify_sip_url(char *url) -{ - pjsip_uri *p; - pj_pool_t *pool; - int len = (url ? strlen(url) : 0); - - if (!len) return -1; - - pool = pj_pool_create(global.pf, "check%p", 1024, 0, NULL); - if (!pool) return -1; - - 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); - 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"); - 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; - -} - -/* - * Parse program arguments - */ -static int parse_args(pj_pool_t *pool, 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}; - 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}, - { NULL, 0, 0, 0} - }; - 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 0: - config_file = optarg; - break; - } - if (config_file) - break; - } - - if (config_file) { - if (read_config_file(pool, config_file, &argc, &argv) != 0) - return -1; - } - - /* 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 *err, *p; - - switch (c) { - case OPT_LOG_FILE: - global.log_filename = optarg; - break; - case OPT_LOG_LEVEL: - c = strtoul(optarg, &err, 10); - if (*err) { - printf("Error: expecting integer value 0-6 for --log-level\n"); - return -1; - } - pj_log_set_level( c ); - break; - case OPT_APP_LOG_LEVEL: - global.app_log_level = strtoul(optarg, &err, 10); - if (*err) { - printf("Error: expecting integer value 0-6 for --app-log-level\n"); - return -1; - } - break; - case OPT_HELP: - usage(); - return -1; - case OPT_VERSION: /* version */ - pj_dump_config(); - return -1; - case OPT_NULL_AUDIO: - global.null_audio = 1; - break; - case OPT_LOCAL_PORT: /* local-port */ - global.sip_port = strtoul(optarg, &err, 10); - if (*err) { - printf("Error: expecting integer value for --local-port\n"); - return -1; - } - break; - case OPT_PROXY: /* proxy */ - if (verify_sip_url(optarg) != 0) { - printf("Error: invalid SIP URL '%s' in proxy argument\n", optarg); - return -1; - } - global.proxy = pj_str(optarg); - break; - case OPT_OUTBOUND_PROXY: /* outbound proxy */ - if (verify_sip_url(optarg) != 0) { - printf("Error: invalid SIP URL '%s' in outbound proxy argument\n", optarg); - return -1; - } - global.outbound_proxy = pj_str(optarg); - break; - case OPT_REGISTRAR: /* registrar */ - if (verify_sip_url(optarg) != 0) { - printf("Error: invalid SIP URL '%s' in registrar argument\n", optarg); - return -1; - } - global.registrar_uri = pj_str(optarg); - break; - case OPT_REG_TIMEOUT: /* reg-timeout */ - global.reg_timeout = strtoul(optarg, &err, 10); - if (*err) { - printf("Error: expecting integer value for --reg-timeout\n"); - return -1; - } - break; - case OPT_ID: /* id */ - if (verify_sip_url(optarg) != 0) { - printf("Error: invalid SIP URL '%s' in local id argument\n", optarg); - return -1; - } - global.local_uri = pj_str(optarg); - break; - case OPT_CONTACT: /* contact */ - if (verify_sip_url(optarg) != 0) { - printf("Error: invalid SIP URL '%s' in contact argument\n", optarg); - return -1; - } - global.contact = pj_str(optarg); - break; - case OPT_USERNAME: /* Default authentication user */ - if (!global.cred_count) global.cred_count = 1; - global.cred_info[0].username = pj_str(optarg); - break; - case OPT_REALM: /* Default authentication realm. */ - if (!global.cred_count) global.cred_count = 1; - global.cred_info[0].realm = pj_str(optarg); - break; - case OPT_PASSWORD: /* authentication password */ - if (!global.cred_count) global.cred_count = 1; - global.cred_info[0].data_type = 0; - global.cred_info[0].data = pj_str(optarg); - break; - case OPT_USE_STUN1: /* STUN server 1 */ - p = pj_native_strchr(optarg, ':'); - if (p) { - *p = '\0'; - global.stun_srv1 = pj_str(optarg); - global.stun_port1 = strtoul(p+1, &err, 10); - if (*err || global.stun_port1==0) { - printf("Error: expecting port number with option --use-stun1\n"); - return -1; - } - } else { - global.stun_port1 = 3478; - global.stun_srv1 = pj_str(optarg); - } - break; - case OPT_USE_STUN2: /* STUN server 2 */ - p = pj_native_strchr(optarg, ':'); - if (p) { - *p = '\0'; - global.stun_srv2 = pj_str(optarg); - global.stun_port2 = strtoul(p+1, &err, 10); - if (*err || global.stun_port2==0) { - printf("Error: expecting port number with option --use-stun2\n"); - return -1; - } - } else { - global.stun_port2 = 3478; - global.stun_srv2 = pj_str(optarg); - } - break; - case OPT_ADD_BUDDY: /* Add to buddy list. */ - if (verify_sip_url(optarg) != 0) { - printf("Error: invalid URL '%s' in --add-buddy option\n", optarg); - return -1; - } - if (global.buddy_cnt == MAX_BUDDIES) { - printf("Error: too many buddies in buddy list.\n"); - return -1; - } - global.buddy[global.buddy_cnt++] = pj_str(optarg); - break; - case OPT_OFFER_X_MS_MSG: - global.offer_x_ms_msg = 1; - break; - case OPT_NO_PRESENCE: - global.no_presence = 1; - break; - case OPT_AUTO_ANSWER: - global.auto_answer = strtoul(optarg, &err, 10); - if (*err) { - printf("Error: expecting integer value for --auto-answer option\n"); - return -1; - } - break; - case OPT_AUTO_HANGUP: - global.auto_hangup = strtoul(optarg, &err, 10); - if (*err) { - printf("Error: expecting integer value for --auto-hangup option\n"); - return -1; - } - break; - } - } - - if (optind != argc) { - printf("Error: unknown options %s\n", argv[optind]); - return -1; - } - - if (global.reg_timeout == 0) - global.reg_timeout = 3600; - - return 0; -} - -/* Print dialog. */ -static void print_dialog(pjsip_dlg *dlg) -{ - if (!dlg) { - puts("none"); - return; - } - - printf("%s: call-id=%.*s", dlg->obj_name, - (int)dlg->call_id->id.slen, - dlg->call_id->id.ptr); - - printf(" (%s, %s)\n", pjsip_role_name(dlg->role), - pjsip_dlg_state_str(dlg->state)); -} - -/* Dump media statistic */ -void dump_media_statistic(pjsip_dlg *dlg) -{ - struct dialog_data *dlg_data = dlg->user_data; - pj_media_stream_stat stat[2]; - const char *statname[2] = { "TX", "RX" }; - int i; - - pj_media_session_get_stat (dlg_data->msession, 0, &stat[0], &stat[1]); - - printf("Media statistic:\n"); - for (i=0; i<2; ++i) { - printf(" %s statistics:\n", statname[i]); - printf(" Pkt TX=%d RX=%d\n", stat[i].pkt_tx, stat[i].pkt_rx); - printf(" Octets TX=%d RX=%d\n", stat[i].oct_tx, stat[i].oct_rx); - printf(" Jitter %d ms\n", stat[i].jitter); - printf(" Pkt lost %d\n", stat[i].pkt_lost); - } - printf("\n"); -} - -/* Print all dialogs. */ -static void print_all_dialogs() -{ - pjsip_dlg *dlg = (pjsip_dlg *)global.user_agent->dlg_list.next; - - puts("List all dialogs:"); - - while (dlg != (pjsip_dlg *) &global.user_agent->dlg_list) { - printf("%c", (dlg==global.cur_dlg ? '*' : ' ')); - print_dialog(dlg); - dlg = dlg->next; - } - - puts(""); -} - +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * 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 + */ + +/* + * THIS FILE IS INCLUDED BY main.c. + * IT WON'T COMPILE BY ITSELF. + */ + +#include "getopt.h" +#include + + +/* + * Display program usage + */ +static void usage() +{ + puts("Usage:"); + puts(" pjsua [options] [sip-url]"); + puts(""); + puts(" [sip-url] Default URL to invite."); + puts(""); + puts("General 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(" --help Display this help screen"); + puts(" --version Display version info"); + puts(""); + puts("Media options:"); + puts(" --null-audio Use NULL audio device"); + puts(""); + puts("User Agent options:"); + puts(" --auto-answer=sec Auto-answer all incoming calls after sec seconds."); + puts(" --auto-hangup=sec Auto-hangup all calls after sec seconds."); + puts(""); + puts("SIP options:"); + puts(" --local-port=port Set TCP/UDP port"); + 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(" --outbound=url Set the URL of outbound proxy server"); + puts(" --registrar=url Set the URL of registrar server"); + puts(" --reg-timeout=secs Set registration interval to secs (default 3600)"); + puts(""); + puts("Authentication options:"); + puts(" --realm=string Set realm"); + puts(" --username=string Set authentication username"); + puts(" --password=string Set authentication password"); + puts(""); + puts("STUN options (all must be specified):"); + puts(" --use-stun1=host[:port]"); + puts(" --use-stun2=host[:port] Use STUN and set host name and port of STUN servers"); + puts(""); + puts("SIMPLE options (may be specified more than once):"); + puts(" --add-buddy url Add the specified URL to the buddy list."); + puts(" --offer-x-ms-msg Offer \"x-ms-message\" in outgoing INVITE"); + puts(" --no-presence Do not subscribe presence of buddies"); + puts(""); + fflush(stdout); +} + +/* Display keystroke help. */ +static void keystroke_help() +{ + int i; + + printf("Advertise status as: %s\n", (global.hide_status ? "Offline" : "Online")); + puts(""); + puts("Buddy list:"); + puts("-------------------------------------------------------------------------------"); + for (i=0; i\n", i+1, global.buddy[i].ptr, + (global.buddy_status[i]?"Online":"Offline")); + } + //printf("-------------------------------------\n"); + puts(""); + //puts("Commands:"); + puts("+=============================================================================+"); + puts("| Call Commands: | IM & Presence: | Misc: |"); + puts("| | | |"); + puts("| m Make new call | i Send IM | o Send OPTIONS |"); + puts("| a Answer call | su Subscribe presence | d Dump status |"); + puts("| h Hangup call | us Unsubscribe presence | d1 Dump detailed |"); + puts("| ] Select next dialog | t Toggle Online status | |"); + puts("| [ Select previous dialog | | |"); + puts("+-----------------------------------------------------------------------------+"); + puts("| q QUIT |"); + puts("+=============================================================================+"); + puts(""); + + + fflush(stdout); +} + +/* + * Verify that valid SIP url is given. + */ +static pj_status_t verify_sip_url(char *url) +{ + pjsip_uri *p; + pj_pool_t *pool; + int len = (url ? strlen(url) : 0); + + if (!len) return -1; + + pool = pj_pool_create(global.pf, "check%p", 1024, 0, NULL); + if (!pool) return -1; + + 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); + 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"); + 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; + +} + +/* + * Parse program arguments + */ +static int parse_args(pj_pool_t *pool, 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}; + 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}, + { NULL, 0, 0, 0} + }; + 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 0: + config_file = optarg; + break; + } + if (config_file) + break; + } + + if (config_file) { + if (read_config_file(pool, config_file, &argc, &argv) != 0) + return -1; + } + + /* 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 *err, *p; + + switch (c) { + case OPT_LOG_FILE: + global.log_filename = optarg; + break; + case OPT_LOG_LEVEL: + c = strtoul(optarg, &err, 10); + if (*err) { + printf("Error: expecting integer value 0-6 for --log-level\n"); + return -1; + } + pj_log_set_level( c ); + break; + case OPT_APP_LOG_LEVEL: + global.app_log_level = strtoul(optarg, &err, 10); + if (*err) { + printf("Error: expecting integer value 0-6 for --app-log-level\n"); + return -1; + } + break; + case OPT_HELP: + usage(); + return -1; + case OPT_VERSION: /* version */ + pj_dump_config(); + return -1; + case OPT_NULL_AUDIO: + global.null_audio = 1; + break; + case OPT_LOCAL_PORT: /* local-port */ + global.sip_port = strtoul(optarg, &err, 10); + if (*err) { + printf("Error: expecting integer value for --local-port\n"); + return -1; + } + break; + case OPT_PROXY: /* proxy */ + if (verify_sip_url(optarg) != 0) { + printf("Error: invalid SIP URL '%s' in proxy argument\n", optarg); + return -1; + } + global.proxy = pj_str(optarg); + break; + case OPT_OUTBOUND_PROXY: /* outbound proxy */ + if (verify_sip_url(optarg) != 0) { + printf("Error: invalid SIP URL '%s' in outbound proxy argument\n", optarg); + return -1; + } + global.outbound_proxy = pj_str(optarg); + break; + case OPT_REGISTRAR: /* registrar */ + if (verify_sip_url(optarg) != 0) { + printf("Error: invalid SIP URL '%s' in registrar argument\n", optarg); + return -1; + } + global.registrar_uri = pj_str(optarg); + break; + case OPT_REG_TIMEOUT: /* reg-timeout */ + global.reg_timeout = strtoul(optarg, &err, 10); + if (*err) { + printf("Error: expecting integer value for --reg-timeout\n"); + return -1; + } + break; + case OPT_ID: /* id */ + if (verify_sip_url(optarg) != 0) { + printf("Error: invalid SIP URL '%s' in local id argument\n", optarg); + return -1; + } + global.local_uri = pj_str(optarg); + break; + case OPT_CONTACT: /* contact */ + if (verify_sip_url(optarg) != 0) { + printf("Error: invalid SIP URL '%s' in contact argument\n", optarg); + return -1; + } + global.contact = pj_str(optarg); + break; + case OPT_USERNAME: /* Default authentication user */ + if (!global.cred_count) global.cred_count = 1; + global.cred_info[0].username = pj_str(optarg); + break; + case OPT_REALM: /* Default authentication realm. */ + if (!global.cred_count) global.cred_count = 1; + global.cred_info[0].realm = pj_str(optarg); + break; + case OPT_PASSWORD: /* authentication password */ + if (!global.cred_count) global.cred_count = 1; + global.cred_info[0].data_type = 0; + global.cred_info[0].data = pj_str(optarg); + break; + case OPT_USE_STUN1: /* STUN server 1 */ + p = pj_native_strchr(optarg, ':'); + if (p) { + *p = '\0'; + global.stun_srv1 = pj_str(optarg); + global.stun_port1 = strtoul(p+1, &err, 10); + if (*err || global.stun_port1==0) { + printf("Error: expecting port number with option --use-stun1\n"); + return -1; + } + } else { + global.stun_port1 = 3478; + global.stun_srv1 = pj_str(optarg); + } + break; + case OPT_USE_STUN2: /* STUN server 2 */ + p = pj_native_strchr(optarg, ':'); + if (p) { + *p = '\0'; + global.stun_srv2 = pj_str(optarg); + global.stun_port2 = strtoul(p+1, &err, 10); + if (*err || global.stun_port2==0) { + printf("Error: expecting port number with option --use-stun2\n"); + return -1; + } + } else { + global.stun_port2 = 3478; + global.stun_srv2 = pj_str(optarg); + } + break; + case OPT_ADD_BUDDY: /* Add to buddy list. */ + if (verify_sip_url(optarg) != 0) { + printf("Error: invalid URL '%s' in --add-buddy option\n", optarg); + return -1; + } + if (global.buddy_cnt == MAX_BUDDIES) { + printf("Error: too many buddies in buddy list.\n"); + return -1; + } + global.buddy[global.buddy_cnt++] = pj_str(optarg); + break; + case OPT_OFFER_X_MS_MSG: + global.offer_x_ms_msg = 1; + break; + case OPT_NO_PRESENCE: + global.no_presence = 1; + break; + case OPT_AUTO_ANSWER: + global.auto_answer = strtoul(optarg, &err, 10); + if (*err) { + printf("Error: expecting integer value for --auto-answer option\n"); + return -1; + } + break; + case OPT_AUTO_HANGUP: + global.auto_hangup = strtoul(optarg, &err, 10); + if (*err) { + printf("Error: expecting integer value for --auto-hangup option\n"); + return -1; + } + break; + } + } + + if (optind != argc) { + printf("Error: unknown options %s\n", argv[optind]); + return -1; + } + + if (global.reg_timeout == 0) + global.reg_timeout = 3600; + + return 0; +} + +/* Print dialog. */ +static void print_dialog(pjsip_dlg *dlg) +{ + if (!dlg) { + puts("none"); + return; + } + + printf("%s: call-id=%.*s", dlg->obj_name, + (int)dlg->call_id->id.slen, + dlg->call_id->id.ptr); + + printf(" (%s, %s)\n", pjsip_role_name(dlg->role), + pjsip_dlg_state_str(dlg->state)); +} + +/* Dump media statistic */ +void dump_media_statistic(pjsip_dlg *dlg) +{ + struct dialog_data *dlg_data = dlg->user_data; + pj_media_stream_stat stat[2]; + const char *statname[2] = { "TX", "RX" }; + int i; + + pj_media_session_get_stat (dlg_data->msession, 0, &stat[0], &stat[1]); + + printf("Media statistic:\n"); + for (i=0; i<2; ++i) { + printf(" %s statistics:\n", statname[i]); + printf(" Pkt TX=%d RX=%d\n", stat[i].pkt_tx, stat[i].pkt_rx); + printf(" Octets TX=%d RX=%d\n", stat[i].oct_tx, stat[i].oct_rx); + printf(" Jitter %d ms\n", stat[i].jitter); + printf(" Pkt lost %d\n", stat[i].pkt_lost); + } + printf("\n"); +} + +/* Print all dialogs. */ +static void print_all_dialogs() +{ + pjsip_dlg *dlg = (pjsip_dlg *)global.user_agent->dlg_list.next; + + puts("List all dialogs:"); + + while (dlg != (pjsip_dlg *) &global.user_agent->dlg_list) { + printf("%c", (dlg==global.cur_dlg ? '*' : ' ')); + print_dialog(dlg); + dlg = dlg->next; + } + + puts(""); +} + -- cgit v1.2.3